@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,509 @@
|
|
|
1
|
+
# bf-alert-dialog
|
|
2
|
+
|
|
3
|
+
Centred confirmation dialog with a title, message and a row of role-styled buttons
|
|
4
|
+
|
|
5
|
+
`BfAlertDialogComponent` from `@bitforgehq/ui/mobile` · Overlays · since 0.2.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, inject, signal } from '@angular/core';
|
|
11
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
12
|
+
import {
|
|
13
|
+
BfAlertDialogService,
|
|
14
|
+
BfContentComponent,
|
|
15
|
+
BfHeaderComponent,
|
|
16
|
+
BfPageComponent,
|
|
17
|
+
} from '@bitforgehq/ui/mobile';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `bf-alert-dialog` showcase: a confirmation with a cancel and a destructive button, and an
|
|
21
|
+
* informational alert with a single OK.
|
|
22
|
+
*/
|
|
23
|
+
@Component({
|
|
24
|
+
selector: 'app-alert-dialog-showcase',
|
|
25
|
+
imports: [BfPageComponent, BfHeaderComponent, BfContentComponent, BfButtonComponent],
|
|
26
|
+
template: `
|
|
27
|
+
<bf-page data-showcase="alert-dialog">
|
|
28
|
+
<bf-header title="Alert dialog" [largeTitle]="false" />
|
|
29
|
+
<bf-content>
|
|
30
|
+
<div class="flex flex-col gap-4 p-4">
|
|
31
|
+
<p class="text-caption text-text-muted">
|
|
32
|
+
An alert must be answered: there is no backdrop dismiss, and Escape answers with the
|
|
33
|
+
cancel button when there is one.
|
|
34
|
+
</p>
|
|
35
|
+
<bf-button variant="danger" (clicked)="confirmDelete()">Delete draft</bf-button>
|
|
36
|
+
<bf-button variant="tonal" (clicked)="inform()">Show sync status</bf-button>
|
|
37
|
+
<p data-showcase-result class="text-body">{{ result() }}</p>
|
|
38
|
+
</div>
|
|
39
|
+
</bf-content>
|
|
40
|
+
</bf-page>
|
|
41
|
+
`,
|
|
42
|
+
})
|
|
43
|
+
export class AlertDialogShowcasePage {
|
|
44
|
+
private readonly alerts = inject(BfAlertDialogService);
|
|
45
|
+
|
|
46
|
+
protected readonly result = signal('No answer yet.');
|
|
47
|
+
|
|
48
|
+
async confirmDelete(): Promise<void> {
|
|
49
|
+
const answer = await this.alerts.open({
|
|
50
|
+
title: 'Delete draft?',
|
|
51
|
+
message: 'This cannot be undone.',
|
|
52
|
+
buttons: [
|
|
53
|
+
{ label: 'Cancel', role: 'cancel' },
|
|
54
|
+
{ label: 'Delete', role: 'destructive' },
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
this.report(answer);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async inform(): Promise<void> {
|
|
61
|
+
const answer = await this.alerts.open({
|
|
62
|
+
title: 'All synced',
|
|
63
|
+
message: 'Your notes are up to date on every device.',
|
|
64
|
+
});
|
|
65
|
+
this.report(answer);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A navigation closes an alert without a cancel button with no answer (`undefined`). */
|
|
69
|
+
private report(answer: string | undefined): void {
|
|
70
|
+
this.result.set(answer === undefined ? 'Closed without an answer.' : `Answered “${answer}”.`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Overview
|
|
76
|
+
|
|
77
|
+
The alert dialog card that `BfAlertDialogService.open()` renders on the overlay core
|
|
78
|
+
(`position: 'center'`): an `h2.text-heading` title, an optional `text-body text-text-secondary`
|
|
79
|
+
message, and a right-aligned row of `bf-button`s (`cancel`-role `text`, `destructive`-role
|
|
80
|
+
`danger`, the first plain button `primary`, further plain ones `tonal`). Focus starts on the
|
|
81
|
+
`cancel`-role button, else the first one. Tapping a button resolves the overlay with its
|
|
82
|
+
`id ?? label`. There is no backdrop dismiss; `Escape` and the hardware
|
|
83
|
+
back button are handled by `BfAlertDialogService` (the cancel button's answer, or claimed and
|
|
84
|
+
ignored when there is none). Not used directly in templates.
|
|
85
|
+
|
|
86
|
+
A centred confirmation dialog. `BfAlertDialogService.open({ title, message?, buttons? })` renders
|
|
87
|
+
`bf-alert-dialog` on the overlay core: a title, an optional message and a row of buttons. It
|
|
88
|
+
resolves a `Promise<string>` with the tapped button's `id`, or its label when it has none — always
|
|
89
|
+
a string, never `undefined`, because the dialog cannot be dismissed without answering it. You never place `<bf-alert-dialog>`
|
|
90
|
+
in a template yourself.
|
|
91
|
+
|
|
92
|
+
## When to use
|
|
93
|
+
|
|
94
|
+
Use `BfAlertDialogService` in the Capacitor shell for a short question the user must answer before going on: discard a draft, delete an item. Use [BfActionSheetService](https://bitforge-ui.web.app/api/bf-action-sheet-service) instead for a choice between several actions, and [BfDialogService](https://bitforge-ui.web.app/api/bf-dialog-service) `confirm()` on desktop.
|
|
95
|
+
|
|
96
|
+
## Basic usage
|
|
97
|
+
|
|
98
|
+
Without `buttons`, the dialog shows a single button labelled with `provideBfLabels()`'s `ok` ("OK").
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { Component, inject, signal } from '@angular/core';
|
|
102
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
103
|
+
import { BfAlertDialogService } from '@bitforgehq/ui/mobile';
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* An alert with a message and the default OK button.
|
|
107
|
+
*
|
|
108
|
+
* `open()` resolves once the alert is answered.
|
|
109
|
+
*
|
|
110
|
+
* @preview none
|
|
111
|
+
*/
|
|
112
|
+
@Component({
|
|
113
|
+
selector: 'app-example-alert-dialog-basic',
|
|
114
|
+
imports: [BfButtonComponent],
|
|
115
|
+
template: `
|
|
116
|
+
<div class="flex flex-col items-start gap-3">
|
|
117
|
+
<bf-button (clicked)="open()">Sync</bf-button>
|
|
118
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
119
|
+
</div>
|
|
120
|
+
`,
|
|
121
|
+
})
|
|
122
|
+
export class AlertDialogBasicExampleComponent {
|
|
123
|
+
private readonly alerts = inject(BfAlertDialogService);
|
|
124
|
+
protected readonly result = signal('');
|
|
125
|
+
|
|
126
|
+
protected async open(): Promise<void> {
|
|
127
|
+
await this.alerts.open({
|
|
128
|
+
title: 'Sync failed',
|
|
129
|
+
message: 'Check your connection and try again.',
|
|
130
|
+
});
|
|
131
|
+
this.result.set('Alert closed.');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Confirmation with a cancel button
|
|
137
|
+
|
|
138
|
+
Give `buttons` a `cancel`-role entry to let the dialog be dismissed by `Escape` or the hardware
|
|
139
|
+
back button too — both resolve with that button's answer. The cancel button also takes the
|
|
140
|
+
initial focus, wherever it sits in the row.
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import { Component, inject, signal } from '@angular/core';
|
|
144
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
145
|
+
import { BfAlertDialogService } from '@bitforgehq/ui/mobile';
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* A confirmation with a cancel button.
|
|
149
|
+
*
|
|
150
|
+
* The `cancel`-role button answers a dismissal; the destructive one renders in the danger colour.
|
|
151
|
+
*
|
|
152
|
+
* @preview none
|
|
153
|
+
*/
|
|
154
|
+
@Component({
|
|
155
|
+
selector: 'app-example-alert-dialog-confirm',
|
|
156
|
+
imports: [BfButtonComponent],
|
|
157
|
+
template: `
|
|
158
|
+
<div class="flex flex-col items-start gap-3">
|
|
159
|
+
<bf-button variant="danger" (clicked)="open()">Delete list…</bf-button>
|
|
160
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
161
|
+
</div>
|
|
162
|
+
`,
|
|
163
|
+
})
|
|
164
|
+
export class AlertDialogConfirmExampleComponent {
|
|
165
|
+
private readonly alerts = inject(BfAlertDialogService);
|
|
166
|
+
protected readonly result = signal('');
|
|
167
|
+
|
|
168
|
+
protected async open(): Promise<void> {
|
|
169
|
+
const answer = await this.alerts.open({
|
|
170
|
+
title: 'Delete list?',
|
|
171
|
+
message: 'This cannot be undone.',
|
|
172
|
+
buttons: [
|
|
173
|
+
{ id: 'cancel', label: 'Cancel', role: 'cancel' },
|
|
174
|
+
{ id: 'delete', label: 'Delete', role: 'destructive' },
|
|
175
|
+
],
|
|
176
|
+
});
|
|
177
|
+
this.result.set(answer === 'delete' ? 'Deleted.' : 'Kept.');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Answers with ids
|
|
183
|
+
|
|
184
|
+
Give each button an `id` and compare against it: `open()` resolves `id ?? label`, so the check
|
|
185
|
+
keeps working when the labels are translated, and two buttons that end up with the same label are
|
|
186
|
+
still told apart. Without an `id` the label is the answer, as before.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
import { Component, inject, signal } from '@angular/core';
|
|
190
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
191
|
+
import { BfAlertDialogService } from '@bitforgehq/ui/mobile';
|
|
192
|
+
|
|
193
|
+
/** The app's translated labels (German here): the ids, not these, carry the answer. */
|
|
194
|
+
const LABELS = { cancel: 'Abbrechen', device: 'Auf diesem Gerät', cloud: 'In der Cloud' };
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Buttons answered by id.
|
|
198
|
+
*
|
|
199
|
+
* The ids stay the same whatever the translated labels say.
|
|
200
|
+
*
|
|
201
|
+
* @preview none
|
|
202
|
+
*/
|
|
203
|
+
@Component({
|
|
204
|
+
selector: 'app-example-alert-dialog-ids',
|
|
205
|
+
imports: [BfButtonComponent],
|
|
206
|
+
template: `
|
|
207
|
+
<div class="flex flex-col items-start gap-3">
|
|
208
|
+
<bf-button (clicked)="open()">Save a copy</bf-button>
|
|
209
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
210
|
+
</div>
|
|
211
|
+
`,
|
|
212
|
+
})
|
|
213
|
+
export class AlertDialogIdsExampleComponent {
|
|
214
|
+
private readonly alerts = inject(BfAlertDialogService);
|
|
215
|
+
protected readonly result = signal('');
|
|
216
|
+
|
|
217
|
+
protected async open(): Promise<void> {
|
|
218
|
+
const where = await this.alerts.open({
|
|
219
|
+
title: 'Save a copy?',
|
|
220
|
+
buttons: [
|
|
221
|
+
{ id: 'cancel', label: LABELS.cancel, role: 'cancel' },
|
|
222
|
+
{ id: 'device', label: LABELS.device },
|
|
223
|
+
{ id: 'cloud', label: LABELS.cloud },
|
|
224
|
+
],
|
|
225
|
+
});
|
|
226
|
+
this.result.set(where ?? 'Dismissed.');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Button variants
|
|
232
|
+
|
|
233
|
+
Buttons render left to right in the order given: a `cancel`-role button is `text`, a
|
|
234
|
+
`destructive`-role button is `danger`, the first plain button is `primary`, and any further plain
|
|
235
|
+
button is `tonal`.
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
import { Component, inject, signal } from '@angular/core';
|
|
239
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
240
|
+
import { BfAlertDialogService } from '@bitforgehq/ui/mobile';
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The button variants of an alert.
|
|
244
|
+
*
|
|
245
|
+
* The first plain button is primary, further plain ones tonal, and a destructive one danger.
|
|
246
|
+
*
|
|
247
|
+
* @preview none
|
|
248
|
+
*/
|
|
249
|
+
@Component({
|
|
250
|
+
selector: 'app-example-alert-dialog-variants',
|
|
251
|
+
imports: [BfButtonComponent],
|
|
252
|
+
template: `
|
|
253
|
+
<div class="flex flex-col items-start gap-3">
|
|
254
|
+
<bf-button (clicked)="open()">Close the draft</bf-button>
|
|
255
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
256
|
+
</div>
|
|
257
|
+
`,
|
|
258
|
+
})
|
|
259
|
+
export class AlertDialogVariantsExampleComponent {
|
|
260
|
+
private readonly alerts = inject(BfAlertDialogService);
|
|
261
|
+
protected readonly result = signal('');
|
|
262
|
+
|
|
263
|
+
protected async open(): Promise<void> {
|
|
264
|
+
const answer = await this.alerts.open({
|
|
265
|
+
title: 'Keep editing?',
|
|
266
|
+
buttons: [
|
|
267
|
+
{ label: 'Discard', role: 'destructive' },
|
|
268
|
+
{ label: 'Save as draft' }, // tonal (a plain button after the primary one)
|
|
269
|
+
{ label: 'Save' }, // primary (the first plain button)
|
|
270
|
+
],
|
|
271
|
+
});
|
|
272
|
+
this.result.set(answer ?? 'Dismissed.');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Navigation
|
|
278
|
+
|
|
279
|
+
A navigation to another URL path closes the alert: `open()` resolves the cancel button's answer
|
|
280
|
+
when it has one, else `undefined` (so `open()` returns `Promise<string | undefined>`; treat
|
|
281
|
+
`undefined` like cancel). A query- or fragment-only change leaves it open. In the `/mobile` shell
|
|
282
|
+
every push, every pop (the header back button, swipe-back, `BfNavController.back()`) and every tab
|
|
283
|
+
switch changes the path, so an alert opened on a page closes when that page is left. The Android
|
|
284
|
+
hardware back button with an alert open never pops the page (its handler runs at priority 100,
|
|
285
|
+
above the page pop): it answers with the cancel button, or is ignored without one. Android back
|
|
286
|
+
in a browser without Capacitor (Chrome, a PWA) does the same where `CloseWatcher` exists, and
|
|
287
|
+
the URL stays: an alert without a cancel button swallows it and stays open. An alert
|
|
288
|
+
opened before the first navigation ends closes when the start URL redirects. Pass
|
|
289
|
+
`closeOnNavigation: false` to keep it open until a button is tapped.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
import { Component, inject, signal } from '@angular/core';
|
|
293
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
294
|
+
import { BfAlertDialogService } from '@bitforgehq/ui/mobile';
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* An alert that stays open across navigations.
|
|
298
|
+
*
|
|
299
|
+
* `closeOnNavigation: false` keeps it open when the route changes underneath.
|
|
300
|
+
*
|
|
301
|
+
* @preview none
|
|
302
|
+
*/
|
|
303
|
+
@Component({
|
|
304
|
+
selector: 'app-example-alert-dialog-navigation',
|
|
305
|
+
imports: [BfButtonComponent],
|
|
306
|
+
template: `
|
|
307
|
+
<div class="flex flex-col items-start gap-3">
|
|
308
|
+
<bf-button (clicked)="open()">Sync</bf-button>
|
|
309
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
310
|
+
</div>
|
|
311
|
+
`,
|
|
312
|
+
})
|
|
313
|
+
export class AlertDialogNavigationExampleComponent {
|
|
314
|
+
private readonly alerts = inject(BfAlertDialogService);
|
|
315
|
+
protected readonly result = signal('');
|
|
316
|
+
|
|
317
|
+
protected async open(): Promise<void> {
|
|
318
|
+
const answer = await this.alerts.open({ title: 'Sync failed', closeOnNavigation: false });
|
|
319
|
+
this.result.set(answer ?? 'Dismissed.');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Labels and the injector
|
|
325
|
+
|
|
326
|
+
Default strings come from `provideBfLabels()` (`ok`, "OK"). The service is a root singleton, so it reads
|
|
327
|
+
the app-level labels; to use a feature's own `provideBfLabels()` (in a component's `providers`),
|
|
328
|
+
pass that component's injector. It then also becomes the parent of the dialog's content injector,
|
|
329
|
+
so the content resolves the feature's providers.
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
import { Component, Injector, inject } from '@angular/core';
|
|
333
|
+
import { BfButtonComponent, provideBfLabels } from '@bitforgehq/ui';
|
|
334
|
+
import { BfAlertDialogService } from '@bitforgehq/ui/mobile';
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* An alert that reads a feature's labels.
|
|
338
|
+
*
|
|
339
|
+
* This feature provides German labels; passing the opener's injector is what makes the alert's
|
|
340
|
+
* default button read "Verstanden" instead of the app's "OK".
|
|
341
|
+
*
|
|
342
|
+
* @preview none
|
|
343
|
+
*/
|
|
344
|
+
@Component({
|
|
345
|
+
selector: 'app-example-alert-dialog-injector',
|
|
346
|
+
imports: [BfButtonComponent],
|
|
347
|
+
providers: [provideBfLabels({ ok: 'Verstanden' })],
|
|
348
|
+
template: `<bf-button (clicked)="open()">Speichern</bf-button>`,
|
|
349
|
+
})
|
|
350
|
+
export class AlertDialogInjectorExampleComponent {
|
|
351
|
+
private readonly alerts = inject(BfAlertDialogService);
|
|
352
|
+
private readonly injector = inject(Injector);
|
|
353
|
+
|
|
354
|
+
protected open(): void {
|
|
355
|
+
void this.alerts.open({ title: 'Gespeichert', injector: this.injector });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Desktop
|
|
361
|
+
|
|
362
|
+
`bf-alert-dialog` is phone-shaped and meant for the Capacitor shell (`provideBfShell()`). On a
|
|
363
|
+
desktop web app, use the desktop overlays from `@bitforgehq/ui/overlay` instead: they need only
|
|
364
|
+
`@angular/cdk`, no Capacitor and no router. A desktop dialog locks the page's scroll, makes the page
|
|
365
|
+
behind it `inert`, and traps and restores focus.
|
|
366
|
+
|
|
367
|
+
`BfDialogService.confirm()` is the desktop alert dialog: the same `buttons` (ids, `cancel` and
|
|
368
|
+
`destructive` roles), focus on the cancel button, and an answer of `id ?? label`.
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
import { Component, inject, signal } from '@angular/core';
|
|
372
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
373
|
+
import { BfDialogService } from '@bitforgehq/ui/overlay';
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* A destructive confirmation.
|
|
377
|
+
*
|
|
378
|
+
* `confirm()` resolves with the pressed button's id: `delete`, `cancel`, or the cancel answer on
|
|
379
|
+
* `Escape`.
|
|
380
|
+
*/
|
|
381
|
+
@Component({
|
|
382
|
+
selector: 'app-example-dialog-confirm',
|
|
383
|
+
imports: [BfButtonComponent],
|
|
384
|
+
template: `
|
|
385
|
+
<div class="flex flex-col items-start gap-3">
|
|
386
|
+
<bf-button variant="danger" (clicked)="remove()">Delete project…</bf-button>
|
|
387
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
388
|
+
</div>
|
|
389
|
+
`,
|
|
390
|
+
})
|
|
391
|
+
export class DialogConfirmExampleComponent {
|
|
392
|
+
private readonly dialogs = inject(BfDialogService);
|
|
393
|
+
protected readonly result = signal('');
|
|
394
|
+
|
|
395
|
+
protected async remove(): Promise<void> {
|
|
396
|
+
const answer = await this.dialogs.confirm({
|
|
397
|
+
title: 'Delete project?',
|
|
398
|
+
message: 'Its tasks and files are deleted too. This cannot be undone.',
|
|
399
|
+
buttons: [
|
|
400
|
+
{ id: 'cancel', label: 'Cancel', role: 'cancel' },
|
|
401
|
+
{ id: 'delete', label: 'Delete', role: 'destructive' },
|
|
402
|
+
],
|
|
403
|
+
});
|
|
404
|
+
this.result.set(answer === 'delete' ? 'Deleted (not really).' : 'Kept.');
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Theming
|
|
410
|
+
|
|
411
|
+
- Card: `bg-surface`, `rounded-card` (`--radius-card`), `shadow-floating`, fixed `w-75` (300 px)
|
|
412
|
+
(`max-w-[calc(100vw-2*var(--spacing-gutter))]` on narrow viewports).
|
|
413
|
+
- Title: `text-heading`; message: `text-body text-text-secondary`.
|
|
414
|
+
- Buttons: `bf-button` (`text`, `danger`, `primary`, `tonal` — see Button variants).
|
|
415
|
+
- Backdrop: `bf-overlay-backdrop` from the overlay core (a black scrim: 40 % in light mode, 60 % in dark), rendered but never
|
|
416
|
+
dismissing.
|
|
417
|
+
|
|
418
|
+
## Accessibility
|
|
419
|
+
|
|
420
|
+
The overlay panel is a `role="alertdialog"` with `aria-modal="true"`, `aria-labelledby`
|
|
421
|
+
pointing at the title and, when there is a `message`, `aria-describedby` pointing at it; the page stack behind it is `inert`. Focus lands on the `cancel`-role
|
|
422
|
+
button, else on the first button (the title and message hold no focusable content), so Enter on
|
|
423
|
+
open never confirms a destructive action ([APG alert dialog](https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/):
|
|
424
|
+
focus the least destructive choice). Tab is trapped inside, and focus returns to the
|
|
425
|
+
element that opened the dialog on close. There is no backdrop dismiss. `Escape` and the hardware
|
|
426
|
+
back button resolve with the cancel button's `id ?? label` when `buttons` has one; with none, both are
|
|
427
|
+
claimed and otherwise ignored, so the dialog stays open — **an alert must have a cancel button to
|
|
428
|
+
be dismissible** any other way than tapping a button. Either way the key stops at the alert: an
|
|
429
|
+
alert opened over a sheet never lets `Escape` close the sheet underneath. Under `prefers-reduced-motion` the card
|
|
430
|
+
cross-fades in and out instead of scaling.
|
|
431
|
+
|
|
432
|
+
### Keyboard
|
|
433
|
+
|
|
434
|
+
| Key | Action |
|
|
435
|
+
| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
|
|
436
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves between the buttons, wrapping inside the dialog; focus starts on the `cancel`-role button, else the first |
|
|
437
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Activates the focused button |
|
|
438
|
+
| <kbd>Escape</kbd> | Answers with the `cancel`-role button; without one, it is claimed and ignored |
|
|
439
|
+
|
|
440
|
+
## Notes
|
|
441
|
+
|
|
442
|
+
### Do
|
|
443
|
+
|
|
444
|
+
- Give every dismissible confirmation a `cancel`-role button, so `Escape` and the hardware back
|
|
445
|
+
button work the way people expect.
|
|
446
|
+
- Keep `message` short and put the consequence in it ("This cannot be undone") rather than in the
|
|
447
|
+
title.
|
|
448
|
+
- Use `role: 'destructive'` on the button that causes data loss, and word it as the action
|
|
449
|
+
("Delete", not "Yes").
|
|
450
|
+
|
|
451
|
+
### Don't
|
|
452
|
+
|
|
453
|
+
- Don't open an alert with no cancel button unless every button is a safe, equally valid answer
|
|
454
|
+
(an informational dialog with just "OK") — otherwise give it a cancel button.
|
|
455
|
+
- Don't stack more than two or three buttons; use an action sheet (`BfActionSheetService`) for a
|
|
456
|
+
longer list of choices.
|
|
457
|
+
- Don't rely on a backdrop tap to close the dialog — there is none, by design, so the user must
|
|
458
|
+
make a choice.
|
|
459
|
+
|
|
460
|
+
## Migration notes
|
|
461
|
+
|
|
462
|
+
### 0.3.0
|
|
463
|
+
|
|
464
|
+
New exported `BfAlertDialogButtonRole` (`'cancel' | 'destructive'`), the type of
|
|
465
|
+
`BfAlertDialogButton.role` (was an inline union with the same values).
|
|
466
|
+
|
|
467
|
+
**Type:** `open()` returns `Promise<string | undefined>` (was `Promise<string>`): a navigation to
|
|
468
|
+
another URL path closes the alert and resolves the cancel button's answer, else `undefined`. New
|
|
469
|
+
`closeOnNavigation` option (default true). Migration: handle `undefined` like cancel (see
|
|
470
|
+
Migration 18).
|
|
471
|
+
|
|
472
|
+
**Behaviour:** the default button (no `buttons`) is labelled with `provideBfLabels()`'s `ok`
|
|
473
|
+
("OK").
|
|
474
|
+
|
|
475
|
+
New `injector` option: labels resolve from it per call (default the root injector) and it becomes
|
|
476
|
+
the parent of the content's injector, so a feature-level `provideBfLabels()` and the feature's
|
|
477
|
+
other providers reach the dialog.
|
|
478
|
+
|
|
479
|
+
`BfAlertDialogButton` gains `id?: string`; `open()` resolves the tapped button's `id ?? label`,
|
|
480
|
+
and `Escape` and the hardware back button the cancel button's. Buttons render with
|
|
481
|
+
`track $index`, so two buttons with the same label both render.
|
|
482
|
+
|
|
483
|
+
**Behaviour:** focus starts on the `cancel`-role button, else the first button (APG alert dialog),
|
|
484
|
+
even when a destructive button comes first (see Migration 17).
|
|
485
|
+
|
|
486
|
+
JSDoc of `alert-dialog.service.ts` for the API reference: `@summary`/`@since` on the exported
|
|
487
|
+
symbols; `@default` on the option properties the code applies; `@param` docs. No behaviour change.
|
|
488
|
+
|
|
489
|
+
The docs say that Android back in a browser with `CloseWatcher` answers with the cancel button,
|
|
490
|
+
and that an alert without one swallows it; see `### overlay core` and Migration 35.
|
|
491
|
+
|
|
492
|
+
### 0.2.0
|
|
493
|
+
|
|
494
|
+
New `BfAlertDialogService.open({ title, message?, buttons? })` in `@bitforgehq/ui/mobile`,
|
|
495
|
+
resolving `Promise<string>` with the tapped button's label. Renders `bf-alert-dialog` on the
|
|
496
|
+
overlay core (`position: 'center'`, back priority 100, not counted as a modal, no backdrop
|
|
497
|
+
dismiss, `role="alertdialog"` with `aria-labelledby` the generated title id and, when there is a
|
|
498
|
+
message, `aria-describedby` its paragraph). The card (`w-75
|
|
499
|
+
max-w-[calc(100vw-2*var(--spacing-gutter))] bg-surface rounded-card shadow-floating p-6 flex flex-col gap-4`) is an
|
|
500
|
+
`h2.text-heading` title, an optional `text-body text-text-secondary` message and a right-aligned
|
|
501
|
+
`buttons` row (default `[{ label: 'OK' }]`): `cancel`-role renders `text`, `destructive`-role
|
|
502
|
+
`danger`, the first plain button `primary`, further plain ones `tonal`. Unlike every other
|
|
503
|
+
overlay, `Escape` and the hardware back button do not close through the core's
|
|
504
|
+
`dismissOnEscape`/back handling (the service passes `onEscape` and claims the back button itself):
|
|
505
|
+
they
|
|
506
|
+
resolve with the cancel button's label when `buttons` has one, and are otherwise claimed and
|
|
507
|
+
ignored, since an alert with no cancel button can only be answered by tapping a button. Focus
|
|
508
|
+
lands on the first button — the core's first-tabbable search, since the title and message hold no
|
|
509
|
+
focusable content. `provideBfShell()` now also provides `BfAlertDialogService`.
|