@bitforgehq/ui 0.3.0
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 +3665 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/ai/MANIFEST.index.json +383 -0
- package/ai/MANIFEST.json +7528 -0
- package/ai/REFERENCE.index.json +1461 -0
- package/ai/REFERENCE.json +7213 -0
- package/ai/UI_GUIDE.md +1159 -0
- package/ai/components/action-bar.md +209 -0
- package/ai/components/action-sheet.md +526 -0
- package/ai/components/alert-dialog.md +509 -0
- package/ai/components/alert.md +376 -0
- package/ai/components/avatar.md +146 -0
- package/ai/components/back-button.md +286 -0
- package/ai/components/badge.md +171 -0
- package/ai/components/button.md +431 -0
- package/ai/components/card.md +303 -0
- package/ai/components/checkbox.md +418 -0
- package/ai/components/chip-group.md +205 -0
- package/ai/components/chip.md +300 -0
- package/ai/components/content.md +474 -0
- package/ai/components/desktop-toast.md +420 -0
- package/ai/components/dialog.md +962 -0
- package/ai/components/empty-state.md +146 -0
- package/ai/components/field.md +319 -0
- package/ai/components/header.md +809 -0
- package/ai/components/icon-button.md +221 -0
- package/ai/components/icon.md +176 -0
- package/ai/components/input.md +455 -0
- package/ai/components/list-item.md +326 -0
- package/ai/components/list.md +178 -0
- package/ai/components/menu-button.md +377 -0
- package/ai/components/menu.md +661 -0
- package/ai/components/modal.md +613 -0
- package/ai/components/number-input.md +423 -0
- package/ai/components/page.md +299 -0
- package/ai/components/progress.md +215 -0
- package/ai/components/radio-group.md +432 -0
- package/ai/components/refresher.md +377 -0
- package/ai/components/segmented.md +517 -0
- package/ai/components/select.md +510 -0
- package/ai/components/sheet.md +845 -0
- package/ai/components/spinner.md +171 -0
- package/ai/components/stack-outlet.md +661 -0
- package/ai/components/tab-bar.md +427 -0
- package/ai/components/tab.md +295 -0
- package/ai/components/tabs.md +408 -0
- package/ai/components/textarea.md +371 -0
- package/ai/components/toast.md +522 -0
- package/ai/components/toggle.md +387 -0
- package/ai/reference/bf-action-directive.md +21 -0
- package/ai/reference/bf-action-sheet-action-role.md +27 -0
- package/ai/reference/bf-action-sheet-action.md +28 -0
- package/ai/reference/bf-action-sheet-options.md +28 -0
- package/ai/reference/bf-action-sheet-service.md +33 -0
- package/ai/reference/bf-alert-dialog-button-role.md +27 -0
- package/ai/reference/bf-alert-dialog-button.md +27 -0
- package/ai/reference/bf-alert-dialog-options.md +29 -0
- package/ai/reference/bf-alert-dialog-service.md +34 -0
- package/ai/reference/bf-alert-variant.md +28 -0
- package/ai/reference/bf-autocapitalize.md +30 -0
- package/ai/reference/bf-avatar-size.md +27 -0
- package/ai/reference/bf-avatar-variant.md +28 -0
- package/ai/reference/bf-back-at-root.md +28 -0
- package/ai/reference/bf-back-button-service.md +40 -0
- package/ai/reference/bf-badge-variant.md +29 -0
- package/ai/reference/bf-button-size.md +29 -0
- package/ai/reference/bf-button-type.md +28 -0
- package/ai/reference/bf-button-variant.md +29 -0
- package/ai/reference/bf-card-padding.md +26 -0
- package/ai/reference/bf-card-variant.md +28 -0
- package/ai/reference/bf-chip-group-mode.md +27 -0
- package/ai/reference/bf-chip-variant.md +26 -0
- package/ai/reference/bf-compare-with.md +24 -0
- package/ai/reference/bf-component-overlay-ref.md +33 -0
- package/ai/reference/bf-confirm-button-role.md +26 -0
- package/ai/reference/bf-confirm-button.md +27 -0
- package/ai/reference/bf-confirm-options.md +31 -0
- package/ai/reference/bf-confirm-size.md +26 -0
- package/ai/reference/bf-content-scroll-event.md +21 -0
- package/ai/reference/bf-defaults-options.md +32 -0
- package/ai/reference/bf-defaults.md +28 -0
- package/ai/reference/bf-desktop-toast-config.md +28 -0
- package/ai/reference/bf-desktop-toast-harness.md +33 -0
- package/ai/reference/bf-desktop-toast-options.md +28 -0
- package/ai/reference/bf-desktop-toast-position.md +28 -0
- package/ai/reference/bf-desktop-toast-service.md +42 -0
- package/ai/reference/bf-dialog-config.md +44 -0
- package/ai/reference/bf-dialog-harness.md +52 -0
- package/ai/reference/bf-dialog-ref.md +44 -0
- package/ai/reference/bf-dialog-role.md +26 -0
- package/ai/reference/bf-dialog-service.md +55 -0
- package/ai/reference/bf-dialog-size.md +31 -0
- package/ai/reference/bf-dialog-template-context.md +32 -0
- package/ai/reference/bf-empty-state-heading-level.md +26 -0
- package/ai/reference/bf-enter-key-hint.md +31 -0
- package/ai/reference/bf-field-control-class.md +32 -0
- package/ai/reference/bf-field-control-state.md +28 -0
- package/ai/reference/bf-field-size.md +29 -0
- package/ai/reference/bf-haptics-impact-style.md +27 -0
- package/ai/reference/bf-haptics-notification-type.md +27 -0
- package/ai/reference/bf-haptics-service.md +41 -0
- package/ai/reference/bf-harness-filters.md +29 -0
- package/ai/reference/bf-horizontal-scroll-directive.md +129 -0
- package/ai/reference/bf-icon-button-variant.md +28 -0
- package/ai/reference/bf-icon-size.md +27 -0
- package/ai/reference/bf-input-autocapitalize.md +36 -0
- package/ai/reference/bf-input-enter-key-hint.md +38 -0
- package/ai/reference/bf-input-input-mode.md +38 -0
- package/ai/reference/bf-input-mode.md +32 -0
- package/ai/reference/bf-input-type.md +35 -0
- package/ai/reference/bf-jsdom-shim-options.md +22 -0
- package/ai/reference/bf-keyboard-assist-behavior.md +26 -0
- package/ai/reference/bf-keyboard-assist-directive.md +185 -0
- package/ai/reference/bf-keyboard-phase.md +30 -0
- package/ai/reference/bf-keyboard-service.md +50 -0
- package/ai/reference/bf-labels-overrides.md +26 -0
- package/ai/reference/bf-labels.md +44 -0
- package/ai/reference/bf-menu-button-harness.md +40 -0
- package/ai/reference/bf-menu-button-variant.md +27 -0
- package/ai/reference/bf-menu-divider-directive.md +13 -0
- package/ai/reference/bf-menu-group-directive.md +14 -0
- package/ai/reference/bf-menu-harness.md +55 -0
- package/ai/reference/bf-menu-item-checkbox-directive.md +31 -0
- package/ai/reference/bf-menu-item-directive.md +31 -0
- package/ai/reference/bf-menu-item-harness.md +52 -0
- package/ai/reference/bf-menu-item-radio-directive.md +29 -0
- package/ai/reference/bf-menu-placement.md +29 -0
- package/ai/reference/bf-menu-trigger-directive.md +46 -0
- package/ai/reference/bf-modal-options.md +30 -0
- package/ai/reference/bf-modal-presentation.md +26 -0
- package/ai/reference/bf-modal-service.md +38 -0
- package/ai/reference/bf-nav-controller.md +59 -0
- package/ai/reference/bf-nav-direction.md +23 -0
- package/ai/reference/bf-nav-target.md +21 -0
- package/ai/reference/bf-number-input-mode.md +28 -0
- package/ai/reference/bf-number-input-step.md +25 -0
- package/ai/reference/bf-overlay-auto-focus.md +29 -0
- package/ai/reference/bf-overlay-controller.md +22 -0
- package/ai/reference/bf-overlay-host.md +36 -0
- package/ai/reference/bf-overlay-options.md +53 -0
- package/ai/reference/bf-overlay-position.md +30 -0
- package/ai/reference/bf-overlay-ref.md +53 -0
- package/ai/reference/bf-overlay-role.md +27 -0
- package/ai/reference/bf-overlay-service.md +53 -0
- package/ai/reference/bf-overlay-stack-options.md +24 -0
- package/ai/reference/bf-overlay-stack.md +81 -0
- package/ai/reference/bf-overlay-template-context.md +24 -0
- package/ai/reference/bf-overscroll-stretch-directive.md +163 -0
- package/ai/reference/bf-page-context.md +24 -0
- package/ai/reference/bf-platform-service.md +53 -0
- package/ai/reference/bf-platform.md +28 -0
- package/ai/reference/bf-progress-size.md +26 -0
- package/ai/reference/bf-progress-tone.md +28 -0
- package/ai/reference/bf-radio-group-harness.md +49 -0
- package/ai/reference/bf-radio-group-orientation.md +26 -0
- package/ai/reference/bf-radio-option.md +30 -0
- package/ai/reference/bf-refresher-refresh-event.md +22 -0
- package/ai/reference/bf-segment-option.md +29 -0
- package/ai/reference/bf-segmented-harness.md +48 -0
- package/ai/reference/bf-segmented-size.md +29 -0
- package/ai/reference/bf-select-harness.md +70 -0
- package/ai/reference/bf-select-option.md +29 -0
- package/ai/reference/bf-sheet-auto-focus.md +29 -0
- package/ai/reference/bf-sheet-options.md +32 -0
- package/ai/reference/bf-sheet-ref.md +45 -0
- package/ai/reference/bf-sheet-service.md +34 -0
- package/ai/reference/bf-shell-config-mode.md +28 -0
- package/ai/reference/bf-shell-config.md +38 -0
- package/ai/reference/bf-shell-dynamic-type-limit.md +21 -0
- package/ai/reference/bf-shell-dynamic-type.md +25 -0
- package/ai/reference/bf-shell-mode.md +28 -0
- package/ai/reference/bf-shell-testing-options.md +26 -0
- package/ai/reference/bf-size.md +45 -0
- package/ai/reference/bf-spinner-size.md +27 -0
- package/ai/reference/bf-stack-change.md +22 -0
- package/ai/reference/bf-stack-reuse-strategy.md +32 -0
- package/ai/reference/bf-stack-transition-event.md +26 -0
- package/ai/reference/bf-stack-transition.md +26 -0
- package/ai/reference/bf-stacked-overlay.md +23 -0
- package/ai/reference/bf-system-bars-style.md +38 -0
- package/ai/reference/bf-tab-bar-controller.md +51 -0
- package/ai/reference/bf-tab-bar-host.md +52 -0
- package/ai/reference/bf-tab-context.md +32 -0
- package/ai/reference/bf-tab-links.md +37 -0
- package/ai/reference/bf-testing-options.md +27 -0
- package/ai/reference/bf-text-scale-service.md +35 -0
- package/ai/reference/bf-theme.md +27 -0
- package/ai/reference/bf-toast-action.md +25 -0
- package/ai/reference/bf-toast-controller.md +17 -0
- package/ai/reference/bf-toast-dismiss-reason.md +29 -0
- package/ai/reference/bf-toast-options.md +29 -0
- package/ai/reference/bf-toast-position.md +26 -0
- package/ai/reference/bf-toast-ref.md +41 -0
- package/ai/reference/bf-toast-service.md +39 -0
- package/ai/reference/bf-toggle-label-position.md +26 -0
- package/ai/reference/bf-transition-kind.md +28 -0
- package/ai/reference/bf-validation-message.md +31 -0
- package/ai/reference/bf-validation-messages.md +33 -0
- package/ai/reference/bf-view-event.md +28 -0
- package/ai/reference/bf-view-lifecycle.md +41 -0
- package/ai/reference/bf-view-state.md +28 -0
- package/ai/reference/bf_default_labels.md +21 -0
- package/ai/reference/bf_default_values.md +21 -0
- package/ai/reference/bf_defaults.md +21 -0
- package/ai/reference/bf_desktop_toast_defaults.md +21 -0
- package/ai/reference/bf_dialog_data.md +17 -0
- package/ai/reference/bf_labels.md +21 -0
- package/ai/reference/bf_overlay_data.md +19 -0
- package/ai/reference/bf_overlay_host.md +21 -0
- package/ai/reference/bf_sheet_default_snap_points.md +17 -0
- package/ai/reference/bf_shell_config.md +21 -0
- package/ai/reference/bf_shell_defaults.md +21 -0
- package/ai/reference/bf_tab_bar.md +21 -0
- package/ai/reference/bf_tab_bar_controller.md +21 -0
- package/ai/reference/bf_tab_context.md +21 -0
- package/ai/reference/bf_tab_links.md +21 -0
- package/ai/reference/bf_theme_storage.md +19 -0
- package/ai/reference/create-transition.md +36 -0
- package/ai/reference/duration_fast.md +17 -0
- package/ai/reference/duration_md.md +17 -0
- package/ai/reference/duration_overlay.md +17 -0
- package/ai/reference/duration_push.md +17 -0
- package/ai/reference/fade-down.md +26 -0
- package/ai/reference/fade-in.md +25 -0
- package/ai/reference/fade-out.md +25 -0
- package/ai/reference/fade-up.md +26 -0
- package/ai/reference/inject-shell-mode.md +23 -0
- package/ai/reference/install-bf-jsdom-shims.md +31 -0
- package/ai/reference/ios_duration.md +17 -0
- package/ai/reference/ios_easing.md +17 -0
- package/ai/reference/is-text-entry.md +25 -0
- package/ai/reference/max_complete_ms.md +17 -0
- package/ai/reference/md-transition.md +31 -0
- package/ai/reference/md_duration.md +17 -0
- package/ai/reference/md_easing.md +17 -0
- package/ai/reference/overlay_duration.md +21 -0
- package/ai/reference/overlay_easing.md +19 -0
- package/ai/reference/overlay_reduced_duration.md +17 -0
- package/ai/reference/pop-frames-at.md +25 -0
- package/ai/reference/prefers-reduced-motion.md +25 -0
- package/ai/reference/provide-bf-defaults.md +33 -0
- package/ai/reference/provide-bf-desktop-toast.md +28 -0
- package/ai/reference/provide-bf-labels.md +38 -0
- package/ai/reference/provide-bf-shell-testing.md +34 -0
- package/ai/reference/provide-bf-shell.md +41 -0
- package/ai/reference/provide-bf-testing.md +33 -0
- package/ai/reference/reduced-transition.md +28 -0
- package/ai/reference/reduced_duration.md +17 -0
- package/ai/reference/resolve-shell-mode.md +33 -0
- package/ai/reference/scale-in.md +25 -0
- package/ai/reference/settle-duration.md +25 -0
- package/ai/reference/settle-pop.md +33 -0
- package/ai/reference/settle_easing.md +17 -0
- package/ai/reference/should-complete-pop.md +26 -0
- package/ai/reference/slide-down.md +26 -0
- package/ai/reference/slide-up.md +26 -0
- package/ai/reference/with-bf-shell-routing.md +19 -0
- package/bin/bf-ui.js +81 -0
- package/fesm2022/bitforgehq-ui-icons.mjs +230 -0
- package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-mobile.mjs +5530 -0
- package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-number-input.mjs +680 -0
- package/fesm2022/bitforgehq-ui-number-input.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-overlay.mjs +2675 -0
- package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-testing.mjs +402 -0
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui.mjs +7765 -0
- package/fesm2022/bitforgehq-ui.mjs.map +1 -0
- package/llms-full.txt +25394 -0
- package/llms.txt +329 -0
- package/package.json +94 -0
- package/theme.css +253 -0
- package/types/bitforgehq-ui-icons.d.ts +50 -0
- package/types/bitforgehq-ui-mobile.d.ts +2914 -0
- package/types/bitforgehq-ui-number-input.d.ts +282 -0
- package/types/bitforgehq-ui-overlay.d.ts +1514 -0
- package/types/bitforgehq-ui-testing.d.ts +495 -0
- package/types/bitforgehq-ui.d.ts +3499 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
# bf-checkbox
|
|
2
|
+
|
|
3
|
+
Rounded checkbox with an optional label that binds to Signal Forms
|
|
4
|
+
|
|
5
|
+
`BfCheckboxComponent` from `@bitforgehq/ui` · Forms · since 0.1.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, computed, signal } from '@angular/core';
|
|
11
|
+
import { BfCheckboxComponent } from '@bitforgehq/ui';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The bf-checkbox overview.
|
|
15
|
+
*
|
|
16
|
+
* Live demo of `bf-checkbox`: unchecked, checked and indeterminate boxes, all bound to signals; then the sm, md and lg sizes in a row.
|
|
17
|
+
*/
|
|
18
|
+
@Component({
|
|
19
|
+
selector: 'app-example-checkbox-overview',
|
|
20
|
+
imports: [BfCheckboxComponent],
|
|
21
|
+
template: `
|
|
22
|
+
<div class="flex flex-col gap-3">
|
|
23
|
+
<bf-checkbox [(checked)]="subscribe">Subscribe to updates</bf-checkbox>
|
|
24
|
+
<bf-checkbox [(checked)]="agree">I agree to the terms</bf-checkbox>
|
|
25
|
+
<bf-checkbox [(checked)]="selectAll" [(indeterminate)]="mixed">
|
|
26
|
+
Select all (starts indeterminate)
|
|
27
|
+
</bf-checkbox>
|
|
28
|
+
<bf-checkbox [disabled]="true">Disabled</bf-checkbox>
|
|
29
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
30
|
+
<div data-sizes class="flex flex-wrap items-center gap-3">
|
|
31
|
+
<bf-checkbox size="sm" [checked]="true">sm · 32 px</bf-checkbox>
|
|
32
|
+
<bf-checkbox [checked]="true">md · 40 px</bf-checkbox>
|
|
33
|
+
<bf-checkbox size="lg" [checked]="true">lg · 48 px</bf-checkbox>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
`,
|
|
37
|
+
})
|
|
38
|
+
export class CheckboxOverviewExampleComponent {
|
|
39
|
+
readonly subscribe = signal(false);
|
|
40
|
+
readonly agree = signal(true);
|
|
41
|
+
readonly selectAll = signal(false);
|
|
42
|
+
readonly mixed = signal(true);
|
|
43
|
+
|
|
44
|
+
readonly status = computed(() => {
|
|
45
|
+
const checked = [
|
|
46
|
+
this.subscribe() ? 'Subscribe' : '',
|
|
47
|
+
this.agree() ? 'Agree' : '',
|
|
48
|
+
this.mixed() ? 'Select all (mixed)' : this.selectAll() ? 'Select all' : '',
|
|
49
|
+
].filter(Boolean);
|
|
50
|
+
return checked.length > 0 ? `Checked: ${checked.join(', ')}.` : 'Nothing checked.';
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Overview
|
|
56
|
+
|
|
57
|
+
Checkbox with a 22 px rounded box and an optional label, implementing the Signal Forms
|
|
58
|
+
checkbox contract so `[formField]` binds `checked`.
|
|
59
|
+
|
|
60
|
+
A checkbox for yes/no choices and multi-select lists. The native input stays in the DOM (visually hidden) so keyboard, screen readers and forms work as expected; the visible 22 px box is drawn with tokens.
|
|
61
|
+
|
|
62
|
+
## When to use
|
|
63
|
+
|
|
64
|
+
Use `bf-checkbox` for a yes/no choice that takes effect when the form is submitted, and for picking several items of a list. Use [bf-toggle](https://bitforge-ui.web.app/components/toggle) instead for a setting that applies at once, and [bf-radio-group](https://bitforge-ui.web.app/components/radio-group) when exactly one option may be chosen.
|
|
65
|
+
|
|
66
|
+
## Basic usage
|
|
67
|
+
|
|
68
|
+
Put the label in the default slot or in `label`.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { Component, signal } from '@angular/core';
|
|
72
|
+
import { BfCheckboxComponent } from '@bitforgehq/ui';
|
|
73
|
+
|
|
74
|
+
/** A checkbox with its label in the default slot. */
|
|
75
|
+
@Component({
|
|
76
|
+
selector: 'app-example-checkbox-basic',
|
|
77
|
+
imports: [BfCheckboxComponent],
|
|
78
|
+
template: `
|
|
79
|
+
<div class="flex flex-col gap-2">
|
|
80
|
+
<bf-checkbox [(checked)]="agree">I agree to the terms</bf-checkbox>
|
|
81
|
+
<p class="text-caption text-text-muted">{{ agree() ? 'Agreed.' : 'Not agreed yet.' }}</p>
|
|
82
|
+
</div>
|
|
83
|
+
`,
|
|
84
|
+
})
|
|
85
|
+
export class CheckboxBasicExampleComponent {
|
|
86
|
+
protected readonly agree = signal(false);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## With Signal Forms
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
import { Component, signal } from '@angular/core';
|
|
94
|
+
import { FormField, form } from '@angular/forms/signals';
|
|
95
|
+
import { BfCheckboxComponent } from '@bitforgehq/ui';
|
|
96
|
+
|
|
97
|
+
/** A checkbox bound to a Signal Forms field. */
|
|
98
|
+
@Component({
|
|
99
|
+
selector: 'app-example-checkbox-signal-forms',
|
|
100
|
+
imports: [FormField, BfCheckboxComponent],
|
|
101
|
+
template: `
|
|
102
|
+
<div class="flex flex-col gap-2">
|
|
103
|
+
<bf-checkbox label="Send me the weekly summary" [formField]="prefs.weekly" />
|
|
104
|
+
<p class="text-caption text-text-muted">weekly: {{ prefs.weekly().value() }}</p>
|
|
105
|
+
</div>
|
|
106
|
+
`,
|
|
107
|
+
})
|
|
108
|
+
export class CheckboxSignalFormsExampleComponent {
|
|
109
|
+
private readonly model = signal({ weekly: true });
|
|
110
|
+
protected readonly prefs = form(this.model);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Sizes
|
|
115
|
+
|
|
116
|
+
`sm` (32 px row, 18 px box, 14 px label), `md` (default, 40 px row, 22 px box), `lg` (48 px row, 24 px box); the check mark scales with the box. Unset, `size` follows `provideBfDefaults()`'s `controlSize`, `md` by default (`provideBfDefaults({ size: 'lg' })` enlarges every control of a Capacitor app; `{ size: 'sm' }` in a component's `providers` makes a dense sub-tree). Under a fine pointer the row is the hit area, at least 24×24 px (WCAG 2.5.8) even for a label-less box with only `ariaLabel`; under a coarse pointer (touch) `sm` and `md` rows grow to 44 px.
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
import { Component, signal } from '@angular/core';
|
|
120
|
+
import { BfCheckboxComponent } from '@bitforgehq/ui';
|
|
121
|
+
|
|
122
|
+
/** The sm, md and lg checkbox sizes. */
|
|
123
|
+
@Component({
|
|
124
|
+
selector: 'app-example-checkbox-sizes',
|
|
125
|
+
imports: [BfCheckboxComponent],
|
|
126
|
+
template: `
|
|
127
|
+
<div class="flex flex-col gap-2">
|
|
128
|
+
<bf-checkbox size="sm" [(checked)]="compact">Compact rows</bf-checkbox>
|
|
129
|
+
<bf-checkbox [(checked)]="agree">I agree to the terms</bf-checkbox>
|
|
130
|
+
<bf-checkbox size="lg" [(checked)]="notify">Notify me</bf-checkbox>
|
|
131
|
+
</div>
|
|
132
|
+
`,
|
|
133
|
+
})
|
|
134
|
+
export class CheckboxSizesExampleComponent {
|
|
135
|
+
protected readonly compact = signal(true);
|
|
136
|
+
protected readonly agree = signal(false);
|
|
137
|
+
protected readonly notify = signal(true);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Indeterminate
|
|
142
|
+
|
|
143
|
+
Use for a "select all" control while only some items are selected. `indeterminate` is a model: a click clears it (as the native checkbox does) and writes `false` back through `[(indeterminate)]`, so the dash, the check mark and `aria-checked` always agree.
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { Component, computed, signal } from '@angular/core';
|
|
147
|
+
import { BfCheckboxComponent } from '@bitforgehq/ui';
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* A select-all checkbox over three tasks.
|
|
151
|
+
*
|
|
152
|
+
* It shows the dash while only some tasks are checked; a click selects or clears them all.
|
|
153
|
+
*/
|
|
154
|
+
@Component({
|
|
155
|
+
selector: 'app-example-checkbox-indeterminate',
|
|
156
|
+
imports: [BfCheckboxComponent],
|
|
157
|
+
template: `
|
|
158
|
+
<div class="flex flex-col gap-2">
|
|
159
|
+
<bf-checkbox
|
|
160
|
+
label="All tasks"
|
|
161
|
+
[checked]="allSelected()"
|
|
162
|
+
[(indeterminate)]="someSelected"
|
|
163
|
+
(changed)="toggleAll($event)"
|
|
164
|
+
/>
|
|
165
|
+
<div class="flex flex-col gap-2 ps-8">
|
|
166
|
+
@for (task of tasks(); track task.name; let i = $index) {
|
|
167
|
+
<bf-checkbox [label]="task.name" [checked]="task.done" (changed)="toggle(i, $event)" />
|
|
168
|
+
}
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
`,
|
|
172
|
+
})
|
|
173
|
+
export class CheckboxIndeterminateExampleComponent {
|
|
174
|
+
protected readonly tasks = signal([
|
|
175
|
+
{ name: 'Brief', done: true },
|
|
176
|
+
{ name: 'Moodboard', done: false },
|
|
177
|
+
{ name: 'Deck', done: false },
|
|
178
|
+
]);
|
|
179
|
+
protected readonly allSelected = computed(() => this.tasks().every((t) => t.done));
|
|
180
|
+
protected readonly someSelected = signal(true);
|
|
181
|
+
|
|
182
|
+
protected toggleAll(checked: boolean): void {
|
|
183
|
+
this.tasks.update((tasks) => tasks.map((t) => ({ ...t, done: checked })));
|
|
184
|
+
this.someSelected.set(false);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
protected toggle(index: number, checked: boolean): void {
|
|
188
|
+
this.tasks.update((tasks) => tasks.map((t, i) => (i === index ? { ...t, done: checked } : t)));
|
|
189
|
+
const done = this.tasks().filter((t) => t.done).length;
|
|
190
|
+
this.someSelected.set(done > 0 && done < this.tasks().length);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## In a selectable row
|
|
196
|
+
|
|
197
|
+
Skip the visible `label` when the row itself already shows the name, and give the box an `ariaLabel` instead so assistive tech still gets a name, or `ariaLabelledby` with the id(s) of the element(s) that already show it (a table's column header and row title).
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { Component, signal } from '@angular/core';
|
|
201
|
+
import { BfCheckboxComponent } from '@bitforgehq/ui';
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* A checkbox in a selectable row.
|
|
205
|
+
*
|
|
206
|
+
* The row shows the name, so the box takes an `ariaLabel` instead of a visible label.
|
|
207
|
+
*/
|
|
208
|
+
@Component({
|
|
209
|
+
selector: 'app-example-checkbox-row',
|
|
210
|
+
imports: [BfCheckboxComponent],
|
|
211
|
+
template: `
|
|
212
|
+
<div class="bg-surface rounded-card flex items-center gap-3 p-3">
|
|
213
|
+
<bf-checkbox ariaLabel="Select Atlas rebrand" [(checked)]="selected" />
|
|
214
|
+
<span class="text-body">Atlas rebrand</span>
|
|
215
|
+
</div>
|
|
216
|
+
`,
|
|
217
|
+
})
|
|
218
|
+
export class CheckboxRowExampleComponent {
|
|
219
|
+
protected readonly selected = signal(false);
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Read-only
|
|
224
|
+
|
|
225
|
+
`readonly` shows the state but blocks changes: clicks and Space do nothing, the box stays in the
|
|
226
|
+
tab order, and the box rests on the muted surface without the hover tint, like a read-only radio.
|
|
227
|
+
A checked box keeps a `primary` border and mark instead of the filled box. Signal Forms binds it
|
|
228
|
+
from `readonly()` rules.
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { Component } from '@angular/core';
|
|
232
|
+
import { BfCheckboxComponent } from '@bitforgehq/ui';
|
|
233
|
+
|
|
234
|
+
/** A read-only checkbox: it shows its state and blocks changes. */
|
|
235
|
+
@Component({
|
|
236
|
+
selector: 'app-example-checkbox-readonly',
|
|
237
|
+
imports: [BfCheckboxComponent],
|
|
238
|
+
template: `<bf-checkbox label="Terms accepted" [readonly]="true" [checked]="accepted" />`,
|
|
239
|
+
})
|
|
240
|
+
export class CheckboxReadonlyExampleComponent {
|
|
241
|
+
protected readonly accepted = true;
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Validation
|
|
246
|
+
|
|
247
|
+
Once the control is touched it shows the first error as text: the error's own `message`, else the default for its kind from `provideBfLabels()`'s `validation` labels (`required` "Fill in this field", `email`, `pattern`, `parse`, `min(n)`, `max(n)`, `minLength(n)` "Use at least n characters", `maxLength(n)`, `minDate(d)`, `maxDate(d)`, and `fallback` "Check this value" for any other kind), so it never turns red without text. Reactive Forms `Validators.*` errors carry no message and get the same defaults (`minlength`/`maxlength` read their limit from the error's `context`). An explicit `errorMessage` wins over both and shows at once, e.g. for a server error.
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
import { Component, signal } from '@angular/core';
|
|
251
|
+
import { FormField, FormRoot, form, required } from '@angular/forms/signals';
|
|
252
|
+
import { BfButtonComponent, BfCheckboxComponent } from '@bitforgehq/ui';
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* A required checkbox with the default message.
|
|
256
|
+
*
|
|
257
|
+
* `required(p.terms)` has no message, so a submit shows "Fill in this field".
|
|
258
|
+
*/
|
|
259
|
+
@Component({
|
|
260
|
+
selector: 'app-example-checkbox-validation',
|
|
261
|
+
imports: [FormField, FormRoot, BfButtonComponent, BfCheckboxComponent],
|
|
262
|
+
template: `
|
|
263
|
+
<form [formRoot]="signup" class="flex flex-col items-start gap-3">
|
|
264
|
+
<bf-checkbox label="I accept the terms" [formField]="signup.terms" />
|
|
265
|
+
<bf-button type="submit">Sign up</bf-button>
|
|
266
|
+
</form>
|
|
267
|
+
`,
|
|
268
|
+
})
|
|
269
|
+
export class CheckboxValidationExampleComponent {
|
|
270
|
+
private readonly model = signal({ terms: false });
|
|
271
|
+
protected readonly signup = form(
|
|
272
|
+
this.model,
|
|
273
|
+
(p) => {
|
|
274
|
+
required(p.terms);
|
|
275
|
+
},
|
|
276
|
+
{ submission: { action: () => Promise.resolve(undefined) } },
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Desktop
|
|
282
|
+
|
|
283
|
+
Hovering the row (box and label) darkens the box border, or the checked fill, with the pointer cursor; a read-only box has no hover and the default cursor. Hover styles apply only on devices that can hover, so a tap never leaves one stuck. The ring shows around the box on keyboard focus only. Under a fine pointer the row is the hit area, at least 24 × 24 px; `sm` (32 px rows) suits dense desktop forms and tables; set it with `provideBfDefaults({ size: 'sm' })` ([density guide](https://bitforge-ui.web.app/density)).
|
|
284
|
+
|
|
285
|
+
## Theming
|
|
286
|
+
|
|
287
|
+
- Unchecked box: `border-strong` border, `surface` background; checked (or indeterminate): `primary` background with an `on-primary` mark.
|
|
288
|
+
- Error, unchecked: border switches to `danger-border`. Error text: `danger`.
|
|
289
|
+
- Hover over the row (pointer cursor): unchecked border `text-muted` (`danger` on error); checked box `primary-hover`.
|
|
290
|
+
- Pressed (the row held down): the box border turns `text`; not while disabled or read-only.
|
|
291
|
+
- Disabled: border `surface-sunken`, background `surface-muted`; label text `text-disabled`.
|
|
292
|
+
- Read-only: `surface-muted` background, no hover, default cursor; unchecked keeps the `border-strong` border, checked (or indeterminate) a `primary` border and mark (≥ 4.8:1 on `surface-muted` in both themes).
|
|
293
|
+
- Focus ring (on the visually hidden native input): `ring`.
|
|
294
|
+
|
|
295
|
+
The box sits 10 px from the label and the error text 6 px under the row, indented to the label (logical padding, so it follows `dir="rtl"`).
|
|
296
|
+
|
|
297
|
+
## Accessibility
|
|
298
|
+
|
|
299
|
+
Native `<input type="checkbox">`, visually hidden (`sr-only`) and wrapped in a `<label>` so the whole row is tappable. Keyboard: native checkbox behaviour — Space toggles it while focused. `indeterminate` sets the native DOM property, which assistive tech reads as "mixed" without any extra ARIA; a user change clears it, so the visual and the reported state agree. The label row is only as wide as its content, so a click on empty space beside it does nothing. `aria-label` carries `ariaLabel` and `aria-labelledby` carries `ariaLabelledby` when there is no visible `label`; `aria-invalid`, `aria-describedby` and `aria-errormessage` point at the error text once the field is touched and invalid (an explicit `errorMessage` shows immediately). Read-only: `aria-readonly="true"`, still focusable; the click (which Space also triggers) is cancelled, so the state never changes. `focus()` focuses the native checkbox; Signal Forms calls it from `focusBoundControl()`.
|
|
300
|
+
|
|
301
|
+
### Keyboard
|
|
302
|
+
|
|
303
|
+
| Key | Action |
|
|
304
|
+
| ------------------------------------------------ | ----------------------------------------------------------------------------- |
|
|
305
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves focus to and from the checkbox; a `readonly` one stays in the tab order |
|
|
306
|
+
| <kbd>Space</kbd> | Toggles it and clears `indeterminate`; nothing while `readonly` |
|
|
307
|
+
|
|
308
|
+
## Inputs
|
|
309
|
+
|
|
310
|
+
| Name | Type | Default | Description |
|
|
311
|
+
| ---------------- | ----------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
312
|
+
| `label` | `string` | `''` | Label text; the default slot works too. |
|
|
313
|
+
| `ariaLabel` | `string` | `''` | Accessible name when there is no visible `label`, e.g. a row-selection box. |
|
|
314
|
+
| `ariaLabelledby` | `string` | `''` | Id(s) of the element(s) that name the control when no visible label is rendered (e.g. a table column header). |
|
|
315
|
+
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Height step of the row: sm 32, md 40, lg 48 px (44 px under a coarse pointer for sm and md); unset follows `provideBfDefaults()` (`controlSize`, `md` by default). |
|
|
316
|
+
| `disabled` | `boolean` | `false` | Disables the control. Set by `[formField]`; do not bind it yourself. |
|
|
317
|
+
| `readonly` | `boolean` | `false` | Shows the state but blocks changes; the box stays focusable and exposes `aria-readonly`. Set by `[formField]`; do not bind it yourself. |
|
|
318
|
+
| `required` | `boolean` | `false` | Marks the control required. Set by `[formField]`; do not bind it yourself. |
|
|
319
|
+
| `invalid` | `boolean` | `false` | Field validity from Signal Forms. Set by `[formField]`; do not bind it yourself. |
|
|
320
|
+
| `touched` | `boolean` | `false` | Whether the user has left the field. Set by `[formField]`; do not bind it yourself. |
|
|
321
|
+
| `errors` | `readonly ValidationError.WithOptionalFieldTree[]` | `[]` | Validation errors from Signal Forms. Set by `[formField]`; do not bind it yourself. |
|
|
322
|
+
| `errorMessage` | `string` | `''` | Explicit error text; takes precedence over Signal Forms errors. |
|
|
323
|
+
| `name` | `string` | `''` | Native name attribute. Set by `[formField]`; do not bind it yourself. |
|
|
324
|
+
| `id` | `string` | `generated` | Control id; generated when not set. |
|
|
325
|
+
| `checked` | `boolean` | `false` | Whether the box is checked; two-way bindable and bound by `[formField]`. Two-way: `[(checked)]`. |
|
|
326
|
+
| `indeterminate` | `boolean` | `false` | Shows the mixed state (e.g. "select all" with some rows selected); two-way bindable. A user change clears it, like the native checkbox, so the box and `aria-checked` agree. Two-way: `[(indeterminate)]`. |
|
|
327
|
+
|
|
328
|
+
## Outputs
|
|
329
|
+
|
|
330
|
+
| Name | Payload | Description |
|
|
331
|
+
| --------- | --------- | ------------------------------------------------------------------------------------------------------- |
|
|
332
|
+
| `changed` | `boolean` | Emitted with the new checked state after a user change. |
|
|
333
|
+
| `touch` | `void` | Emitted on blur so Signal Forms marks the field touched. Set by `[formField]`; do not bind it yourself. |
|
|
334
|
+
|
|
335
|
+
## Slots
|
|
336
|
+
|
|
337
|
+
- `default`: Rich label content next to the box, before the `label` text.
|
|
338
|
+
|
|
339
|
+
## Methods
|
|
340
|
+
|
|
341
|
+
### `focus(options?: FocusOptions): void`
|
|
342
|
+
|
|
343
|
+
Focuses the native checkbox; Signal Forms calls it from `focusBoundControl()`.
|
|
344
|
+
|
|
345
|
+
| Parameter | Type | Description |
|
|
346
|
+
| ---------- | -------------- | --------------------------------------------------------------------- |
|
|
347
|
+
| `options?` | `FocusOptions` | Passed on to the native `focus()`, such as `{ preventScroll: true }`. |
|
|
348
|
+
|
|
349
|
+
## Notes
|
|
350
|
+
|
|
351
|
+
### Do
|
|
352
|
+
|
|
353
|
+
- Use a checkbox when the choice takes effect on submit; use `bf-toggle` when it applies immediately.
|
|
354
|
+
- Keep labels positive ("Send notifications", not "Don't send").
|
|
355
|
+
|
|
356
|
+
### Don't
|
|
357
|
+
|
|
358
|
+
- Don't use a lone checkbox for a required agreement without an error message.
|
|
359
|
+
|
|
360
|
+
## Migration notes
|
|
361
|
+
|
|
362
|
+
### 0.3.0
|
|
363
|
+
|
|
364
|
+
New `ariaLabelledby` input: the id(s) of the element(s) that name the box when no visible `label`
|
|
365
|
+
is shown (a table's column header and row title), set as the native checkbox's `aria-labelledby`.
|
|
366
|
+
Additive.
|
|
367
|
+
|
|
368
|
+
**Behaviour:** new `size` (`BfSize`; unset follows `provideBfDefaults()`'s `controlSize`): rows
|
|
369
|
+
are `sm` 32, `md` 40 (default), `lg` 48 px under a fine pointer, and at least 44 px under a coarse
|
|
370
|
+
pointer (was 44 px everywhere); the box is 18 / 22 / 24 px with a matching check mark, and the error
|
|
371
|
+
text's indent is logical (`ps-*`). The row is at least 24 px wide (`min-w-6`), so a label-less box
|
|
372
|
+
with only `ariaLabel` still meets the 24×24 px target size of WCAG 2.5.8 under a fine pointer.
|
|
373
|
+
|
|
374
|
+
**Behaviour:** an error without a message shows its kind's default text (was a red border only):
|
|
375
|
+
once touched, the field shows the first error's own `message`, else `provideBfLabels()`'s
|
|
376
|
+
`validation` text for its kind (Signal Forms and Reactive interop kinds), else
|
|
377
|
+
`validation.fallback`. `errorMessage` still wins. See Migration 1.
|
|
378
|
+
|
|
379
|
+
JSDoc of `checkbox.component.ts` for the API reference: `@slot` docs for `default`; `@param` docs;
|
|
380
|
+
template handlers and framework hooks marked `@internal`, so the reference leaves them out. No
|
|
381
|
+
behaviour change.
|
|
382
|
+
|
|
383
|
+
**Behaviour:** a pressed state (F-045): the box border turns `text` while the row is held down. None while disabled or read-only; under forced colours the system colours win.
|
|
384
|
+
|
|
385
|
+
**Behaviour:** sets `aria-errormessage` to its error paragraph (`<id>-error`) while
|
|
386
|
+
`aria-invalid="true"`, and an error that appears while a pointer button is held down (a press
|
|
387
|
+
elsewhere that blurred it) shows once the press ends, so the layout never moves under the pointer
|
|
388
|
+
(F-020).
|
|
389
|
+
|
|
390
|
+
### 0.2.0
|
|
391
|
+
|
|
392
|
+
The error text sits 6 px under the row (`gap-1.5`, was 4 px), the label-to-message spacing of the
|
|
393
|
+
other form widgets.
|
|
394
|
+
|
|
395
|
+
Fixed: hovering an enabled row tints the box (unchecked border `text-muted`, `danger` on error;
|
|
396
|
+
checked `primary-hover`); the label was already `cursor-pointer`.
|
|
397
|
+
|
|
398
|
+
Added: `readonly` input. Behaviour change: the control ignored Signal Forms `readonly()` rules and
|
|
399
|
+
could be edited; it now declares `readonly` and blocks changes. While read-only the click (which
|
|
400
|
+
Space also triggers) is cancelled, `aria-readonly="true"` is set, the box stays focusable, and the
|
|
401
|
+
box rests on `surface-muted` without hover (default cursor), like a read-only radio; a checked box
|
|
402
|
+
keeps a `primary` border and mark instead of the fill. `focus(options?)` focuses the
|
|
403
|
+
native control, so Signal Forms' `focusBoundControl()` and "focus the first invalid field" on submit
|
|
404
|
+
now work (they were no-ops).
|
|
405
|
+
|
|
406
|
+
Behaviour change (breaking for a bare attribute): `indeterminate` is now a `model(false)` (it was an
|
|
407
|
+
`input` with `booleanAttribute`). A user change clears it and writes `false` back, so after a click
|
|
408
|
+
the box shows the check mark that assistive tech reports instead of a stale dash; the "select all"
|
|
409
|
+
pattern needs no `(changed)` handler to reset it. Migration: `[indeterminate]="expr"` still works
|
|
410
|
+
one-way and `[(indeterminate)]` binds both ways; a bare `indeterminate` attribute no longer compiles,
|
|
411
|
+
so write `[indeterminate]="true"`.
|
|
412
|
+
|
|
413
|
+
Fixed: the column is `items-start`, so the label row is only as wide as its content and a click on
|
|
414
|
+
empty space beside it no longer toggles the box.
|
|
415
|
+
|
|
416
|
+
### 0.1.0
|
|
417
|
+
|
|
418
|
+
The label's cursor styling is now a computed `labelClasses`. The redundant `[attr.aria-checked]` binding was removed from the native checkbox; the native `checked`/`indeterminate` state already conveys it to assistive tech. The focus ring now clears in forced-colors mode. The host no longer reflects a static `id` attribute onto `<bf-checkbox>`. Fixed the checked box never showing its fill: `bg-surface`/`after:opacity-0` and the "on" branch's `bg-primary`/`after:opacity-100` were both present at once, and Tailwind's stylesheet order let `bg-surface` win, leaving a checked box white with an invisible check mark. The base class list and the on/off branches now each emit exactly one background and one after-opacity utility. The label's clickable hit area is now `min-h-11 items-center` instead of a `::after` pseudo-element; a `::after` extension over projected content (the label text) is the wrong tool — `min-h-11` gets the same ≥ 44 px target without an overlay. Dropped the box's `mt-0.5` top-margin nudge; with the row already `min-h-11 items-center`, the box centres on the label line on its own. Added `ariaLabel` (mirrors `bf-toggle`) for an accessible name when there's no visible `label`, e.g. a row-selection box; the label `<span>` no longer renders when `label` is empty, so an `ariaLabel`-only checkbox has no empty gap next to the box. Projected label content and the `label` input share one styled text span, which hides itself when both are empty (`empty:hidden`).
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# bf-chip-group
|
|
2
|
+
|
|
3
|
+
Named row of chips that picks one like radios, or groups independent toggles
|
|
4
|
+
|
|
5
|
+
`BfChipGroupComponent` from `@bitforgehq/ui` · Forms · since 0.3.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, signal } from '@angular/core';
|
|
11
|
+
import { BfChipComponent, BfChipGroupComponent } from '@bitforgehq/ui';
|
|
12
|
+
|
|
13
|
+
type Status = 'all' | 'open' | 'done';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The bf-chip-group overview.
|
|
17
|
+
*
|
|
18
|
+
* Live demo of `bf-chip-group`: a single-choice status row that reads as radio buttons, with an
|
|
19
|
+
* arrow-key tab stop, and a multiple-choice tag row of independent toggles; the choices are echoed
|
|
20
|
+
* underneath.
|
|
21
|
+
*/
|
|
22
|
+
@Component({
|
|
23
|
+
selector: 'app-example-chip-group-overview',
|
|
24
|
+
imports: [BfChipComponent, BfChipGroupComponent],
|
|
25
|
+
template: `
|
|
26
|
+
<div class="flex flex-col gap-3">
|
|
27
|
+
<bf-chip-group label="Status" [(value)]="status">
|
|
28
|
+
<bf-chip variant="ink" value="all">All</bf-chip>
|
|
29
|
+
<bf-chip variant="ink" value="open">Open</bf-chip>
|
|
30
|
+
<bf-chip variant="ink" value="done">Done</bf-chip>
|
|
31
|
+
</bf-chip-group>
|
|
32
|
+
<bf-chip-group mode="multiple" label="Tags">
|
|
33
|
+
<bf-chip [(selected)]="design">Design</bf-chip>
|
|
34
|
+
<bf-chip [(selected)]="copy">Copy</bf-chip>
|
|
35
|
+
<bf-chip disabled>Archived</bf-chip>
|
|
36
|
+
</bf-chip-group>
|
|
37
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">
|
|
38
|
+
Status: {{ status() }} · Tags: {{ tags() }}
|
|
39
|
+
</p>
|
|
40
|
+
</div>
|
|
41
|
+
`,
|
|
42
|
+
})
|
|
43
|
+
export class ChipGroupOverviewExampleComponent {
|
|
44
|
+
protected readonly status = signal<Status | null>('all');
|
|
45
|
+
protected readonly design = signal(true);
|
|
46
|
+
protected readonly copy = signal(false);
|
|
47
|
+
|
|
48
|
+
protected tags(): string {
|
|
49
|
+
const tags = [this.design() ? 'Design' : '', this.copy() ? 'Copy' : ''].filter(Boolean);
|
|
50
|
+
return tags.length > 0 ? tags.join(', ') : 'none';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Overview
|
|
56
|
+
|
|
57
|
+
A named row of `bf-chip`s. In `single` mode (the default) the chips are a radio group: the
|
|
58
|
+
group owns the selected chip's `value`, one chip is the tab stop and the arrow keys move the
|
|
59
|
+
selection. In `multiple` mode the chips stay independent toggles and the group only names them.
|
|
60
|
+
Not a Signal Forms control: for a form field use `bf-segmented` or `bf-radio-group`.
|
|
61
|
+
|
|
62
|
+
A chip group names a row of `bf-chip`s and, in `single` mode, turns them into a radio group: one chip is selected, the group holds its `value`, and assistive tech announces each chip as "radio, 1 of 3, selected". In `multiple` mode the chips stay independent toggles and the group only names the row.
|
|
63
|
+
|
|
64
|
+
## When to use
|
|
65
|
+
|
|
66
|
+
Use `bf-chip-group` for a row of filter chips that belong together: one status out of "All", "Open", "Done" (`single`), or any number of tags (`multiple`). Use [bf-segmented](https://bitforge-ui.web.app/components/segmented) or [bf-radio-group](https://bitforge-ui.web.app/components/radio-group) instead for a choice in a form that is submitted: the chip group is not a Signal Forms control. Use [bf-chip](https://bitforge-ui.web.app/components/chip) on its own for a single toggle.
|
|
67
|
+
|
|
68
|
+
## Single choice
|
|
69
|
+
|
|
70
|
+
Give every chip a `value` and bind the group's `[(value)]`. The chip whose `value` matches (compared with `Object.is`) is selected; a click, <kbd>Space</kbd> or <kbd>Enter</kbd> selects another one and `(changed)` emits its value. There is no deselect: clicking the selected chip keeps it, as with radio buttons. `value` is `null` while nothing is selected. Inside a `single` group the group's `value` decides which chip is selected, so the chips' own `selected` is ignored. A programmatic write to `value` does not emit.
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
import { Component, signal } from '@angular/core';
|
|
74
|
+
import { BfChipComponent, BfChipGroupComponent } from '@bitforgehq/ui';
|
|
75
|
+
|
|
76
|
+
type View = 'all' | 'open' | 'done';
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* A single-choice filter row.
|
|
80
|
+
*
|
|
81
|
+
* The group holds the selected chip's value and makes the row one radio group: Tab reaches the
|
|
82
|
+
* selected chip, the arrow keys move the selection.
|
|
83
|
+
*/
|
|
84
|
+
@Component({
|
|
85
|
+
selector: 'app-example-chip-group-single',
|
|
86
|
+
imports: [BfChipComponent, BfChipGroupComponent],
|
|
87
|
+
template: `
|
|
88
|
+
<bf-chip-group label="View" [(value)]="view">
|
|
89
|
+
<bf-chip value="all">All</bf-chip>
|
|
90
|
+
<bf-chip value="open">Open</bf-chip>
|
|
91
|
+
<bf-chip value="done">Done</bf-chip>
|
|
92
|
+
</bf-chip-group>
|
|
93
|
+
`,
|
|
94
|
+
})
|
|
95
|
+
export class ChipGroupSingleExampleComponent {
|
|
96
|
+
protected readonly view = signal<View | null>('open');
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Multiple choice
|
|
101
|
+
|
|
102
|
+
With `mode="multiple"` each chip keeps its own `[(selected)]` and `(changed)` and announces as a toggle button (`aria-pressed`); the group adds the `group` role and its name. The group's `value` is unused.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
import { Component, signal } from '@angular/core';
|
|
106
|
+
import { BfChipComponent, BfChipGroupComponent } from '@bitforgehq/ui';
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* A multiple-choice tag row.
|
|
110
|
+
*
|
|
111
|
+
* `mode="multiple"` names the row and leaves each chip an independent toggle with its own
|
|
112
|
+
* `[(selected)]`.
|
|
113
|
+
*/
|
|
114
|
+
@Component({
|
|
115
|
+
selector: 'app-example-chip-group-multiple',
|
|
116
|
+
imports: [BfChipComponent, BfChipGroupComponent],
|
|
117
|
+
template: `
|
|
118
|
+
<bf-chip-group mode="multiple" label="Tags">
|
|
119
|
+
<bf-chip [(selected)]="design">Design</bf-chip>
|
|
120
|
+
<bf-chip [(selected)]="development">Development</bf-chip>
|
|
121
|
+
<bf-chip [(selected)]="research">Research</bf-chip>
|
|
122
|
+
</bf-chip-group>
|
|
123
|
+
`,
|
|
124
|
+
})
|
|
125
|
+
export class ChipGroupMultipleExampleComponent {
|
|
126
|
+
protected readonly design = signal(true);
|
|
127
|
+
protected readonly development = signal(false);
|
|
128
|
+
protected readonly research = signal(true);
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Desktop
|
|
133
|
+
|
|
134
|
+
The chips keep their own hover (a darker border unselected, a deeper tint selected), pointer cursor and pressed fill. In `single` mode the whole row is one tab stop and the arrow keys move through it, so a keyboard user reaches the next control with one <kbd>Tab</kbd>. The row wraps with 8 px between chips and rows. Density follows the chips' `size` (`sm` 32 px for a toolbar or table filter, [density guide](https://bitforge-ui.web.app/density)).
|
|
135
|
+
|
|
136
|
+
## Theming
|
|
137
|
+
|
|
138
|
+
The group draws nothing itself: every colour comes from its chips (see [bf-chip](https://bitforge-ui.web.app/components/chip)), selected chips `primary-soft` / `primary-strong` (`tonal`) or `text` / `bg` (`ink`). The host is a wrapping flex row with an 8 px gap (`gap-2`); add classes to the element to change the layout.
|
|
139
|
+
|
|
140
|
+
## Accessibility
|
|
141
|
+
|
|
142
|
+
`single`: the host is `role="radiogroup"` named by `label` (`aria-label`) or `ariaLabelledby`; each chip's button is `role="radio"` with `aria-checked` and no `aria-pressed`. Only the selected chip (else the first enabled one) sits in the tab order, the others carry `tabindex="-1"`, and selection follows focus, as in `bf-segmented`. Disabled chips, and every chip of a `disabled` group, get the native `disabled` attribute and are skipped. `multiple`: the host is `role="group"` with the same name, and the chips stay `aria-pressed` toggle buttons in the normal tab order. Give the group a `label` or `ariaLabelledby`: it is the only name of the row.
|
|
143
|
+
|
|
144
|
+
### Keyboard
|
|
145
|
+
|
|
146
|
+
| Key | Where | Action |
|
|
147
|
+
| ------------------------------------------------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
148
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | `single` | Moves focus into and out of the group: one tab stop, on the selected chip (else the first enabled one) |
|
|
149
|
+
| <kbd>ArrowRight</kbd> / <kbd>ArrowDown</kbd> | `single` | Selects and focuses the next enabled chip in DOM order, wrapping at the end; not mirrored under `dir="rtl"` yet (a known gap) |
|
|
150
|
+
| <kbd>ArrowLeft</kbd> / <kbd>ArrowUp</kbd> | `single` | Selects and focuses the previous enabled chip in DOM order, wrapping at the start; not mirrored under `dir="rtl"` yet |
|
|
151
|
+
| <kbd>Home</kbd> / <kbd>End</kbd> | `single` | Selects and focuses the first / last enabled chip |
|
|
152
|
+
| <kbd>Space</kbd> / <kbd>Enter</kbd> | `single` | Selects the focused chip |
|
|
153
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | `multiple` | Moves focus from chip to chip |
|
|
154
|
+
| <kbd>Space</kbd> / <kbd>Enter</kbd> | `multiple` | Toggles the focused chip |
|
|
155
|
+
|
|
156
|
+
## Inputs
|
|
157
|
+
|
|
158
|
+
| Name | Type | Default | Description |
|
|
159
|
+
| ---------------- | -------------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
160
|
+
| `mode` | `BfChipGroupMode` (`'single' \| 'multiple'`) | `'single'` | `single` (default): one chip selected, radio semantics; `multiple`: independent toggles. |
|
|
161
|
+
| `label` | `string` | `''` | Accessible name of the group (required unless `ariaLabelledby` is set). |
|
|
162
|
+
| `ariaLabelledby` | `string` | `''` | Id(s) of the element(s) that name the group (e.g. a visible heading) when `label` is not set. |
|
|
163
|
+
| `disabled` | `boolean` | `false` | Disables every chip of the group. |
|
|
164
|
+
| `value` | `T \| null` | `null` | single: the selected chip's value (`null` = none); ignored in multiple mode. Two-way. Every chip of a single-mode group needs a `value` (of type `T`); one without it cannot be selected. Two-way: `[(value)]`. |
|
|
165
|
+
|
|
166
|
+
## Outputs
|
|
167
|
+
|
|
168
|
+
| Name | Payload | Description |
|
|
169
|
+
| --------- | ----------- | ------------------------------------------------------------- |
|
|
170
|
+
| `changed` | `T \| null` | Emitted with the new value after a user change (single mode). |
|
|
171
|
+
|
|
172
|
+
## Slots
|
|
173
|
+
|
|
174
|
+
- `default`: The `bf-chip`s of the group, each with its `value`.
|
|
175
|
+
|
|
176
|
+
## Notes
|
|
177
|
+
|
|
178
|
+
### Do
|
|
179
|
+
|
|
180
|
+
- Give every chip of a `single` group a distinct `value` of the group's value type. A chip without a `value` cannot be selected: it never matches, and a click on it changes nothing.
|
|
181
|
+
- Name the group with `label`, or `ariaLabelledby` pointing at a visible heading.
|
|
182
|
+
|
|
183
|
+
### Don't
|
|
184
|
+
|
|
185
|
+
- Don't bind `[formField]` to a chip group; use `bf-segmented` or `bf-radio-group` in a form.
|
|
186
|
+
- Don't nest other interactive controls between the chips of a `single` group; the arrow keys move between chips only.
|
|
187
|
+
|
|
188
|
+
## Migration notes
|
|
189
|
+
|
|
190
|
+
### 0.3.0
|
|
191
|
+
|
|
192
|
+
New `bf-chip-group` (`BfChipGroupComponent<T>`, `BfChipGroupMode` `'single' | 'multiple'`, root
|
|
193
|
+
entry, F-038): a named row of `bf-chip`s. `single` (default): the host is `role="radiogroup"`
|
|
194
|
+
named by `label` or `ariaLabelledby`, the chips are `role="radio"` with `aria-checked`, one roving
|
|
195
|
+
tab stop (the selected chip, else the first enabled), and the arrow keys (wrapping, skipping
|
|
196
|
+
disabled chips), `Home` and `End` move focus and the selection, as in `bf-segmented`; `Space` and
|
|
197
|
+
`Enter` select, there is no deselect. `[(value)]` holds the selected chip's `value` (`null` for
|
|
198
|
+
none) and `(changed)` emits it after a user change. `multiple`: the host is `role="group"` and the
|
|
199
|
+
chips stay independent `aria-pressed` toggles with their own `[(selected)]`. `disabled` disables
|
|
200
|
+
every chip. It is not a Signal Forms control; for a form field use `bf-segmented` or
|
|
201
|
+
`bf-radio-group`.
|
|
202
|
+
|
|
203
|
+
A chip without a `value` inside a `single` group never matches the group's value and cannot be
|
|
204
|
+
selected, so chips that all leave it unset never read as selected together; give every chip a
|
|
205
|
+
`value`.
|