@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,526 @@
|
|
|
1
|
+
# bf-action-sheet
|
|
2
|
+
|
|
3
|
+
Bottom panel of labelled actions with a separate cancel button, destructive ones shown in red
|
|
4
|
+
|
|
5
|
+
`BfActionSheetComponent` 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
|
+
type BfActionSheetAction,
|
|
14
|
+
BfActionSheetService,
|
|
15
|
+
BfContentComponent,
|
|
16
|
+
BfHeaderComponent,
|
|
17
|
+
BfPageComponent,
|
|
18
|
+
} from '@bitforgehq/ui/mobile';
|
|
19
|
+
import { Copy, Share, Trash2 } from 'lucide';
|
|
20
|
+
|
|
21
|
+
/** The showcase's rows: two plain actions with icons and a destructive one. */
|
|
22
|
+
export const SHOWCASE_ACTIONS: readonly BfActionSheetAction[] = [
|
|
23
|
+
{ label: 'Copy link', icon: Copy },
|
|
24
|
+
{ label: 'Share', icon: Share },
|
|
25
|
+
{ label: 'Delete', icon: Trash2, role: 'destructive' },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** `bf-action-sheet` showcase: a button opens a titled action sheet with a destructive row. */
|
|
29
|
+
@Component({
|
|
30
|
+
selector: 'app-action-sheet-showcase',
|
|
31
|
+
imports: [BfPageComponent, BfHeaderComponent, BfContentComponent, BfButtonComponent],
|
|
32
|
+
template: `
|
|
33
|
+
<bf-page data-showcase="action-sheet">
|
|
34
|
+
<bf-header title="Action sheet" [largeTitle]="false" />
|
|
35
|
+
<bf-content>
|
|
36
|
+
<div class="flex flex-col gap-4 p-4">
|
|
37
|
+
<p class="text-caption text-text-muted">
|
|
38
|
+
Tap a row to choose it; Cancel, the backdrop and Escape dismiss the sheet without a
|
|
39
|
+
choice.
|
|
40
|
+
</p>
|
|
41
|
+
<bf-button (clicked)="open()">Photo actions</bf-button>
|
|
42
|
+
<p data-showcase-result class="text-body">{{ result() }}</p>
|
|
43
|
+
</div>
|
|
44
|
+
</bf-content>
|
|
45
|
+
</bf-page>
|
|
46
|
+
`,
|
|
47
|
+
})
|
|
48
|
+
export class ActionSheetShowcasePage {
|
|
49
|
+
private readonly actionSheets = inject(BfActionSheetService);
|
|
50
|
+
|
|
51
|
+
protected readonly result = signal('Nothing chosen yet.');
|
|
52
|
+
|
|
53
|
+
async open(): Promise<void> {
|
|
54
|
+
const choice = await this.actionSheets.open({
|
|
55
|
+
title: 'Holiday photo',
|
|
56
|
+
actions: [...SHOWCASE_ACTIONS],
|
|
57
|
+
});
|
|
58
|
+
this.result.set(choice ? `Chose “${choice}”.` : 'Dismissed.');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Overview
|
|
64
|
+
|
|
65
|
+
The action sheet panel that `BfActionSheetService.open()` renders on the overlay core
|
|
66
|
+
(`position: 'bottom'`): an optional title above a `bf-list` of interactive rows for the
|
|
67
|
+
actions (destructive ones in `text-danger`, an icon in the leading slot) and a separate
|
|
68
|
+
`tonal` `bf-button` for the cancel action — rendered even when the caller passes none,
|
|
69
|
+
labelled "Cancel", which takes the initial focus. Tapping a row resolves the overlay with its
|
|
70
|
+
`id ?? label`; the cancel button, like
|
|
71
|
+
the backdrop, `Escape` and the hardware back button, resolves `undefined`. Not used directly
|
|
72
|
+
in templates.
|
|
73
|
+
|
|
74
|
+
A bottom sheet of actions. `BfActionSheetService.open({ title?, actions })` renders `bf-action-sheet`
|
|
75
|
+
on the overlay core: an optional title above a list of tappable rows and a separate cancel
|
|
76
|
+
button. It resolves a `Promise` with the tapped row's `id` (its label when it has none), or
|
|
77
|
+
`undefined` when the sheet is dismissed. You never place `<bf-action-sheet>` in a template yourself.
|
|
78
|
+
|
|
79
|
+
## When to use
|
|
80
|
+
|
|
81
|
+
Use `BfActionSheetService` in the Capacitor shell for a choice between a few actions on one item, raised from the bottom of the screen, with a separate Cancel. Use [BfAlertDialogService](https://bitforge-ui.web.app/api/bf-alert-dialog-service) instead for a yes/no question that must be answered, and [bf-menu](https://bitforge-ui.web.app/components/menu) on desktop.
|
|
82
|
+
|
|
83
|
+
## Basic usage
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
import { Component, inject, signal } from '@angular/core';
|
|
87
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
88
|
+
import { BfActionSheetService } from '@bitforgehq/ui/mobile';
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* An action sheet that shares a list.
|
|
92
|
+
*
|
|
93
|
+
* `open()` resolves with the chosen action's label (its `id` when it has one), or `undefined` when
|
|
94
|
+
* dismissed.
|
|
95
|
+
*
|
|
96
|
+
* @preview none
|
|
97
|
+
*/
|
|
98
|
+
@Component({
|
|
99
|
+
selector: 'app-example-action-sheet-basic',
|
|
100
|
+
imports: [BfButtonComponent],
|
|
101
|
+
template: `
|
|
102
|
+
<div class="flex flex-col items-start gap-3">
|
|
103
|
+
<bf-button (clicked)="open()">Share…</bf-button>
|
|
104
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
105
|
+
</div>
|
|
106
|
+
`,
|
|
107
|
+
})
|
|
108
|
+
export class ActionSheetBasicExampleComponent {
|
|
109
|
+
private readonly sheets = inject(BfActionSheetService);
|
|
110
|
+
protected readonly result = signal('');
|
|
111
|
+
|
|
112
|
+
protected async open(): Promise<void> {
|
|
113
|
+
const label = await this.sheets.open({
|
|
114
|
+
title: 'Share list',
|
|
115
|
+
actions: [{ label: 'Copy link' }, { label: 'Send a copy' }],
|
|
116
|
+
});
|
|
117
|
+
this.result.set(label === 'Copy link' ? 'Link copied.' : (label ?? 'Dismissed.'));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`open()` resolves once, either with the tapped row's `id ?? label` or with `undefined` — tapping
|
|
123
|
+
the cancel button, a backdrop tap, `Escape` and the hardware back button all resolve `undefined`.
|
|
124
|
+
|
|
125
|
+
## Answers with ids
|
|
126
|
+
|
|
127
|
+
Give each row an `id` and compare against it, so the check survives translated labels and two
|
|
128
|
+
rows with the same label stay distinct. A row without an `id` answers with its label.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import { Component, inject, signal } from '@angular/core';
|
|
132
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
133
|
+
import { BfActionSheetService } from '@bitforgehq/ui/mobile';
|
|
134
|
+
|
|
135
|
+
/** The app's translated labels (German here): the ids, not these, carry the answer. */
|
|
136
|
+
const LABELS = { share: 'Teilen', delete: 'Löschen' };
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Actions answered by id.
|
|
140
|
+
*
|
|
141
|
+
* Each action has an `id`, so the answer stays the same whatever the translated
|
|
142
|
+
* label says.
|
|
143
|
+
*
|
|
144
|
+
* @preview none
|
|
145
|
+
*/
|
|
146
|
+
@Component({
|
|
147
|
+
selector: 'app-example-action-sheet-ids',
|
|
148
|
+
imports: [BfButtonComponent],
|
|
149
|
+
template: `
|
|
150
|
+
<div class="flex flex-col items-start gap-3">
|
|
151
|
+
<bf-button (clicked)="open()">Actions</bf-button>
|
|
152
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
153
|
+
</div>
|
|
154
|
+
`,
|
|
155
|
+
})
|
|
156
|
+
export class ActionSheetIdsExampleComponent {
|
|
157
|
+
private readonly sheets = inject(BfActionSheetService);
|
|
158
|
+
protected readonly result = signal('');
|
|
159
|
+
|
|
160
|
+
protected async open(): Promise<void> {
|
|
161
|
+
const answer = await this.sheets.open({
|
|
162
|
+
actions: [
|
|
163
|
+
{ id: 'share', label: LABELS.share },
|
|
164
|
+
{ id: 'delete', label: LABELS.delete, role: 'destructive' },
|
|
165
|
+
],
|
|
166
|
+
});
|
|
167
|
+
this.result.set(answer === 'delete' ? 'List deleted.' : (answer ?? 'Dismissed.'));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Destructive actions
|
|
173
|
+
|
|
174
|
+
An action with `role: 'destructive'` renders its label in `text-danger` instead of the default
|
|
175
|
+
text color; nothing else changes.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { Component, inject, signal } from '@angular/core';
|
|
179
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
180
|
+
import { BfActionSheetService } from '@bitforgehq/ui/mobile';
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A destructive action.
|
|
184
|
+
*
|
|
185
|
+
* `role: 'destructive'` renders the action in the danger colour.
|
|
186
|
+
*
|
|
187
|
+
* @preview none
|
|
188
|
+
*/
|
|
189
|
+
@Component({
|
|
190
|
+
selector: 'app-example-action-sheet-destructive',
|
|
191
|
+
imports: [BfButtonComponent],
|
|
192
|
+
template: `
|
|
193
|
+
<div class="flex flex-col items-start gap-3">
|
|
194
|
+
<bf-button variant="danger" (clicked)="open()">Delete list…</bf-button>
|
|
195
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
196
|
+
</div>
|
|
197
|
+
`,
|
|
198
|
+
})
|
|
199
|
+
export class ActionSheetDestructiveExampleComponent {
|
|
200
|
+
private readonly sheets = inject(BfActionSheetService);
|
|
201
|
+
protected readonly result = signal('');
|
|
202
|
+
|
|
203
|
+
protected async open(): Promise<void> {
|
|
204
|
+
const label = await this.sheets.open({
|
|
205
|
+
title: 'Delete list?',
|
|
206
|
+
actions: [{ label: 'Delete', role: 'destructive' }],
|
|
207
|
+
});
|
|
208
|
+
this.result.set(label ? 'Deleted.' : 'Kept.');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Icons
|
|
214
|
+
|
|
215
|
+
Pass `icon` (a Lucide `IconNode`, e.g. `Share` from `lucide`) to render it in the row's leading
|
|
216
|
+
slot.
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { Component, inject, signal } from '@angular/core';
|
|
220
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
221
|
+
import { BfActionSheetService } from '@bitforgehq/ui/mobile';
|
|
222
|
+
import { Share, Trash2 } from 'lucide';
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Actions with icons.
|
|
226
|
+
*
|
|
227
|
+
* Pass Lucide icon data as `icon`; it renders before the label.
|
|
228
|
+
*
|
|
229
|
+
* @preview none
|
|
230
|
+
*/
|
|
231
|
+
@Component({
|
|
232
|
+
selector: 'app-example-action-sheet-icons',
|
|
233
|
+
imports: [BfButtonComponent],
|
|
234
|
+
template: `
|
|
235
|
+
<div class="flex flex-col items-start gap-3">
|
|
236
|
+
<bf-button (clicked)="open()">More</bf-button>
|
|
237
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
238
|
+
</div>
|
|
239
|
+
`,
|
|
240
|
+
})
|
|
241
|
+
export class ActionSheetIconsExampleComponent {
|
|
242
|
+
private readonly sheets = inject(BfActionSheetService);
|
|
243
|
+
protected readonly result = signal('');
|
|
244
|
+
|
|
245
|
+
protected async open(): Promise<void> {
|
|
246
|
+
const label = await this.sheets.open({
|
|
247
|
+
actions: [
|
|
248
|
+
{ label: 'Share', icon: Share },
|
|
249
|
+
{ label: 'Delete', icon: Trash2, role: 'destructive' },
|
|
250
|
+
],
|
|
251
|
+
});
|
|
252
|
+
this.result.set(label ?? 'Dismissed.');
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Cancel button
|
|
258
|
+
|
|
259
|
+
A separate `tonal` `bf-button` always sits under the rows. Give one of your `actions` `role:
|
|
260
|
+
'cancel'` to control its label; without one, the sheet adds a cancel button of its own, labelled with `provideBfLabels()`'s `cancel` ("Cancel") — either
|
|
261
|
+
way, tapping it resolves `afterClosed`/`open()` with `undefined` (an `id` on it is ignored), and it
|
|
262
|
+
never appears as a row. It takes the initial focus.
|
|
263
|
+
Only the first `cancel`-role action is used; any further `cancel`-role actions are ignored.
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { Component, inject, signal } from '@angular/core';
|
|
267
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
268
|
+
import { BfActionSheetService } from '@bitforgehq/ui/mobile';
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* A sheet with its own cancel action.
|
|
272
|
+
*
|
|
273
|
+
* A `cancel`-role action replaces the built-in Cancel button and answers a dismissal.
|
|
274
|
+
*
|
|
275
|
+
* @preview none
|
|
276
|
+
*/
|
|
277
|
+
@Component({
|
|
278
|
+
selector: 'app-example-action-sheet-cancel',
|
|
279
|
+
imports: [BfButtonComponent],
|
|
280
|
+
template: `
|
|
281
|
+
<div class="flex flex-col items-start gap-3">
|
|
282
|
+
<bf-button (clicked)="open()">Close editor</bf-button>
|
|
283
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
284
|
+
</div>
|
|
285
|
+
`,
|
|
286
|
+
})
|
|
287
|
+
export class ActionSheetCancelExampleComponent {
|
|
288
|
+
private readonly sheets = inject(BfActionSheetService);
|
|
289
|
+
protected readonly result = signal('');
|
|
290
|
+
|
|
291
|
+
protected async open(): Promise<void> {
|
|
292
|
+
const label = await this.sheets.open({
|
|
293
|
+
actions: [
|
|
294
|
+
{ label: 'Discard changes', role: 'destructive' },
|
|
295
|
+
{ label: "Don't discard", role: 'cancel' },
|
|
296
|
+
],
|
|
297
|
+
});
|
|
298
|
+
this.result.set(label ?? 'Dismissed.');
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Navigation
|
|
304
|
+
|
|
305
|
+
A navigation to another URL path closes the action sheet and `open()` resolves `undefined`, as
|
|
306
|
+
its cancel button does. A query- or fragment-only change leaves it open. In the `/mobile` shell
|
|
307
|
+
every push, every pop (the header back button, swipe-back, `BfNavController.back()`) and every tab
|
|
308
|
+
switch changes the path, so an action sheet opened on a page closes when that page is left. The
|
|
309
|
+
Android hardware back button with an action sheet open closes the action sheet only (its handler
|
|
310
|
+
runs at priority 100, above the page pop), so one press never closes it and pops the page as
|
|
311
|
+
well. Android back in a browser without Capacitor (Chrome, a PWA) does the same where
|
|
312
|
+
`CloseWatcher` exists: it closes the action sheet, resolving `undefined`, and leaves the URL
|
|
313
|
+
alone. An action sheet opened before the first navigation ends closes when the start URL
|
|
314
|
+
redirects. Pass `closeOnNavigation: false` to keep it open.
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
import { Component, inject, signal } from '@angular/core';
|
|
318
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
319
|
+
import { BfActionSheetService } from '@bitforgehq/ui/mobile';
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* An action sheet that stays open across navigations.
|
|
323
|
+
*
|
|
324
|
+
* `closeOnNavigation: false` keeps it open when the route changes underneath.
|
|
325
|
+
*
|
|
326
|
+
* @preview none
|
|
327
|
+
*/
|
|
328
|
+
@Component({
|
|
329
|
+
selector: 'app-example-action-sheet-navigation',
|
|
330
|
+
imports: [BfButtonComponent],
|
|
331
|
+
template: `
|
|
332
|
+
<div class="flex flex-col items-start gap-3">
|
|
333
|
+
<bf-button (clicked)="open()">Actions</bf-button>
|
|
334
|
+
<p aria-live="polite" class="text-caption text-text-muted">{{ result() }}</p>
|
|
335
|
+
</div>
|
|
336
|
+
`,
|
|
337
|
+
})
|
|
338
|
+
export class ActionSheetNavigationExampleComponent {
|
|
339
|
+
private readonly sheets = inject(BfActionSheetService);
|
|
340
|
+
protected readonly result = signal('');
|
|
341
|
+
private readonly actions = [{ label: 'Pin' }, { label: 'Archive' }];
|
|
342
|
+
|
|
343
|
+
protected async open(): Promise<void> {
|
|
344
|
+
const answer = await this.sheets.open({ actions: this.actions, closeOnNavigation: false });
|
|
345
|
+
this.result.set(answer ?? 'Dismissed.');
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## Labels and the injector
|
|
351
|
+
|
|
352
|
+
Default strings come from `provideBfLabels()` (`cancel` "Cancel", `actions` "Actions"). The service is a root singleton, so it reads
|
|
353
|
+
the app-level labels; to use a feature's own `provideBfLabels()` (in a component's `providers`),
|
|
354
|
+
pass that component's injector. It then also becomes the parent of the action sheet's content injector,
|
|
355
|
+
so the content resolves the feature's providers.
|
|
356
|
+
|
|
357
|
+
```ts
|
|
358
|
+
import { Component, Injector, inject } from '@angular/core';
|
|
359
|
+
import { BfButtonComponent, provideBfLabels } from '@bitforgehq/ui';
|
|
360
|
+
import { BfActionSheetService } from '@bitforgehq/ui/mobile';
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* An action sheet that reads a feature's labels.
|
|
364
|
+
*
|
|
365
|
+
* This feature provides German labels; the service is a root singleton, so passing the opener's
|
|
366
|
+
* injector is what makes the sheet's Cancel button read "Abbrechen" and its name "Aktionen".
|
|
367
|
+
*
|
|
368
|
+
* @preview none
|
|
369
|
+
*/
|
|
370
|
+
@Component({
|
|
371
|
+
selector: 'app-example-action-sheet-injector',
|
|
372
|
+
imports: [BfButtonComponent],
|
|
373
|
+
providers: [provideBfLabels({ cancel: 'Abbrechen', actions: 'Aktionen' })],
|
|
374
|
+
template: `<bf-button (clicked)="open()">Aktionen</bf-button>`,
|
|
375
|
+
})
|
|
376
|
+
export class ActionSheetInjectorExampleComponent {
|
|
377
|
+
private readonly actionSheets = inject(BfActionSheetService);
|
|
378
|
+
private readonly injector = inject(Injector);
|
|
379
|
+
private readonly actions = [{ label: 'Anheften' }, { label: 'Archivieren' }];
|
|
380
|
+
|
|
381
|
+
protected open(): void {
|
|
382
|
+
void this.actionSheets.open({ actions: this.actions, injector: this.injector });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Desktop
|
|
388
|
+
|
|
389
|
+
`bf-action-sheet` is phone-shaped and meant for the Capacitor shell (`provideBfShell()`). On a
|
|
390
|
+
desktop web app, use the desktop overlays from `@bitforgehq/ui/overlay` instead: they need only
|
|
391
|
+
`@angular/cdk`, no Capacitor and no router. A desktop dialog locks the page's scroll, makes the page
|
|
392
|
+
behind it `inert`, and traps and restores focus.
|
|
393
|
+
|
|
394
|
+
A list of actions belongs in a `bf-menu`, opened by a `bf-menu-button` next to the thing it acts on;
|
|
395
|
+
a single destructive choice in `BfDialogService.confirm()`.
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
import { Component, signal } from '@angular/core';
|
|
399
|
+
import {
|
|
400
|
+
BfMenuButtonComponent,
|
|
401
|
+
BfMenuComponent,
|
|
402
|
+
BfMenuItemDirective,
|
|
403
|
+
} from '@bitforgehq/ui/overlay';
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Row actions in a menu.
|
|
407
|
+
*
|
|
408
|
+
* A `bf-menu-button` opens the menu; each item reports its `triggered`, and Archive is disabled.
|
|
409
|
+
*/
|
|
410
|
+
@Component({
|
|
411
|
+
selector: 'app-example-menu-basic',
|
|
412
|
+
imports: [BfMenuButtonComponent, BfMenuComponent, BfMenuItemDirective],
|
|
413
|
+
template: `
|
|
414
|
+
<bf-menu-button [menu]="rowActions">Actions</bf-menu-button>
|
|
415
|
+
<p aria-live="polite" class="text-caption text-text-muted mt-3">{{ result() }}</p>
|
|
416
|
+
|
|
417
|
+
<ng-template #rowActions>
|
|
418
|
+
<bf-menu>
|
|
419
|
+
<button bfMenuItem (triggered)="result.set('Renamed.')">Rename</button>
|
|
420
|
+
<button bfMenuItem (triggered)="result.set('Duplicated.')">Duplicate</button>
|
|
421
|
+
<button bfMenuItem bfMenuItemDisabled>Archive</button>
|
|
422
|
+
</bf-menu>
|
|
423
|
+
</ng-template>
|
|
424
|
+
`,
|
|
425
|
+
})
|
|
426
|
+
export class MenuBasicExampleComponent {
|
|
427
|
+
protected readonly result = signal('');
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Theming
|
|
432
|
+
|
|
433
|
+
- Panel: `bg-surface`, `rounded-t-card` (`--radius-card`), `shadow-floating`, `w-screen` (edge to
|
|
434
|
+
edge), `pb-[max(var(--spacing-safe-bottom),16px)]` (the safe area, at least 16 px).
|
|
435
|
+
- Title: `text-caption text-text-muted`.
|
|
436
|
+
- Rows: `bf-list-item` (`text-danger` for destructive labels); Backdrop: `bf-overlay-backdrop`
|
|
437
|
+
from the overlay core (a black scrim: 40 % in light mode, 60 % in dark).
|
|
438
|
+
- Cancel button: `bf-button variant="tonal" [fullWidth]="true"`, spanning the panel.
|
|
439
|
+
- Inset: `px-3` (12 px), deliberately tighter than the 16 px screen gutter: the rows add their own
|
|
440
|
+
14 px padding, so row text still sits 26 px from the screen edge.
|
|
441
|
+
|
|
442
|
+
## Accessibility
|
|
443
|
+
|
|
444
|
+
The overlay panel is a `role="dialog"` with `aria-modal="true"` and an `aria-label` from `title`
|
|
445
|
+
(default `provideBfLabels()`'s `actions`, "Actions"); the page stack behind it is `inert`. Focus starts on the cancel button, the
|
|
446
|
+
least destructive choice, even when a destructive row comes first; Shift+Tab reaches the rows. Tab
|
|
447
|
+
is trapped inside, and focus returns to the element that
|
|
448
|
+
opened the sheet on close. `Escape`, a backdrop tap and the hardware back button all resolve
|
|
449
|
+
`undefined`. Under `prefers-reduced-motion` the panel cross-fades in and out instead of sliding.
|
|
450
|
+
|
|
451
|
+
### Keyboard
|
|
452
|
+
|
|
453
|
+
| Key | Action |
|
|
454
|
+
| ------------------------------------------------ | ------------------------------------------------------------------------------------ |
|
|
455
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves between the rows and Cancel, wrapping inside the sheet; focus starts on Cancel |
|
|
456
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Activates the focused row or Cancel |
|
|
457
|
+
| <kbd>Escape</kbd> | Closes the sheet; the promise resolves `undefined` |
|
|
458
|
+
|
|
459
|
+
## Notes
|
|
460
|
+
|
|
461
|
+
### Do
|
|
462
|
+
|
|
463
|
+
- Order destructive actions clearly and keep the cancel action's intent obvious ("Discard",
|
|
464
|
+
"Keep editing") rather than relying on its position alone.
|
|
465
|
+
- Give the sheet a `title` when the actions need context ("Delete list?", "Share via").
|
|
466
|
+
- Use icons consistently — either every row has one, or none does.
|
|
467
|
+
|
|
468
|
+
### Don't
|
|
469
|
+
|
|
470
|
+
- Don't put more than one destructive action in the same sheet; ask a confirming alert dialog
|
|
471
|
+
instead for anything hard to undo.
|
|
472
|
+
- Don't rely on the synthesized "Cancel" label meaning "no" for a destructive prompt — pass an
|
|
473
|
+
explicit `cancel`-role action with wording that matches ("Keep list").
|
|
474
|
+
- Don't use an action sheet for a single choice with only one outcome; use `BfAlertDialogService`
|
|
475
|
+
for confirmations that need a message.
|
|
476
|
+
|
|
477
|
+
## Migration notes
|
|
478
|
+
|
|
479
|
+
### 0.3.0
|
|
480
|
+
|
|
481
|
+
New exported `BfActionSheetActionRole` (`'destructive' | 'cancel'`), the type of
|
|
482
|
+
`BfActionSheetAction.role` (was an inline union with the same values).
|
|
483
|
+
|
|
484
|
+
**Behaviour:** a navigation to another URL path closes it, resolving `undefined`; new
|
|
485
|
+
`closeOnNavigation` option (default true). Migration: pass `closeOnNavigation: false` to keep it
|
|
486
|
+
open (see Migration 19).
|
|
487
|
+
|
|
488
|
+
**Behaviour:** the synthesized cancel button is labelled with `provideBfLabels()`'s `cancel`
|
|
489
|
+
("Cancel"), and a sheet without `title` is named by its `actions` label ("Actions").
|
|
490
|
+
|
|
491
|
+
New `injector` option: labels resolve from it per call (default the root injector) and it becomes
|
|
492
|
+
the parent of the content's injector, so a feature-level `provideBfLabels()` and the feature's
|
|
493
|
+
other providers reach the action sheet.
|
|
494
|
+
|
|
495
|
+
`BfActionSheetAction` gains `id?: string`; `open()` resolves the tapped row's `id ?? label` (the
|
|
496
|
+
cancel action still resolves `undefined`). Rows render with `track $index`, so two rows with the
|
|
497
|
+
same label both render.
|
|
498
|
+
|
|
499
|
+
**Behaviour:** focus starts on the cancel button, not the first row (see Migration 17).
|
|
500
|
+
|
|
501
|
+
JSDoc of `action-sheet.component.ts` for the API reference: `@summary`/`@since` on the exported
|
|
502
|
+
symbols. No behaviour change.
|
|
503
|
+
|
|
504
|
+
JSDoc of `action-sheet.service.ts` for the API reference: `@summary`/`@since` on the exported
|
|
505
|
+
symbols; `@default` on the option properties the code applies; `@param` docs. No behaviour change.
|
|
506
|
+
|
|
507
|
+
The docs say that Android back in a browser with `CloseWatcher` closes the action sheet, resolving
|
|
508
|
+
`undefined`; see `### overlay core` and Migration 35.
|
|
509
|
+
|
|
510
|
+
### 0.2.0
|
|
511
|
+
|
|
512
|
+
New `BfActionSheetService.open({ title?, actions })` in `@bitforgehq/ui/mobile`, resolving
|
|
513
|
+
`Promise<string | undefined>`. Renders `bf-action-sheet` on the overlay core (`position:
|
|
514
|
+
'bottom'`, back priority 100, not counted as a modal, `role="dialog"` with `aria-label` = `title`
|
|
515
|
+
or "Actions"; slides up on open and down on close). The panel (`bg-surface rounded-t-card
|
|
516
|
+
shadow-floating w-screen px-3 pt-3 pb-[max(var(--spacing-safe-bottom),16px)]`, spanning the
|
|
517
|
+
screen with at least 16 px under the cancel button) is an optional `title` caption over a `bf-list
|
|
518
|
+
[inset]="false"` of the non-`cancel` `actions` as interactive rows (an icon from `icon` in the
|
|
519
|
+
leading slot; `destructive`-role labels render `text-danger`, projected through the row's default
|
|
520
|
+
slot since `bf-list-item` has no way to colour its own title), and a separate `tonal` `bf-button`
|
|
521
|
+
(`fullWidth`) for the cancel action — the caller's first `cancel`-role entry if any (further ones are
|
|
522
|
+
ignored), else a synthesized "Cancel". Destructive labels truncate like the other rows.
|
|
523
|
+
Tapping a row resolves with its label; the cancel button, a backdrop tap, `Escape` and the
|
|
524
|
+
hardware back button all resolve `undefined` (the overlay core's defaults, unchanged). The panel's
|
|
525
|
+
`px-3` inset is deliberately tighter than the 16 px gutter: the rows add their own 14 px.
|
|
526
|
+
`provideBfShell()` now also provides `BfActionSheetService`.
|