@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
package/ai/components/list.md
CHANGED
|
@@ -69,7 +69,7 @@ Lists show rows of related items: settings, conversations, people. `bf-list` is
|
|
|
69
69
|
|
|
70
70
|
## When to use
|
|
71
71
|
|
|
72
|
-
Use `bf-list` for a group of rows of the same kind: settings, conversations, people. Use [bf-card](https://bitforge-ui.web.app/components/card) instead for one piece of content with its own layout, and
|
|
72
|
+
Use `bf-list` for a group of rows of the same kind: settings, conversations, people. Use [bf-card](https://bitforge-ui.web.app/components/card) instead for one piece of content with its own layout, and [bf-table](https://bitforge-ui.web.app/components/table) for data the user compares across columns.
|
|
73
73
|
|
|
74
74
|
## Basic usage
|
|
75
75
|
|
|
@@ -128,9 +128,63 @@ import { BfCardComponent, BfListComponent, BfListItemComponent } from '@bitforge
|
|
|
128
128
|
export class ListPlainExampleComponent {}
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
+
## Divided
|
|
132
|
+
|
|
133
|
+
`divided` separates the rows with a `border` line instead of a gap and squares off their surfaces, so the hover fill of a navigation or action row reaches the list's edges. It is off by default, so existing lists keep their look. On an inset list the rows become one rounded surface (only the first and last rows keep the card radius); on a plain list they sit flat on whatever holds them.
|
|
134
|
+
|
|
135
|
+
## On a card
|
|
136
|
+
|
|
137
|
+
To put rows on a card, use a plain divided list and pull it flush to the card's inline edges with `class="-mx-card"` (the card's `md` padding; `-mx-card-lg` on a `padding="lg"` card). The card stays the only container, the lines run edge to edge, and a row's hover fill reaches the card's edge instead of stopping at the card padding. A plain divided list gives its rows the card's inline padding, so their text stays on the card's content edge, aligned with the heading above.
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
import { Component } from '@angular/core';
|
|
141
|
+
import {
|
|
142
|
+
BfActionDirective,
|
|
143
|
+
BfBadgeComponent,
|
|
144
|
+
BfCardComponent,
|
|
145
|
+
BfListComponent,
|
|
146
|
+
BfListItemComponent,
|
|
147
|
+
} from '@bitforgehq/ui';
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* A divided list flush to a card's edges.
|
|
151
|
+
*
|
|
152
|
+
* `[inset]="false" divided` with `class="-mx-card"`: the lines and the rows' hover fill run to
|
|
153
|
+
* the card's edges, and the row text stays aligned with the heading.
|
|
154
|
+
*/
|
|
155
|
+
@Component({
|
|
156
|
+
selector: 'app-example-list-card',
|
|
157
|
+
imports: [
|
|
158
|
+
BfActionDirective,
|
|
159
|
+
BfBadgeComponent,
|
|
160
|
+
BfCardComponent,
|
|
161
|
+
BfListComponent,
|
|
162
|
+
BfListItemComponent,
|
|
163
|
+
],
|
|
164
|
+
template: `
|
|
165
|
+
<bf-card class="max-w-md">
|
|
166
|
+
<h3 class="text-heading">Projects</h3>
|
|
167
|
+
<bf-list [inset]="false" divided class="-mx-card">
|
|
168
|
+
<bf-list-item subtitle="Due Friday" chevron>
|
|
169
|
+
<a bfSlot="title" bfAction href="#atlas">Atlas rebrand</a>
|
|
170
|
+
</bf-list-item>
|
|
171
|
+
<bf-list-item subtitle="Planning" chevron>
|
|
172
|
+
<a bfSlot="title" bfAction href="#harbor">Harbor onboarding</a>
|
|
173
|
+
</bf-list-item>
|
|
174
|
+
<bf-list-item subtitle="In review" chevron>
|
|
175
|
+
<a bfSlot="title" bfAction href="#lumen">Lumen reports</a>
|
|
176
|
+
<bf-badge bfSlot="trailing" variant="warning">2 open</bf-badge>
|
|
177
|
+
</bf-list-item>
|
|
178
|
+
</bf-list>
|
|
179
|
+
</bf-card>
|
|
180
|
+
`,
|
|
181
|
+
})
|
|
182
|
+
export class ListCardExampleComponent {}
|
|
183
|
+
```
|
|
184
|
+
|
|
131
185
|
## Theming
|
|
132
186
|
|
|
133
|
-
`inset` (default) uses the `bg` background on a `card` radius; `[inset]="false"` is transparent with no radius. The rows' own tokens are on the [`bf-list-item` page](https://bitforge-ui.web.app/components/list-item#theming).
|
|
187
|
+
`inset` (default) uses the `bg` background on a `card` radius; `[inset]="false"` is transparent with no radius. `divided` draws the lines in `border`; a divided inset list is a `surface` with the `card` radius, and a divided plain list pads its rows with `--spacing-card` (`px-card`). The rows' own tokens are on the [`bf-list-item` page](https://bitforge-ui.web.app/components/list-item#theming).
|
|
134
188
|
|
|
135
189
|
## Accessibility
|
|
136
190
|
|
|
@@ -138,9 +192,10 @@ export class ListPlainExampleComponent {}
|
|
|
138
192
|
|
|
139
193
|
## Inputs
|
|
140
194
|
|
|
141
|
-
| Name
|
|
142
|
-
|
|
|
143
|
-
| `inset`
|
|
195
|
+
| Name | Type | Default | Description |
|
|
196
|
+
| --------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
197
|
+
| `inset` | `boolean` | `true` | Grouped look with the warm container; false renders rows on the page background. |
|
|
198
|
+
| `divided` | `boolean` | `false` | Separates the rows with a `border` line instead of a gap, with square rows whose hover reaches the list's edges. Off by default. |
|
|
144
199
|
|
|
145
200
|
## Slots
|
|
146
201
|
|
|
@@ -151,15 +206,24 @@ export class ListPlainExampleComponent {}
|
|
|
151
206
|
### Do
|
|
152
207
|
|
|
153
208
|
- Group rows that belong together in one `bf-list`; start a new list under a heading for a new group.
|
|
154
|
-
- Use `[inset]="false"` on a card, so the card is the only container.
|
|
209
|
+
- Use `[inset]="false"` on a card, so the card is the only container; add `divided` and `class="-mx-card"` for rows that run edge to edge.
|
|
155
210
|
|
|
156
211
|
### Don't
|
|
157
212
|
|
|
158
213
|
- Don't put anything but `bf-list-item` rows in a `bf-list`; its `role="list"` expects list items.
|
|
159
214
|
- Don't nest a `bf-list` inside a row.
|
|
215
|
+
- Don't add your own borders or padding to the rows to separate them; use `divided`.
|
|
160
216
|
|
|
161
217
|
## Migration notes
|
|
162
218
|
|
|
219
|
+
### 0.3.1
|
|
220
|
+
|
|
221
|
+
New opt-in `divided` input: a `border` line between rows instead of a gap, with square rows whose
|
|
222
|
+
hover fill reaches the list's edges. On a card, `<bf-list [inset]="false" divided class="-mx-card">`
|
|
223
|
+
runs the rows edge to edge, and the rows take the card's inline padding so their text stays aligned
|
|
224
|
+
with the card's content. A divided inset list is one rounded `surface`. Lists without `divided`
|
|
225
|
+
look as before. When to use links `bf-table` for data compared across columns.
|
|
226
|
+
|
|
163
227
|
### 0.3.0
|
|
164
228
|
|
|
165
229
|
JSDoc of `list.component.ts` for the API reference: `@slot` docs for `default`. No behaviour change.
|
package/ai/components/menu.md
CHANGED
|
@@ -619,6 +619,11 @@ From the [APG menu button](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/
|
|
|
619
619
|
|
|
620
620
|
## Migration notes
|
|
621
621
|
|
|
622
|
+
### 0.3.1
|
|
623
|
+
|
|
624
|
+
The menu's positions now come from the anchored-panel positioning the popover and tooltip share.
|
|
625
|
+
No behaviour change.
|
|
626
|
+
|
|
622
627
|
### 0.3.0
|
|
623
628
|
|
|
624
629
|
New `changed` output on `bfMenuItemCheckbox`: the new `checked` state right after the user toggles
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# bf-multi-combobox
|
|
2
|
+
|
|
3
|
+
Text field that filters options as you type and picks several as tags
|
|
4
|
+
|
|
5
|
+
`BfMultiComboboxComponent` 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 { BfMultiComboboxComponent, type BfComboboxOption } from '@bitforgehq/ui/overlay';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The bf-multi-combobox overview.
|
|
15
|
+
*
|
|
16
|
+
* Live demo of `bf-multi-combobox`: tag a task with labels. Typing filters the labels, `Enter` or
|
|
17
|
+
* a click adds the active one as a tag (or removes it when it is a tag already) and the list
|
|
18
|
+
* stays open for the next; each tag's button removes it, and `Backspace` in the empty field
|
|
19
|
+
* removes the last one. The result line lists the labels.
|
|
20
|
+
*/
|
|
21
|
+
@Component({
|
|
22
|
+
selector: 'app-example-multi-combobox-overview',
|
|
23
|
+
imports: [BfMultiComboboxComponent],
|
|
24
|
+
template: `
|
|
25
|
+
<div class="flex max-w-md flex-col gap-3">
|
|
26
|
+
<bf-multi-combobox
|
|
27
|
+
label="Labels"
|
|
28
|
+
placeholder="Add labels"
|
|
29
|
+
[options]="labels"
|
|
30
|
+
[(value)]="picked"
|
|
31
|
+
/>
|
|
32
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
33
|
+
</div>
|
|
34
|
+
`,
|
|
35
|
+
})
|
|
36
|
+
export class MultiComboboxOverviewExampleComponent {
|
|
37
|
+
protected readonly labels: readonly BfComboboxOption[] = [
|
|
38
|
+
{ value: 'bug', label: 'Bug' },
|
|
39
|
+
{ value: 'design', label: 'Design' },
|
|
40
|
+
{ value: 'docs', label: 'Documentation' },
|
|
41
|
+
{ value: 'perf', label: 'Performance' },
|
|
42
|
+
{ value: 'a11y', label: 'Accessibility' },
|
|
43
|
+
{ value: 'security', label: 'Security' },
|
|
44
|
+
];
|
|
45
|
+
readonly picked = signal<readonly string[]>(['design']);
|
|
46
|
+
|
|
47
|
+
readonly status = computed(() => {
|
|
48
|
+
const names = this.labels.filter((l) => this.picked().includes(l.value)).map((l) => l.label);
|
|
49
|
+
return names.length ? `Labels: ${names.join(', ')}.` : 'No labels.';
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Overview
|
|
55
|
+
|
|
56
|
+
Text field with a filtered list of options to pick several values from, shown as removable tags
|
|
57
|
+
inside the field (the ARIA combobox pattern with a multi-select listbox). Typing filters the
|
|
58
|
+
list by label, ignoring case and accents; `Enter` or a click adds or removes the active option
|
|
59
|
+
and the list stays open. Generic over the option value (a string by default); the value is an
|
|
60
|
+
array, empty while nothing is picked. For one value use `bf-combobox`.
|
|
61
|
+
|
|
62
|
+
A text field with a list of options that filters as you type, to pick several values, shown as
|
|
63
|
+
removable tags inside the field: labels for a task, reviewers, skills. The same
|
|
64
|
+
[ARIA 1.2 combobox](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) pattern as `bf-combobox`,
|
|
65
|
+
with a multi-select listbox: the list stays open after each pick, and picked options carry a check
|
|
66
|
+
mark. Its value is an array (`readonly T[]`, empty while nothing is picked), bound with
|
|
67
|
+
`[(value)]` or `[formField]`.
|
|
68
|
+
|
|
69
|
+
Import `BfMultiComboboxComponent` and `BfComboboxOption` from `@bitforgehq/ui/overlay` (needs
|
|
70
|
+
`@angular/cdk`).
|
|
71
|
+
|
|
72
|
+
## When to use
|
|
73
|
+
|
|
74
|
+
Use `bf-multi-combobox` to pick any number of values from a list long enough to search. Use
|
|
75
|
+
[bf-combobox](https://bitforge-ui.web.app/components/combobox) instead for one value, [bf-chip](https://bitforge-ui.web.app/components/chip) filter
|
|
76
|
+
chips for a few options shown all at once, and checkboxes in a
|
|
77
|
+
[bf-popover](https://bitforge-ui.web.app/components/popover) for a short list of filters. Both work in both shells; in a
|
|
78
|
+
Capacitor app set `provideBfDefaults({ fieldSize: 'lg' })` for 48 px fields.
|
|
79
|
+
|
|
80
|
+
## Tags
|
|
81
|
+
|
|
82
|
+
Each picked value is a tag before the text, in the order it was picked. A tag's button (named
|
|
83
|
+
"Remove …" by the `comboboxRemove` label) removes it and puts focus back in the field, and
|
|
84
|
+
`Backspace` in the empty field removes the last tag. `(changed)` emits the new array after each
|
|
85
|
+
add and remove. The field grows in height as the tags wrap, and the open list follows its bottom
|
|
86
|
+
edge. While `disabled` or `readonly` the tags have no remove buttons.
|
|
87
|
+
|
|
88
|
+
## Typing and picking
|
|
89
|
+
|
|
90
|
+
Typing filters the options by label, ignoring case and accents, and opens the list. `Enter` or a
|
|
91
|
+
click on the active option adds it, or removes it when it is a tag already; the typed text clears
|
|
92
|
+
and the list stays open for the next pick. `Escape` closes the list, a second `Escape` drops the
|
|
93
|
+
typed text, and leaving the field drops it too. Options take a `description`, `disabled` and
|
|
94
|
+
`group` (listed under a heading), as on `bf-combobox`.
|
|
95
|
+
|
|
96
|
+
`filter`, `(queried)`, `loading` and `[filter]="null"` work as on `bf-combobox` for a server
|
|
97
|
+
search; the field keeps the labels of picked values that later results no longer hold.
|
|
98
|
+
|
|
99
|
+
## With Signal Forms
|
|
100
|
+
|
|
101
|
+
Model the field as `readonly T[]`, starting at `[]`, and bind it with `[formField]`. `placeholder`
|
|
102
|
+
shows while nothing is picked. For object values pass `compareWith`, as on `bf-combobox`.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { Component, signal } from '@angular/core';
|
|
106
|
+
import { FormField, form } from '@angular/forms/signals';
|
|
107
|
+
import { BfMultiComboboxComponent, type BfComboboxOption } from '@bitforgehq/ui/overlay';
|
|
108
|
+
|
|
109
|
+
interface Person {
|
|
110
|
+
id: number;
|
|
111
|
+
name: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A multi combobox bound to a Signal Forms field.
|
|
116
|
+
*
|
|
117
|
+
* The field holds an array of objects, starting with one reviewer; `compareWith` matches them to
|
|
118
|
+
* the options by id, so reviewers reloaded from a server still show as tags and check marks.
|
|
119
|
+
*/
|
|
120
|
+
@Component({
|
|
121
|
+
selector: 'app-example-multi-combobox-forms',
|
|
122
|
+
imports: [BfMultiComboboxComponent, FormField],
|
|
123
|
+
template: `
|
|
124
|
+
<bf-multi-combobox
|
|
125
|
+
class="max-w-md"
|
|
126
|
+
label="Reviewers"
|
|
127
|
+
placeholder="Add reviewers"
|
|
128
|
+
helpText="Each reviewer gets a notification."
|
|
129
|
+
[options]="people"
|
|
130
|
+
[compareWith]="sameId"
|
|
131
|
+
[formField]="review.reviewers"
|
|
132
|
+
/>
|
|
133
|
+
`,
|
|
134
|
+
})
|
|
135
|
+
export class MultiComboboxFormsExampleComponent {
|
|
136
|
+
protected readonly people: readonly BfComboboxOption<Person>[] = [
|
|
137
|
+
{ value: { id: 1, name: 'Ada' }, label: 'Ada Lovelace' },
|
|
138
|
+
{ value: { id: 2, name: 'Grace' }, label: 'Grace Hopper' },
|
|
139
|
+
{ value: { id: 3, name: 'Katherine' }, label: 'Katherine Johnson' },
|
|
140
|
+
];
|
|
141
|
+
protected readonly sameId = (a: Person, b: Person): boolean => a.id === b.id;
|
|
142
|
+
private readonly model = signal<{ reviewers: readonly Person[] }>({
|
|
143
|
+
reviewers: [{ id: 2, name: 'Grace' }],
|
|
144
|
+
});
|
|
145
|
+
protected readonly review = form(this.model);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Desktop
|
|
150
|
+
|
|
151
|
+
The field keeps focus while the arrow keys move through the list; the pointer moving over an
|
|
152
|
+
option makes it the active one. A press anywhere on the field's box (around the tags) puts the
|
|
153
|
+
caret in the field and opens the list. Under a fine pointer a tag's remove button is 24 px; under a
|
|
154
|
+
coarse pointer its hit area grows to 44 px and every option row is at least 44 px tall.
|
|
155
|
+
|
|
156
|
+
## Theming
|
|
157
|
+
|
|
158
|
+
- Field: the `bf-input` tokens on the box around the tags: `border-strong` border
|
|
159
|
+
(`danger-border` on error), `primary` border and `ring` ring while the field has focus,
|
|
160
|
+
`surface-muted` while read-only or disabled, a `text-subtle` chevron.
|
|
161
|
+
- Tags: `primary-soft` with `primary-strong` text (the selected tonal chip's colours);
|
|
162
|
+
the remove button hovers `primary-soft-hover` and presses `primary-soft-pressed`.
|
|
163
|
+
- List: as on `bf-combobox`, with a check mark on each picked option.
|
|
164
|
+
|
|
165
|
+
## Accessibility
|
|
166
|
+
|
|
167
|
+
- The input is `role="combobox"` with `aria-autocomplete="list"`, `aria-expanded`,
|
|
168
|
+
`aria-controls` and `aria-activedescendant`, as on `bf-combobox`; the listbox has
|
|
169
|
+
`aria-multiselectable="true"` and every picked option `aria-selected="true"`.
|
|
170
|
+
- Each tag's remove button is a native button named "Remove" plus the tag's label, in the tab
|
|
171
|
+
order before the input.
|
|
172
|
+
- Labels, help and error text, `aria-describedby`, `aria-invalid` and `aria-errormessage` follow
|
|
173
|
+
`bf-select`. `required` sets the native attribute only while no tag is picked.
|
|
174
|
+
|
|
175
|
+
### Keyboard
|
|
176
|
+
|
|
177
|
+
| Key | Where | Action |
|
|
178
|
+
| ---------------------------------------------------- | ------------- | --------------------------------------------------------------- |
|
|
179
|
+
| <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | Field | Opens the list on the first tag's option (else first / last) |
|
|
180
|
+
| <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | Open list | Next / previous enabled option, wrapping at the ends |
|
|
181
|
+
| <kbd>Home</kbd> / <kbd>End</kbd> | Open list | First / last enabled option |
|
|
182
|
+
| <kbd>Enter</kbd> | Open list | Adds the active option, or removes it; the list stays open |
|
|
183
|
+
| <kbd>Backspace</kbd> | Empty field | Removes the last tag |
|
|
184
|
+
| <kbd>Escape</kbd> | Open list | Closes the list |
|
|
185
|
+
| <kbd>Escape</kbd> | Field | Drops the typed text |
|
|
186
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Field | Moves on (or back to the tags' remove buttons); the list closes |
|
|
187
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Remove button | Removes that tag |
|
|
188
|
+
| <kbd>A</kbd>–<kbd>Z</kbd>, <kbd>0</kbd>–<kbd>9</kbd> | Field | Filters the list and opens it |
|
|
189
|
+
|
|
190
|
+
## Inputs
|
|
191
|
+
|
|
192
|
+
| Name | Type | Default | Description |
|
|
193
|
+
| ---------------- | ----------------------------------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
194
|
+
| `options` | `readonly BfComboboxOption<T>[]` | required | The options to choose from; with server search, the latest results. |
|
|
195
|
+
| `compareWith` | `BfCompareWith<T>` | `Object.is` | Equality for matching `value` to an option (default `Object.is`); pass one for object values. |
|
|
196
|
+
| `filter` | `BfComboboxFilter<T> \| null` | `labelContains` | Whether an option matches the typed text (default: its label contains it, ignoring case and accents); `null` shows `options` unfiltered, for results filtered by a server. |
|
|
197
|
+
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Height step of the field (its minimum height): sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()` (`fieldSize`, `md` by default). |
|
|
198
|
+
| `label` | `string` | `''` | Visible label above the field. |
|
|
199
|
+
| `ariaLabel` | `string` | `''` | Accessible name when no visible label is rendered. Ignored when `label` is set. |
|
|
200
|
+
| `ariaLabelledby` | `string` | `''` | Id(s) of the element(s) that name the field when no visible label is rendered. Ignored when `label` is set. |
|
|
201
|
+
| `placeholder` | `string` | `''` | Hint text shown while nothing is picked. |
|
|
202
|
+
| `helpText` | `string` | `''` | Helper text under the field. |
|
|
203
|
+
| `errorMessage` | `string` | `''` | Explicit error text; takes precedence over Signal Forms errors. |
|
|
204
|
+
| `emptyLabel` | `string` | `''` | Text of the list's row when no option matches; empty uses the `comboboxEmpty` label. |
|
|
205
|
+
| `loading` | `boolean` | `false` | Shows a loading row in the list, while a server search for the typed text runs. |
|
|
206
|
+
| `disabled` | `boolean` | `false` | Disables the field and its tags. Set by `[formField]`; do not bind it yourself. |
|
|
207
|
+
| `readonly` | `boolean` | `false` | Shows the tags but blocks changes: no remove buttons, and the list never opens. Set by `[formField]`; do not bind it yourself. |
|
|
208
|
+
| `required` | `boolean` | `false` | Marks the field required (at least one value). Set by `[formField]`; do not bind it yourself. |
|
|
209
|
+
| `invalid` | `boolean` | `false` | Field validity from Signal Forms. Set by `[formField]`; do not bind it yourself. |
|
|
210
|
+
| `touched` | `boolean` | `false` | Whether the user has left the field. Set by `[formField]`; do not bind it yourself. |
|
|
211
|
+
| `errors` | `readonly ValidationError.WithOptionalFieldTree[]` | `[]` | Validation errors from Signal Forms. Set by `[formField]`; do not bind it yourself. |
|
|
212
|
+
| `name` | `string` | `''` | Native name attribute of the text input. Set by `[formField]`; do not bind it yourself. |
|
|
213
|
+
| `id` | `string` | `generated` | Input id; generated when not set. |
|
|
214
|
+
| `value` | `readonly T[]` | `[]` | The picked options' values in the order they were picked, empty while nothing is picked; two-way bindable and bound by `[formField]`. Two-way: `[(value)]`. |
|
|
215
|
+
|
|
216
|
+
## Outputs
|
|
217
|
+
|
|
218
|
+
| Name | Payload | Description |
|
|
219
|
+
| --------- | -------------- | --------------------------------------------------------------------------------------------------------- |
|
|
220
|
+
| `changed` | `readonly T[]` | Emitted with the new values after the user adds or removes one. |
|
|
221
|
+
| `queried` | `string` | Emitted with the typed text each time the user changes it (`''` once it is cleared), for a server search. |
|
|
222
|
+
| `focused` | `FocusEvent` | Emitted when the input gains focus. |
|
|
223
|
+
| `blurred` | `FocusEvent` | Emitted when the input loses focus. |
|
|
224
|
+
| `touch` | `void` | Emitted on blur so Signal Forms marks the field touched. Set by `[formField]`; do not bind it yourself. |
|
|
225
|
+
|
|
226
|
+
## Methods
|
|
227
|
+
|
|
228
|
+
### `focus(options?: FocusOptions): void`
|
|
229
|
+
|
|
230
|
+
Focuses the input; Signal Forms calls it from `focusBoundControl()`.
|
|
231
|
+
|
|
232
|
+
| Parameter | Type | Description |
|
|
233
|
+
| ---------- | -------------- | --------------------------------------------------------------------- |
|
|
234
|
+
| `options?` | `FocusOptions` | Passed on to the native `focus()`, such as `{ preventScroll: true }`. |
|
|
235
|
+
|
|
236
|
+
## Notes
|
|
237
|
+
|
|
238
|
+
### Do
|
|
239
|
+
|
|
240
|
+
- Keep tag labels short; long ones truncate with an ellipsis.
|
|
241
|
+
- Show the picked values somewhere else too when the field is narrow and many are picked.
|
|
242
|
+
|
|
243
|
+
### Don't
|
|
244
|
+
|
|
245
|
+
- Don't use it for one value; use `bf-combobox`.
|
|
246
|
+
- Don't make it the only way to see a long list of picks; tags wrap but get hard to scan.
|
|
247
|
+
|
|
248
|
+
## Migration notes
|
|
249
|
+
|
|
250
|
+
### 0.3.1
|
|
251
|
+
|
|
252
|
+
New `bf-multi-combobox` (`BfMultiComboboxComponent<T>`) in `@bitforgehq/ui/overlay`: the
|
|
253
|
+
combobox for several values, `[(value)]` a `readonly T[]`. Picked values are tags inside the
|
|
254
|
+
field, each with a remove button named by the new `comboboxRemove(label)` label ("Remove
|
|
255
|
+
Design"); `Backspace` in the empty field removes the last tag, the list stays open after a pick
|
|
256
|
+
and picked options carry a check mark (`aria-multiselectable`, `aria-selected`). Same options,
|
|
257
|
+
filtering, server search and Signal Forms contract as `bf-combobox`.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# bf-nav-group
|
|
2
|
+
|
|
3
|
+
A titled group of sidebar links with a small heading that names it
|
|
4
|
+
|
|
5
|
+
`BfNavGroupComponent` from `@bitforgehq/ui` · Navigation · since 0.3.1
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component } from '@angular/core';
|
|
11
|
+
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
12
|
+
import { BfNavGroupComponent, BfNavItemComponent, BfSidebarComponent } from '@bitforgehq/ui';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The bf-nav-group overview.
|
|
16
|
+
*
|
|
17
|
+
* Live demo of `bf-nav-group`: two titled groups of `a[bfNavItem]` router links in a sidebar;
|
|
18
|
+
* each heading names its group for screen readers.
|
|
19
|
+
*/
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'app-example-nav-group-overview',
|
|
22
|
+
imports: [
|
|
23
|
+
RouterLink,
|
|
24
|
+
RouterLinkActive,
|
|
25
|
+
BfNavGroupComponent,
|
|
26
|
+
BfNavItemComponent,
|
|
27
|
+
BfSidebarComponent,
|
|
28
|
+
],
|
|
29
|
+
template: `
|
|
30
|
+
<bf-sidebar label="Component groups">
|
|
31
|
+
<bf-nav-group label="Navigation">
|
|
32
|
+
<a
|
|
33
|
+
bfNavItem
|
|
34
|
+
routerLink="/components/nav-group"
|
|
35
|
+
routerLinkActive
|
|
36
|
+
ariaCurrentWhenActive="page"
|
|
37
|
+
>Nav group</a
|
|
38
|
+
>
|
|
39
|
+
<a bfNavItem routerLink="/components/nav-item" routerLinkActive ariaCurrentWhenActive="page"
|
|
40
|
+
>Nav item</a
|
|
41
|
+
>
|
|
42
|
+
</bf-nav-group>
|
|
43
|
+
<bf-nav-group label="Data display">
|
|
44
|
+
<a bfNavItem routerLink="/components/list" routerLinkActive ariaCurrentWhenActive="page"
|
|
45
|
+
>List</a
|
|
46
|
+
>
|
|
47
|
+
<a bfNavItem routerLink="/components/card" routerLinkActive ariaCurrentWhenActive="page"
|
|
48
|
+
>Card</a
|
|
49
|
+
>
|
|
50
|
+
</bf-nav-group>
|
|
51
|
+
</bf-sidebar>
|
|
52
|
+
`,
|
|
53
|
+
})
|
|
54
|
+
export class NavGroupOverviewExampleComponent {}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Overview
|
|
58
|
+
|
|
59
|
+
A titled group of `a[bfNavItem]` links inside a `bf-sidebar`: `label` is shown as a small
|
|
60
|
+
heading above them and names the group (`role="group"`) for screen readers, also while the
|
|
61
|
+
sidebar is collapsed and the heading is hidden.
|
|
62
|
+
|
|
63
|
+
A titled group of [`a[bfNavItem]`](https://bitforge-ui.web.app/components/nav-item) links inside a [`bf-sidebar`](https://bitforge-ui.web.app/components/sidebar): its `label` is a small heading above the links and the group's accessible name.
|
|
64
|
+
|
|
65
|
+
## When to use
|
|
66
|
+
|
|
67
|
+
Use `bf-nav-group` to split a long sidebar into sections, such as Workspace and Admin. Leave a short sidebar ungrouped, with the links straight in `bf-sidebar`. 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.
|
|
68
|
+
|
|
69
|
+
## Basic usage
|
|
70
|
+
|
|
71
|
+
Put the group's links in its default slot and its name in `label`. Groups stack with 24 px between them.
|
|
72
|
+
|
|
73
|
+
## In a collapsed sidebar
|
|
74
|
+
|
|
75
|
+
While the sidebar is collapsed to an icon rail the heading is hidden visually (`sr-only`), so only the icons show, and it still names the group for screen readers.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { Component } from '@angular/core';
|
|
79
|
+
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
80
|
+
import { BfNavGroupComponent, BfNavItemComponent, BfSidebarComponent } from '@bitforgehq/ui';
|
|
81
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
82
|
+
import { Layers, LayoutList, PanelTop, SquareStack } from 'lucide';
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Groups in a collapsed sidebar.
|
|
86
|
+
*
|
|
87
|
+
* The headings are hidden visually and still name their groups for screen readers; the items
|
|
88
|
+
* show their icons only.
|
|
89
|
+
*/
|
|
90
|
+
@Component({
|
|
91
|
+
selector: 'app-example-nav-group-collapsed',
|
|
92
|
+
imports: [
|
|
93
|
+
RouterLink,
|
|
94
|
+
RouterLinkActive,
|
|
95
|
+
BfIconComponent,
|
|
96
|
+
BfNavGroupComponent,
|
|
97
|
+
BfNavItemComponent,
|
|
98
|
+
BfSidebarComponent,
|
|
99
|
+
],
|
|
100
|
+
template: `
|
|
101
|
+
<bf-sidebar label="Component groups, collapsed" [collapsed]="true">
|
|
102
|
+
<bf-nav-group label="Navigation">
|
|
103
|
+
<a
|
|
104
|
+
bfNavItem
|
|
105
|
+
routerLink="/components/nav-group"
|
|
106
|
+
routerLinkActive
|
|
107
|
+
ariaCurrentWhenActive="page"
|
|
108
|
+
>
|
|
109
|
+
<bf-icon bfSlot="icon" [icon]="layers" />
|
|
110
|
+
Nav group
|
|
111
|
+
</a>
|
|
112
|
+
<a bfNavItem routerLink="/components/sidebar" routerLinkActive ariaCurrentWhenActive="page">
|
|
113
|
+
<bf-icon bfSlot="icon" [icon]="panelTop" />
|
|
114
|
+
Sidebar
|
|
115
|
+
</a>
|
|
116
|
+
</bf-nav-group>
|
|
117
|
+
<bf-nav-group label="Data display">
|
|
118
|
+
<a bfNavItem routerLink="/components/list" routerLinkActive ariaCurrentWhenActive="page">
|
|
119
|
+
<bf-icon bfSlot="icon" [icon]="list" />
|
|
120
|
+
List
|
|
121
|
+
</a>
|
|
122
|
+
<a bfNavItem routerLink="/components/card" routerLinkActive ariaCurrentWhenActive="page">
|
|
123
|
+
<bf-icon bfSlot="icon" [icon]="card" />
|
|
124
|
+
Card
|
|
125
|
+
</a>
|
|
126
|
+
</bf-nav-group>
|
|
127
|
+
</bf-sidebar>
|
|
128
|
+
`,
|
|
129
|
+
})
|
|
130
|
+
export class NavGroupCollapsedExampleComponent {
|
|
131
|
+
protected readonly layers = Layers;
|
|
132
|
+
protected readonly panelTop = PanelTop;
|
|
133
|
+
protected readonly list = LayoutList;
|
|
134
|
+
protected readonly card = SquareStack;
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Desktop
|
|
139
|
+
|
|
140
|
+
The group itself is static; its links carry the hover, pressed and focus styles described on the [nav item](https://bitforge-ui.web.app/components/nav-item) page.
|
|
141
|
+
|
|
142
|
+
## Theming
|
|
143
|
+
|
|
144
|
+
- Heading: `text-caption` in `text-muted`, inset 12 px to line up with the item labels.
|
|
145
|
+
- Items: 4 px apart.
|
|
146
|
+
|
|
147
|
+
## Accessibility
|
|
148
|
+
|
|
149
|
+
The host has `role="group"` and `aria-labelledby` pointing at its heading, so a screen reader announces the group's name when focus enters it ("Workspace, group"). The heading is text, not a heading element, so it does not add to the page outline. Collapsed, the heading is `sr-only` and keeps naming the group.
|
|
150
|
+
|
|
151
|
+
## Inputs
|
|
152
|
+
|
|
153
|
+
| Name | Type | Default | Description |
|
|
154
|
+
| ------- | -------- | -------- | ------------------------------------------------------------- |
|
|
155
|
+
| `label` | `string` | required | The group's heading and accessible name, such as "Workspace". |
|
|
156
|
+
|
|
157
|
+
## Slots
|
|
158
|
+
|
|
159
|
+
- `default`: The group's `a[bfNavItem]` links.
|
|
160
|
+
|
|
161
|
+
## Notes
|
|
162
|
+
|
|
163
|
+
### Do
|
|
164
|
+
|
|
165
|
+
- Give each group a short noun as its label.
|
|
166
|
+
- Keep the links of one area together in a group.
|
|
167
|
+
|
|
168
|
+
### Don't
|
|
169
|
+
|
|
170
|
+
- Don't nest groups; a sidebar has one level of groups.
|
|
171
|
+
- Don't put a group in a `bf-top-nav`; a top nav is a single row.
|
|
172
|
+
|
|
173
|
+
## Migration notes
|
|
174
|
+
|
|
175
|
+
### 0.3.1
|
|
176
|
+
|
|
177
|
+
New `bf-nav-group`: a titled group of sidebar links, `role="group"` named by its small heading,
|
|
178
|
+
which stays the group's name (hidden visually) while the sidebar is collapsed.
|