@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,431 @@
1
+ # bf-button
2
+
3
+ Pill-shaped action button with five variants, three sizes and a loading state
4
+
5
+ `BfButtonComponent` from `@bitforgehq/ui` · Actions · since 0.1.0
6
+
7
+ ## Example
8
+
9
+ ```ts
10
+ import { Component, DestroyRef, inject, signal } from '@angular/core';
11
+ import { RouterLink } from '@angular/router';
12
+ import { BfButtonComponent } from '@bitforgehq/ui';
13
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
14
+ import { ArrowRight, Plus } from 'lucide';
15
+
16
+ /**
17
+ * The bf-button overview.
18
+ *
19
+ * Live demo of `bf-button`: the five variants, plus loading, size and icon-start examples; every press is reported under the buttons; then the sm, md and lg sizes in a row, and an `a[bfButton]` link to this page's "As a link" section.
20
+ */
21
+ @Component({
22
+ selector: 'app-example-button-overview',
23
+ imports: [RouterLink, BfButtonComponent, BfIconComponent],
24
+ template: `
25
+ <div class="flex flex-col gap-3">
26
+ <div class="flex flex-wrap items-center gap-3">
27
+ <bf-button (clicked)="press('Primary')">Primary</bf-button>
28
+ <bf-button variant="tonal" (clicked)="press('Tonal')">Tonal</bf-button>
29
+ <bf-button variant="outline" (clicked)="press('Outline')">Outline</bf-button>
30
+ <bf-button variant="text" (clicked)="press('Text')">Text</bf-button>
31
+ <bf-button variant="danger" (clicked)="press('Danger')">Danger</bf-button>
32
+ </div>
33
+ <div class="flex flex-wrap items-center gap-3">
34
+ <bf-button [loading]="saving()" (clicked)="save()">Save changes</bf-button>
35
+ <bf-button size="sm" (clicked)="press('Small')">Small</bf-button>
36
+ <bf-button variant="tonal" (clicked)="press('New task')">
37
+ <bf-icon bfSlot="icon-start" [icon]="plus" />
38
+ New task
39
+ </bf-button>
40
+ <bf-button variant="outline" [disabled]="true">Disabled</bf-button>
41
+ </div>
42
+ <p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
43
+ <div data-sizes class="flex flex-wrap items-center gap-3">
44
+ <bf-button size="sm" variant="tonal">sm · 32 px</bf-button>
45
+ <bf-button variant="tonal">md · 40 px</bf-button>
46
+ <bf-button size="lg" variant="tonal">lg · 48 px</bf-button>
47
+ </div>
48
+ <!-- a[bfButton]: a real link (Enter, Ctrl/Cmd-click opens a new tab) that looks like the button. -->
49
+ <div data-links class="flex flex-wrap items-center gap-3">
50
+ <a bfButton variant="outline" routerLink="/components/button" fragment="as-a-link">
51
+ Button as a link
52
+ <bf-icon bfSlot="icon-end" [icon]="arrowRight" class="rtl:-scale-x-100" />
53
+ </a>
54
+ </div>
55
+ </div>
56
+ `,
57
+ })
58
+ export class ButtonOverviewExampleComponent {
59
+ private readonly destroyRef = inject(DestroyRef);
60
+
61
+ readonly plus = Plus;
62
+ readonly arrowRight = ArrowRight;
63
+ readonly saving = signal(false);
64
+ readonly status = signal('Press a button.');
65
+
66
+ /** The pending fake-save timer, so it can be cancelled if the page is left mid-save. */
67
+ private saveTimeoutId: ReturnType<typeof setTimeout> | undefined;
68
+
69
+ constructor() {
70
+ this.destroyRef.onDestroy(() => {
71
+ if (this.saveTimeoutId !== undefined) {
72
+ clearTimeout(this.saveTimeoutId);
73
+ }
74
+ });
75
+ }
76
+
77
+ press(name: string): void {
78
+ this.status.set(`Pressed ${name}.`);
79
+ }
80
+
81
+ /** Starts a two-second fake save; the button shows its spinner meanwhile. */
82
+ save(): void {
83
+ this.saving.set(true);
84
+ this.status.set('Saving…');
85
+ this.saveTimeoutId = setTimeout(() => {
86
+ this.saveTimeoutId = undefined;
87
+ this.saving.set(false);
88
+ this.status.set('Saved.');
89
+ }, 2000);
90
+ }
91
+ }
92
+ ```
93
+
94
+ ## Overview
95
+
96
+ Also matches: `a[bfButton]`
97
+
98
+ Pill-shaped action button with five variants, three sizes, a loading state and icon slots. Put
99
+ the label in the default slot and icons in the `icon-start` / `icon-end` slots; never place a
100
+ `bf-button` inside another interactive element. For navigation, put `bfButton` on your own `<a>`
101
+ (with `href` or `routerLink`): it renders as a real link that looks like the button.
102
+
103
+ Buttons trigger an action or move the user forward. The shape is a pill in every variant; the variant encodes emphasis, so a screen reads correctly at a glance: one `primary`, `tonal` beside it, `text` for low-emphasis actions inside cards and rows.
104
+
105
+ ## When to use
106
+
107
+ Use `bf-button` for an action on the page (Save, Send, open a dialog) and `a[bfButton]` for navigation that should look like a button (Get started, View invoice). Use [bf-icon-button](https://bitforge-ui.web.app/components/icon-button) instead for an action shown as an icon alone, [bf-menu-button](https://bitforge-ui.web.app/components/menu-button) when the button opens a list of actions, and a plain link inside running text.
108
+
109
+ ## Basic usage
110
+
111
+ The label goes in the default slot. `clicked` fires on click while the button is enabled and not loading.
112
+
113
+ ```ts
114
+ import { Component, signal } from '@angular/core';
115
+ import { BfButtonComponent } from '@bitforgehq/ui';
116
+
117
+ /**
118
+ * A button that reports its clicks.
119
+ *
120
+ * The label goes in the default slot; `clicked` fires while the button is enabled and not loading.
121
+ */
122
+ @Component({
123
+ selector: 'app-example-button-basic',
124
+ imports: [BfButtonComponent],
125
+ template: `
126
+ <div class="flex flex-col items-start gap-3">
127
+ <bf-button (clicked)="save()">Save changes</bf-button>
128
+ <p aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
129
+ </div>
130
+ `,
131
+ })
132
+ export class ButtonBasicExampleComponent {
133
+ protected readonly status = signal('Not saved yet.');
134
+
135
+ protected save(): void {
136
+ this.status.set('Saved.');
137
+ }
138
+ }
139
+ ```
140
+
141
+ ## Variants
142
+
143
+ `primary` (filled sage), `tonal` (soft tint), `outline`, `text` and `danger`.
144
+
145
+ ```ts
146
+ import { Component } from '@angular/core';
147
+ import { BfButtonComponent } from '@bitforgehq/ui';
148
+
149
+ /** The five button variants. */
150
+ @Component({
151
+ selector: 'app-example-button-variants',
152
+ imports: [BfButtonComponent],
153
+ template: `
154
+ <div class="flex flex-wrap items-center gap-3">
155
+ <bf-button>Primary</bf-button>
156
+ <bf-button variant="tonal">Tonal</bf-button>
157
+ <bf-button variant="outline">Outline</bf-button>
158
+ <bf-button variant="text">Text</bf-button>
159
+ <bf-button variant="danger">Delete</bf-button>
160
+ </div>
161
+ `,
162
+ })
163
+ export class ButtonVariantsExampleComponent {}
164
+ ```
165
+
166
+ ## Sizes
167
+
168
+ `sm` 32 px, `md` 40 px (default), `lg` 48 px — the `BfSize` scale shared with the fields, chips and segmented controls. Unset, `size` follows `provideBfDefaults()`'s `controlSize`, `md` by default (`provideBfDefaults({ size: 'lg' })` enlarges every control of a Capacitor app; `{ size: 'sm' }` in a component's `providers` makes a dense sub-tree). Under a fine pointer (mouse, trackpad) the visible box is the hit area (32 px and up, above the 24 px of WCAG 2.5.8), so dense rows never overlap; under a coarse pointer (touch) `sm` and `md` grow to a 44 px hit area through an invisible extension. The `outline` variant's extension adds its 1.5 px border back, so it reaches 44 px too.
169
+
170
+ ```ts
171
+ import { Component } from '@angular/core';
172
+ import { BfButtonComponent } from '@bitforgehq/ui';
173
+
174
+ /** The sm, md and lg button sizes. */
175
+ @Component({
176
+ selector: 'app-example-button-sizes',
177
+ imports: [BfButtonComponent],
178
+ template: `
179
+ <div class="flex flex-wrap items-center gap-3">
180
+ <bf-button size="sm">Small</bf-button>
181
+ <bf-button>Medium</bf-button>
182
+ <bf-button size="lg">Large</bf-button>
183
+ </div>
184
+ `,
185
+ })
186
+ export class ButtonSizesExampleComponent {}
187
+ ```
188
+
189
+ ## Icons
190
+
191
+ Icons go into the `icon-start` or `icon-end` slot so spacing stays consistent.
192
+
193
+ ```ts
194
+ import { Component } from '@angular/core';
195
+ import { BfButtonComponent } from '@bitforgehq/ui';
196
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
197
+ import { Plus } from 'lucide';
198
+
199
+ /** A button with an icon in its icon-start slot. */
200
+ @Component({
201
+ selector: 'app-example-button-icons',
202
+ imports: [BfButtonComponent, BfIconComponent],
203
+ template: `
204
+ <bf-button variant="tonal">
205
+ <bf-icon bfSlot="icon-start" [icon]="plus" />
206
+ New task
207
+ </bf-button>
208
+ `,
209
+ })
210
+ export class ButtonIconsExampleComponent {
211
+ protected readonly plus = Plus;
212
+ }
213
+ ```
214
+
215
+ ## Loading and disabled
216
+
217
+ `loading` swaps the start icon for a spinner and blocks activation, but keeps the variant fill and keyboard focus: the button reports `aria-disabled` and `aria-busy` instead of turning natively disabled, and a click on it is cancelled (so a `type="submit"` button cannot submit twice). `disabled` disables it outright. Neither emits `clicked`.
218
+
219
+ ```ts
220
+ import { Component, DestroyRef, inject, signal } from '@angular/core';
221
+ import { BfButtonComponent } from '@bitforgehq/ui';
222
+
223
+ /**
224
+ * A loading button and a disabled one.
225
+ *
226
+ * Save shows its spinner for a second and stays focusable meanwhile; the disabled button is
227
+ * natively disabled.
228
+ */
229
+ @Component({
230
+ selector: 'app-example-button-loading',
231
+ imports: [BfButtonComponent],
232
+ template: `
233
+ <div class="flex flex-wrap items-center gap-3">
234
+ <bf-button [loading]="saving()" (clicked)="save()">Save</bf-button>
235
+ <bf-button [disabled]="true">Unavailable</bf-button>
236
+ </div>
237
+ `,
238
+ })
239
+ export class ButtonLoadingExampleComponent {
240
+ protected readonly saving = signal(false);
241
+ private timer: ReturnType<typeof setTimeout> | undefined;
242
+
243
+ constructor() {
244
+ inject(DestroyRef).onDestroy(() => clearTimeout(this.timer));
245
+ }
246
+
247
+ protected save(): void {
248
+ this.saving.set(true);
249
+ this.timer = setTimeout(() => this.saving.set(false), 1000);
250
+ }
251
+ }
252
+ ```
253
+
254
+ ## Full width
255
+
256
+ ```ts
257
+ import { Component } from '@angular/core';
258
+ import { BfButtonComponent } from '@bitforgehq/ui';
259
+
260
+ /** A button that fills its container. */
261
+ @Component({
262
+ selector: 'app-example-button-full-width',
263
+ imports: [BfButtonComponent],
264
+ template: `<bf-button [fullWidth]="true">Continue</bf-button>`,
265
+ })
266
+ export class ButtonFullWidthExampleComponent {}
267
+ ```
268
+
269
+ ## As a link
270
+
271
+ Navigation is a link, not a button: put `bfButton` on your own `<a>` and give it `href` or `routerLink`. The `<a>` itself takes the button's variant, size and full-width classes (no inner `<button>`, no underline), so it looks exactly like the button form, while the browser treats it as a link: Enter follows it (Space does not), Ctrl/Cmd-click and middle-click open it in a new tab, and the URL shows on hover and can be copied. The widget stays router-free; `clicked` still emits on every click. The underline is removed with an important utility, so an unlayered global rule of the app (`a { text-decoration: underline }`) cannot bring it back; a global unlayered link colour (`a:visited { color: … }`) can still win over the variant's text colour, so scope such rules away from buttons (`a:not([bfButton])`). `type`, `disabled` and `loading` do not apply: a link is never disabled, so render a disabled `bf-button` instead while the destination is unavailable.
272
+
273
+ ```ts
274
+ import { Component } from '@angular/core';
275
+ import { RouterLink } from '@angular/router';
276
+ import { BfButtonComponent } from '@bitforgehq/ui';
277
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
278
+ import { ExternalLink } from 'lucide';
279
+
280
+ /**
281
+ * Links that look like buttons.
282
+ *
283
+ * `bfButton` on your own `<a>`: a router link and an external link with an icon-end.
284
+ */
285
+ @Component({
286
+ selector: 'app-example-button-link',
287
+ imports: [RouterLink, BfButtonComponent, BfIconComponent],
288
+ template: `
289
+ <div class="flex flex-wrap items-center gap-3">
290
+ <a bfButton variant="outline" routerLink="/components">All components</a>
291
+ <a bfButton variant="text" size="sm" href="https://bitforge.ch">
292
+ Bitforge
293
+ <bf-icon bfSlot="icon-end" [icon]="externalLink" />
294
+ </a>
295
+ </div>
296
+ `,
297
+ })
298
+ export class ButtonLinkExampleComponent {
299
+ protected readonly externalLink = ExternalLink;
300
+ }
301
+ ```
302
+
303
+ ## Desktop
304
+
305
+ Hover darkens the fill (`primary`, `tonal`, `danger`), the border (`outline`) or the text (`text`), with the pointer cursor; a disabled button shows `not-allowed`. Hover styles apply only on devices that can hover, so a tap never leaves one stuck. The focus ring shows on keyboard focus only (`focus-visible`), never after a mouse click. Under a fine pointer the visible box is the hit area, so `sm` (32 px) buttons sit side by side in toolbars and table rows without overlapping hit areas. Desktop density is the default `md` step; `provideBfDefaults({ size: 'sm' })`, or `{ size: 'sm' }` in a component's `providers`, makes a dense toolbar, table or sidebar ([density guide](https://bitforge-ui.web.app/density)).
306
+
307
+ ## Theming
308
+
309
+ Consumes tokens by variant, rest → hover:
310
+
311
+ - `primary`: `primary` / `on-primary` → `primary-hover`
312
+ - `tonal`: `primary-soft` / `primary-strong` → `primary-soft-hover`
313
+ - `outline`: `surface` / `text` / `border-strong` (the control boundary, ≥ 3:1) → `text-muted` border; focus swaps the border to `primary`
314
+ - `text`: `primary-text` (primary as text, ≥ 4.5:1 on `bg` and `surface`) → `primary-strong`
315
+ - `danger`: `danger-soft` / `danger` → `danger` / `danger-soft`
316
+
317
+ Pressed (while the mouse button, a touch or the Space key holds it down): `primary` →
318
+ `primary-pressed`; `tonal`, `outline` and `text` → `primary-soft-pressed` (the `text` label turns
319
+ `primary-strong`); `danger` → `danger-pressed` with `danger-soft` text. `a[bfButton]` presses the
320
+ same way. A disabled or loading button has no pressed state, and under forced colours the system
321
+ colours win.
322
+
323
+ Enabled buttons show a pointer cursor. Disabled (any variant): `surface-sunken` / `text-disabled`, border turns transparent (never opacity), `not-allowed` cursor. Focus ring: `ring` (≥ 3:1 on `bg` and `surface`). Shape: `rounded-control` (`--radius-control`, `9999px` by default, so a pill); override the token to move every pill control off the pill at once.
324
+
325
+ Icon and label sit 8 px apart. A label longer than its container truncates with an ellipsis; the icons keep their size.
326
+
327
+ ## Accessibility
328
+
329
+ `bf-button` renders a native `<button>`; its `type` reflects the `type` input. Native `disabled` is set only by `disabled`. While `loading`, the button stays focusable and in the tab order with `aria-disabled="true"` and `aria-busy="true"`, so focus does not fall to `<body>` when an action starts; a click or Enter/Space is cancelled (`preventDefault`) and emits nothing. The spinner inside is `decorative`, so it adds no second status announcement; announce the outcome in your own message. Being a real button, it gets keyboard activation (Enter/Space) and the button role for free — no ARIA role is added. The focus-visible ring uses the `ring` token.
330
+
331
+ `a[bfButton]` is your own `<a>`, so it keeps the link role and link keyboard behaviour ([WAI-ARIA APG link pattern](https://www.w3.org/WAI/ARIA/apg/patterns/link/)): it is exposed as `link`, is in the tab order while it has an `href` (in Safari and iOS WKWebView, Tab reaches links only when the user turns on "Press Tab to highlight each item" (Option+Tab otherwise); VoiceOver and Full Keyboard Access are unaffected), activates with Enter only, and opens in a new tab with Ctrl/Cmd-click. Use it whenever activation navigates to a URL, and `bf-button` whenever it performs an action on the page ([APG button pattern](https://www.w3.org/WAI/ARIA/apg/patterns/button/)). No ARIA is added, and none of `disabled`, `loading` or `type` applies.
332
+
333
+ ### Keyboard
334
+
335
+ | Key | Action |
336
+ | ----------------------------------- | ---------------------------------------------------------------------------------------------- |
337
+ | <kbd>Tab</kbd> | Moves focus to the button; a `loading` one stays in the tab order, a `disabled` one is skipped |
338
+ | <kbd>Enter</kbd> / <kbd>Space</kbd> | `bf-button`: activates it and emits `clicked`; nothing while `loading` |
339
+ | <kbd>Enter</kbd> | `a[bfButton]`: follows the link; <kbd>Space</kbd> does not |
340
+
341
+ ## Inputs
342
+
343
+ | Name | Type | Default | Description |
344
+ | ----------- | ----------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
345
+ | `variant` | `BfButtonVariant` (`'primary' \| 'tonal' \| 'outline' \| 'text' \| 'danger'`) | `'primary'` | Visual style: primary (filled), tonal (soft tint), outline, text, danger. |
346
+ | `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Height step: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()` (`controlSize`, `md` by default). |
347
+ | `type` | `BfButtonType` (`'button' \| 'submit' \| 'reset'`) | `'button'` | Native button type. Use `submit` inside a form. Ignored on `a[bfButton]`. |
348
+ | `disabled` | `boolean` | `false` | Disables the control and swallows clicks. Ignored on `a[bfButton]`: a link is never disabled; render a disabled `bf-button` instead. |
349
+ | `loading` | `boolean` | `false` | Shows a spinner instead of the start icon and blocks activation. The button stays focusable (`aria-disabled` and `aria-busy`, never native `disabled`) and keeps its variant fill. Ignored on `a[bfButton]`. |
350
+ | `fullWidth` | `boolean` | `false` | Stretches the button to its container's width. |
351
+
352
+ ## Outputs
353
+
354
+ | Name | Payload | Description |
355
+ | --------- | ------- | ------------------------------------------------------------------------------------------------------------ |
356
+ | `clicked` | `Event` | Emitted on click while enabled and not loading; on `a[bfButton]`, on every click (the link still navigates). |
357
+
358
+ ## Slots
359
+
360
+ - `icon-start`: An icon before the label.
361
+ - `default`: The label.
362
+ - `icon-end`: An icon after the label.
363
+
364
+ ## Notes
365
+
366
+ ### Do
367
+
368
+ - Use `primary` once per screen for the main action; `tonal` for secondary actions next to it; `text` for low-emphasis actions inside cards and lists.
369
+ - Bind `[loading]` to the pending state of the action so the user gets feedback and cannot double-submit.
370
+ - Use `type="submit"` inside a `<form>` so Enter submits.
371
+
372
+ ### Don't
373
+
374
+ - Don't nest `bf-button` in an `<a>` or another button, and don't navigate from `(clicked)`; put `bfButton` on the `<a>` itself (`<a bfButton routerLink="…">`).
375
+ - Don't disable a link; render a disabled `bf-button` while the destination is unavailable.
376
+ - Don't add `dark:` classes or colors on the element; the theme handles both.
377
+ - Don't put a `bf-icon` in the default slot; use the `icon-start` / `icon-end` slots so spacing stays right.
378
+ - Don't rely on truncation for long labels (German "Einstellungen speichern" in a narrow row): shorten the label or give the button the room; the ellipsis is a safety net.
379
+
380
+ ## Migration notes
381
+
382
+ ### 0.3.0
383
+
384
+ **Behaviour:** `size` is a `BfSize` and, unset, follows `provideBfDefaults()`'s `controlSize`
385
+ (`md` by default, as before). The 44 px hit extension of `sm` and `md` applies only under a coarse
386
+ pointer (`pointer-coarse:`), so dense desktop rows do not overlap; the `outline` variant's
387
+ extension adds its 1.5 px border back and reaches 44 px (was 42). See Migration 5 for
388
+ `BfButtonSize`.
389
+
390
+ New link form: the selector is `bf-button, a[bfButton]`. `<a bfButton routerLink="…">` (or
391
+ `href`) puts the variant, size and full-width classes on your own `<a>` (plus an important
392
+ `no-underline!`, so an unlayered global link rule cannot underline it; no
393
+ inner `<button>`), so a navigation action is a real link that looks like the button: exposed as
394
+ `link`, activated with Enter (not Space), opened in a new tab with Ctrl/Cmd-click, the URL
395
+ visible. `clicked` still emits; `type`, `disabled` and `loading` do not apply (a link is never
396
+ disabled; render a disabled `bf-button` instead). Additive. The host of `bf-button` now binds its
397
+ classes through `[class]`; a consumer's own `class` on the element is kept.
398
+
399
+ **Behaviour:** the `text` variant is coloured `primary-text` (new token, ≥ 4.5:1 on `bg` and
400
+ `surface`; was `primary`), still `primary-strong` on hover. The `danger` variant's hover label is
401
+ `danger-soft` on `danger` (was `on-primary`, which a custom light primary can turn black, 2.7:1 on
402
+ the default danger).
403
+
404
+ **Behaviour:** shaped with the new `rounded-control` (`--radius-control`, `9999px` by default, so
405
+ it renders as before); override the token to move off the pill.
406
+
407
+ JSDoc of `button.component.ts` for the API reference: `@summary`/`@since` on the exported symbols;
408
+ `@slot` docs for `icon-start`, `default`, `icon-end`; template handlers and framework hooks marked
409
+ `@internal`, so the reference leaves them out. No behaviour change.
410
+
411
+ **Behaviour:** a pressed state (F-045): `primary` takes `primary-pressed`, `tonal`, `outline` and `text` `primary-soft-pressed` (the `text` label turns `primary-strong`), `danger` `danger-pressed` with `danger-soft` text, while the button is held down (`enabled:active:`, and `active:` on `a[bfButton]`). None while disabled or loading; under forced colours the system colours win.
412
+
413
+ ### 0.2.0
414
+
415
+ The loading spinner is `md` (20 px), the size of a default `bf-icon`, so swapping the start icon
416
+ for the spinner no longer changes the button's width. The `outline` border is `border-strong`
417
+ (hover `text-muted`). The label renders in a `data-label` span that truncates with an ellipsis
418
+ (`max-w-full min-w-0 truncate`) when the container is narrower than the label; the icons keep
419
+ their size, and the host and button are capped at `max-w-full`.
420
+
421
+ Fixed: the enabled button shows a pointer cursor (`cursor-pointer`; Tailwind's preflight gave
422
+ `<button>` the default arrow), `not-allowed` when disabled.
423
+
424
+ Behaviour change: `loading` no longer sets native `disabled`, so keyboard focus stays on the button
425
+ when an action starts (it fell to `<body>` in Chromium and WebKit). While loading the button carries
426
+ `aria-disabled="true"` and `aria-busy="true"`, stays in the tab order, cancels a click or Enter/Space
427
+ with `preventDefault()` (a `type="submit"` button no longer submits its form) and emits nothing. It
428
+ keeps its variant fill instead of the disabled grey, with a `cursor-progress` cursor (a new `STATE`
429
+ class map: `idle` `cursor-pointer`, `loading` `cursor-progress`). The spinner inside is
430
+ `decorative`, so it adds no second status announcement. Consumers styling `:disabled` on a loading
431
+ button see the change.