@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.
Files changed (281) hide show
  1. package/CHANGELOG.md +3665 -0
  2. package/LICENSE +21 -0
  3. package/README.md +175 -0
  4. package/ai/MANIFEST.index.json +383 -0
  5. package/ai/MANIFEST.json +7528 -0
  6. package/ai/REFERENCE.index.json +1461 -0
  7. package/ai/REFERENCE.json +7213 -0
  8. package/ai/UI_GUIDE.md +1159 -0
  9. package/ai/components/action-bar.md +209 -0
  10. package/ai/components/action-sheet.md +526 -0
  11. package/ai/components/alert-dialog.md +509 -0
  12. package/ai/components/alert.md +376 -0
  13. package/ai/components/avatar.md +146 -0
  14. package/ai/components/back-button.md +286 -0
  15. package/ai/components/badge.md +171 -0
  16. package/ai/components/button.md +431 -0
  17. package/ai/components/card.md +303 -0
  18. package/ai/components/checkbox.md +418 -0
  19. package/ai/components/chip-group.md +205 -0
  20. package/ai/components/chip.md +300 -0
  21. package/ai/components/content.md +474 -0
  22. package/ai/components/desktop-toast.md +420 -0
  23. package/ai/components/dialog.md +962 -0
  24. package/ai/components/empty-state.md +146 -0
  25. package/ai/components/field.md +319 -0
  26. package/ai/components/header.md +809 -0
  27. package/ai/components/icon-button.md +221 -0
  28. package/ai/components/icon.md +176 -0
  29. package/ai/components/input.md +455 -0
  30. package/ai/components/list-item.md +326 -0
  31. package/ai/components/list.md +178 -0
  32. package/ai/components/menu-button.md +377 -0
  33. package/ai/components/menu.md +661 -0
  34. package/ai/components/modal.md +613 -0
  35. package/ai/components/number-input.md +423 -0
  36. package/ai/components/page.md +299 -0
  37. package/ai/components/progress.md +215 -0
  38. package/ai/components/radio-group.md +432 -0
  39. package/ai/components/refresher.md +377 -0
  40. package/ai/components/segmented.md +517 -0
  41. package/ai/components/select.md +510 -0
  42. package/ai/components/sheet.md +845 -0
  43. package/ai/components/spinner.md +171 -0
  44. package/ai/components/stack-outlet.md +661 -0
  45. package/ai/components/tab-bar.md +427 -0
  46. package/ai/components/tab.md +295 -0
  47. package/ai/components/tabs.md +408 -0
  48. package/ai/components/textarea.md +371 -0
  49. package/ai/components/toast.md +522 -0
  50. package/ai/components/toggle.md +387 -0
  51. package/ai/reference/bf-action-directive.md +21 -0
  52. package/ai/reference/bf-action-sheet-action-role.md +27 -0
  53. package/ai/reference/bf-action-sheet-action.md +28 -0
  54. package/ai/reference/bf-action-sheet-options.md +28 -0
  55. package/ai/reference/bf-action-sheet-service.md +33 -0
  56. package/ai/reference/bf-alert-dialog-button-role.md +27 -0
  57. package/ai/reference/bf-alert-dialog-button.md +27 -0
  58. package/ai/reference/bf-alert-dialog-options.md +29 -0
  59. package/ai/reference/bf-alert-dialog-service.md +34 -0
  60. package/ai/reference/bf-alert-variant.md +28 -0
  61. package/ai/reference/bf-autocapitalize.md +30 -0
  62. package/ai/reference/bf-avatar-size.md +27 -0
  63. package/ai/reference/bf-avatar-variant.md +28 -0
  64. package/ai/reference/bf-back-at-root.md +28 -0
  65. package/ai/reference/bf-back-button-service.md +40 -0
  66. package/ai/reference/bf-badge-variant.md +29 -0
  67. package/ai/reference/bf-button-size.md +29 -0
  68. package/ai/reference/bf-button-type.md +28 -0
  69. package/ai/reference/bf-button-variant.md +29 -0
  70. package/ai/reference/bf-card-padding.md +26 -0
  71. package/ai/reference/bf-card-variant.md +28 -0
  72. package/ai/reference/bf-chip-group-mode.md +27 -0
  73. package/ai/reference/bf-chip-variant.md +26 -0
  74. package/ai/reference/bf-compare-with.md +24 -0
  75. package/ai/reference/bf-component-overlay-ref.md +33 -0
  76. package/ai/reference/bf-confirm-button-role.md +26 -0
  77. package/ai/reference/bf-confirm-button.md +27 -0
  78. package/ai/reference/bf-confirm-options.md +31 -0
  79. package/ai/reference/bf-confirm-size.md +26 -0
  80. package/ai/reference/bf-content-scroll-event.md +21 -0
  81. package/ai/reference/bf-defaults-options.md +32 -0
  82. package/ai/reference/bf-defaults.md +28 -0
  83. package/ai/reference/bf-desktop-toast-config.md +28 -0
  84. package/ai/reference/bf-desktop-toast-harness.md +33 -0
  85. package/ai/reference/bf-desktop-toast-options.md +28 -0
  86. package/ai/reference/bf-desktop-toast-position.md +28 -0
  87. package/ai/reference/bf-desktop-toast-service.md +42 -0
  88. package/ai/reference/bf-dialog-config.md +44 -0
  89. package/ai/reference/bf-dialog-harness.md +52 -0
  90. package/ai/reference/bf-dialog-ref.md +44 -0
  91. package/ai/reference/bf-dialog-role.md +26 -0
  92. package/ai/reference/bf-dialog-service.md +55 -0
  93. package/ai/reference/bf-dialog-size.md +31 -0
  94. package/ai/reference/bf-dialog-template-context.md +32 -0
  95. package/ai/reference/bf-empty-state-heading-level.md +26 -0
  96. package/ai/reference/bf-enter-key-hint.md +31 -0
  97. package/ai/reference/bf-field-control-class.md +32 -0
  98. package/ai/reference/bf-field-control-state.md +28 -0
  99. package/ai/reference/bf-field-size.md +29 -0
  100. package/ai/reference/bf-haptics-impact-style.md +27 -0
  101. package/ai/reference/bf-haptics-notification-type.md +27 -0
  102. package/ai/reference/bf-haptics-service.md +41 -0
  103. package/ai/reference/bf-harness-filters.md +29 -0
  104. package/ai/reference/bf-horizontal-scroll-directive.md +129 -0
  105. package/ai/reference/bf-icon-button-variant.md +28 -0
  106. package/ai/reference/bf-icon-size.md +27 -0
  107. package/ai/reference/bf-input-autocapitalize.md +36 -0
  108. package/ai/reference/bf-input-enter-key-hint.md +38 -0
  109. package/ai/reference/bf-input-input-mode.md +38 -0
  110. package/ai/reference/bf-input-mode.md +32 -0
  111. package/ai/reference/bf-input-type.md +35 -0
  112. package/ai/reference/bf-jsdom-shim-options.md +22 -0
  113. package/ai/reference/bf-keyboard-assist-behavior.md +26 -0
  114. package/ai/reference/bf-keyboard-assist-directive.md +185 -0
  115. package/ai/reference/bf-keyboard-phase.md +30 -0
  116. package/ai/reference/bf-keyboard-service.md +50 -0
  117. package/ai/reference/bf-labels-overrides.md +26 -0
  118. package/ai/reference/bf-labels.md +44 -0
  119. package/ai/reference/bf-menu-button-harness.md +40 -0
  120. package/ai/reference/bf-menu-button-variant.md +27 -0
  121. package/ai/reference/bf-menu-divider-directive.md +13 -0
  122. package/ai/reference/bf-menu-group-directive.md +14 -0
  123. package/ai/reference/bf-menu-harness.md +55 -0
  124. package/ai/reference/bf-menu-item-checkbox-directive.md +31 -0
  125. package/ai/reference/bf-menu-item-directive.md +31 -0
  126. package/ai/reference/bf-menu-item-harness.md +52 -0
  127. package/ai/reference/bf-menu-item-radio-directive.md +29 -0
  128. package/ai/reference/bf-menu-placement.md +29 -0
  129. package/ai/reference/bf-menu-trigger-directive.md +46 -0
  130. package/ai/reference/bf-modal-options.md +30 -0
  131. package/ai/reference/bf-modal-presentation.md +26 -0
  132. package/ai/reference/bf-modal-service.md +38 -0
  133. package/ai/reference/bf-nav-controller.md +59 -0
  134. package/ai/reference/bf-nav-direction.md +23 -0
  135. package/ai/reference/bf-nav-target.md +21 -0
  136. package/ai/reference/bf-number-input-mode.md +28 -0
  137. package/ai/reference/bf-number-input-step.md +25 -0
  138. package/ai/reference/bf-overlay-auto-focus.md +29 -0
  139. package/ai/reference/bf-overlay-controller.md +22 -0
  140. package/ai/reference/bf-overlay-host.md +36 -0
  141. package/ai/reference/bf-overlay-options.md +53 -0
  142. package/ai/reference/bf-overlay-position.md +30 -0
  143. package/ai/reference/bf-overlay-ref.md +53 -0
  144. package/ai/reference/bf-overlay-role.md +27 -0
  145. package/ai/reference/bf-overlay-service.md +53 -0
  146. package/ai/reference/bf-overlay-stack-options.md +24 -0
  147. package/ai/reference/bf-overlay-stack.md +81 -0
  148. package/ai/reference/bf-overlay-template-context.md +24 -0
  149. package/ai/reference/bf-overscroll-stretch-directive.md +163 -0
  150. package/ai/reference/bf-page-context.md +24 -0
  151. package/ai/reference/bf-platform-service.md +53 -0
  152. package/ai/reference/bf-platform.md +28 -0
  153. package/ai/reference/bf-progress-size.md +26 -0
  154. package/ai/reference/bf-progress-tone.md +28 -0
  155. package/ai/reference/bf-radio-group-harness.md +49 -0
  156. package/ai/reference/bf-radio-group-orientation.md +26 -0
  157. package/ai/reference/bf-radio-option.md +30 -0
  158. package/ai/reference/bf-refresher-refresh-event.md +22 -0
  159. package/ai/reference/bf-segment-option.md +29 -0
  160. package/ai/reference/bf-segmented-harness.md +48 -0
  161. package/ai/reference/bf-segmented-size.md +29 -0
  162. package/ai/reference/bf-select-harness.md +70 -0
  163. package/ai/reference/bf-select-option.md +29 -0
  164. package/ai/reference/bf-sheet-auto-focus.md +29 -0
  165. package/ai/reference/bf-sheet-options.md +32 -0
  166. package/ai/reference/bf-sheet-ref.md +45 -0
  167. package/ai/reference/bf-sheet-service.md +34 -0
  168. package/ai/reference/bf-shell-config-mode.md +28 -0
  169. package/ai/reference/bf-shell-config.md +38 -0
  170. package/ai/reference/bf-shell-dynamic-type-limit.md +21 -0
  171. package/ai/reference/bf-shell-dynamic-type.md +25 -0
  172. package/ai/reference/bf-shell-mode.md +28 -0
  173. package/ai/reference/bf-shell-testing-options.md +26 -0
  174. package/ai/reference/bf-size.md +45 -0
  175. package/ai/reference/bf-spinner-size.md +27 -0
  176. package/ai/reference/bf-stack-change.md +22 -0
  177. package/ai/reference/bf-stack-reuse-strategy.md +32 -0
  178. package/ai/reference/bf-stack-transition-event.md +26 -0
  179. package/ai/reference/bf-stack-transition.md +26 -0
  180. package/ai/reference/bf-stacked-overlay.md +23 -0
  181. package/ai/reference/bf-system-bars-style.md +38 -0
  182. package/ai/reference/bf-tab-bar-controller.md +51 -0
  183. package/ai/reference/bf-tab-bar-host.md +52 -0
  184. package/ai/reference/bf-tab-context.md +32 -0
  185. package/ai/reference/bf-tab-links.md +37 -0
  186. package/ai/reference/bf-testing-options.md +27 -0
  187. package/ai/reference/bf-text-scale-service.md +35 -0
  188. package/ai/reference/bf-theme.md +27 -0
  189. package/ai/reference/bf-toast-action.md +25 -0
  190. package/ai/reference/bf-toast-controller.md +17 -0
  191. package/ai/reference/bf-toast-dismiss-reason.md +29 -0
  192. package/ai/reference/bf-toast-options.md +29 -0
  193. package/ai/reference/bf-toast-position.md +26 -0
  194. package/ai/reference/bf-toast-ref.md +41 -0
  195. package/ai/reference/bf-toast-service.md +39 -0
  196. package/ai/reference/bf-toggle-label-position.md +26 -0
  197. package/ai/reference/bf-transition-kind.md +28 -0
  198. package/ai/reference/bf-validation-message.md +31 -0
  199. package/ai/reference/bf-validation-messages.md +33 -0
  200. package/ai/reference/bf-view-event.md +28 -0
  201. package/ai/reference/bf-view-lifecycle.md +41 -0
  202. package/ai/reference/bf-view-state.md +28 -0
  203. package/ai/reference/bf_default_labels.md +21 -0
  204. package/ai/reference/bf_default_values.md +21 -0
  205. package/ai/reference/bf_defaults.md +21 -0
  206. package/ai/reference/bf_desktop_toast_defaults.md +21 -0
  207. package/ai/reference/bf_dialog_data.md +17 -0
  208. package/ai/reference/bf_labels.md +21 -0
  209. package/ai/reference/bf_overlay_data.md +19 -0
  210. package/ai/reference/bf_overlay_host.md +21 -0
  211. package/ai/reference/bf_sheet_default_snap_points.md +17 -0
  212. package/ai/reference/bf_shell_config.md +21 -0
  213. package/ai/reference/bf_shell_defaults.md +21 -0
  214. package/ai/reference/bf_tab_bar.md +21 -0
  215. package/ai/reference/bf_tab_bar_controller.md +21 -0
  216. package/ai/reference/bf_tab_context.md +21 -0
  217. package/ai/reference/bf_tab_links.md +21 -0
  218. package/ai/reference/bf_theme_storage.md +19 -0
  219. package/ai/reference/create-transition.md +36 -0
  220. package/ai/reference/duration_fast.md +17 -0
  221. package/ai/reference/duration_md.md +17 -0
  222. package/ai/reference/duration_overlay.md +17 -0
  223. package/ai/reference/duration_push.md +17 -0
  224. package/ai/reference/fade-down.md +26 -0
  225. package/ai/reference/fade-in.md +25 -0
  226. package/ai/reference/fade-out.md +25 -0
  227. package/ai/reference/fade-up.md +26 -0
  228. package/ai/reference/inject-shell-mode.md +23 -0
  229. package/ai/reference/install-bf-jsdom-shims.md +31 -0
  230. package/ai/reference/ios_duration.md +17 -0
  231. package/ai/reference/ios_easing.md +17 -0
  232. package/ai/reference/is-text-entry.md +25 -0
  233. package/ai/reference/max_complete_ms.md +17 -0
  234. package/ai/reference/md-transition.md +31 -0
  235. package/ai/reference/md_duration.md +17 -0
  236. package/ai/reference/md_easing.md +17 -0
  237. package/ai/reference/overlay_duration.md +21 -0
  238. package/ai/reference/overlay_easing.md +19 -0
  239. package/ai/reference/overlay_reduced_duration.md +17 -0
  240. package/ai/reference/pop-frames-at.md +25 -0
  241. package/ai/reference/prefers-reduced-motion.md +25 -0
  242. package/ai/reference/provide-bf-defaults.md +33 -0
  243. package/ai/reference/provide-bf-desktop-toast.md +28 -0
  244. package/ai/reference/provide-bf-labels.md +38 -0
  245. package/ai/reference/provide-bf-shell-testing.md +34 -0
  246. package/ai/reference/provide-bf-shell.md +41 -0
  247. package/ai/reference/provide-bf-testing.md +33 -0
  248. package/ai/reference/reduced-transition.md +28 -0
  249. package/ai/reference/reduced_duration.md +17 -0
  250. package/ai/reference/resolve-shell-mode.md +33 -0
  251. package/ai/reference/scale-in.md +25 -0
  252. package/ai/reference/settle-duration.md +25 -0
  253. package/ai/reference/settle-pop.md +33 -0
  254. package/ai/reference/settle_easing.md +17 -0
  255. package/ai/reference/should-complete-pop.md +26 -0
  256. package/ai/reference/slide-down.md +26 -0
  257. package/ai/reference/slide-up.md +26 -0
  258. package/ai/reference/with-bf-shell-routing.md +19 -0
  259. package/bin/bf-ui.js +81 -0
  260. package/fesm2022/bitforgehq-ui-icons.mjs +230 -0
  261. package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -0
  262. package/fesm2022/bitforgehq-ui-mobile.mjs +5530 -0
  263. package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -0
  264. package/fesm2022/bitforgehq-ui-number-input.mjs +680 -0
  265. package/fesm2022/bitforgehq-ui-number-input.mjs.map +1 -0
  266. package/fesm2022/bitforgehq-ui-overlay.mjs +2675 -0
  267. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -0
  268. package/fesm2022/bitforgehq-ui-testing.mjs +402 -0
  269. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -0
  270. package/fesm2022/bitforgehq-ui.mjs +7765 -0
  271. package/fesm2022/bitforgehq-ui.mjs.map +1 -0
  272. package/llms-full.txt +25394 -0
  273. package/llms.txt +329 -0
  274. package/package.json +94 -0
  275. package/theme.css +253 -0
  276. package/types/bitforgehq-ui-icons.d.ts +50 -0
  277. package/types/bitforgehq-ui-mobile.d.ts +2914 -0
  278. package/types/bitforgehq-ui-number-input.d.ts +282 -0
  279. package/types/bitforgehq-ui-overlay.d.ts +1514 -0
  280. package/types/bitforgehq-ui-testing.d.ts +495 -0
  281. 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`.