@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,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.
|