@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,661 @@
|
|
|
1
|
+
# bf-menu
|
|
2
|
+
|
|
3
|
+
Keyboard-operable menu panel with items, checkbox and radio items, groups and submenus
|
|
4
|
+
|
|
5
|
+
`BfMenuComponent` from `@bitforgehq/ui/overlay` · Overlays · since 0.3.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, signal } from '@angular/core';
|
|
11
|
+
import { RouterLink } from '@angular/router';
|
|
12
|
+
import {
|
|
13
|
+
BfMenuButtonComponent,
|
|
14
|
+
BfMenuComponent,
|
|
15
|
+
BfMenuDividerDirective,
|
|
16
|
+
BfMenuGroupDirective,
|
|
17
|
+
BfMenuItemCheckboxDirective,
|
|
18
|
+
BfMenuItemDirective,
|
|
19
|
+
BfMenuItemRadioDirective,
|
|
20
|
+
BfMenuTriggerDirective,
|
|
21
|
+
} from '@bitforgehq/ui/overlay';
|
|
22
|
+
|
|
23
|
+
type Density = 'compact' | 'comfortable';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The bf-menu overview.
|
|
27
|
+
*
|
|
28
|
+
* Live demo of `bf-menu`: row actions with a "Move to" submenu, a disabled item, a link item that
|
|
29
|
+
* navigates to the `bf-menu-button` page (closing the menu) and a destructive item after a divider, then a view menu with a "Show grid" checkbox item and a density radio
|
|
30
|
+
* group; the result line reports each choice. The triggers and the result line carry
|
|
31
|
+
* `data-testid`s for the end-to-end suite.
|
|
32
|
+
*/
|
|
33
|
+
@Component({
|
|
34
|
+
selector: 'app-example-menu-overview',
|
|
35
|
+
imports: [
|
|
36
|
+
BfMenuButtonComponent,
|
|
37
|
+
BfMenuComponent,
|
|
38
|
+
BfMenuDividerDirective,
|
|
39
|
+
BfMenuGroupDirective,
|
|
40
|
+
BfMenuItemCheckboxDirective,
|
|
41
|
+
BfMenuItemDirective,
|
|
42
|
+
BfMenuItemRadioDirective,
|
|
43
|
+
BfMenuTriggerDirective,
|
|
44
|
+
RouterLink,
|
|
45
|
+
],
|
|
46
|
+
template: `
|
|
47
|
+
<div class="flex flex-col gap-4">
|
|
48
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
49
|
+
<bf-menu-button data-testid="menu-actions" [menu]="rowActions">Actions</bf-menu-button>
|
|
50
|
+
<bf-menu-button data-testid="menu-view" [menu]="view" variant="text">View</bf-menu-button>
|
|
51
|
+
</div>
|
|
52
|
+
<p
|
|
53
|
+
data-status
|
|
54
|
+
data-testid="menu-result"
|
|
55
|
+
class="text-caption text-text-muted"
|
|
56
|
+
aria-live="polite"
|
|
57
|
+
>
|
|
58
|
+
{{ result() }}
|
|
59
|
+
</p>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<ng-template #rowActions>
|
|
63
|
+
<bf-menu>
|
|
64
|
+
<button bfMenuItem (triggered)="result.set('Renamed Atlas rebrand.')">Rename</button>
|
|
65
|
+
<button bfMenuItem (triggered)="result.set('Duplicated Atlas rebrand.')">Duplicate</button>
|
|
66
|
+
<button bfMenuItem [bfMenuTriggerFor]="moveTo">Move to</button>
|
|
67
|
+
<button bfMenuItem bfMenuItemDisabled>Share</button>
|
|
68
|
+
<a bfMenuItem routerLink="/components/menu-button">Open the menu button page</a>
|
|
69
|
+
<div bfMenuDivider></div>
|
|
70
|
+
<button bfMenuItem (triggered)="result.set('Deleted (not really, this is a demo).')">
|
|
71
|
+
Delete
|
|
72
|
+
</button>
|
|
73
|
+
</bf-menu>
|
|
74
|
+
</ng-template>
|
|
75
|
+
|
|
76
|
+
<ng-template #moveTo>
|
|
77
|
+
<bf-menu>
|
|
78
|
+
<button bfMenuItem (triggered)="result.set('Moved to Inbox.')">Inbox</button>
|
|
79
|
+
<button bfMenuItem (triggered)="result.set('Moved to In progress.')">In progress</button>
|
|
80
|
+
<button bfMenuItem (triggered)="result.set('Moved to Done.')">Done</button>
|
|
81
|
+
</bf-menu>
|
|
82
|
+
</ng-template>
|
|
83
|
+
|
|
84
|
+
<ng-template #view>
|
|
85
|
+
<bf-menu>
|
|
86
|
+
<button bfMenuItemCheckbox [(checked)]="showGrid" (changed)="reportGrid($event)">
|
|
87
|
+
Show grid
|
|
88
|
+
</button>
|
|
89
|
+
<div bfMenuDivider></div>
|
|
90
|
+
<div bfMenuGroup aria-label="Density">
|
|
91
|
+
<button
|
|
92
|
+
bfMenuItemRadio
|
|
93
|
+
[checked]="density() === 'compact'"
|
|
94
|
+
(triggered)="setDensity('compact')"
|
|
95
|
+
>
|
|
96
|
+
Compact
|
|
97
|
+
</button>
|
|
98
|
+
<button
|
|
99
|
+
bfMenuItemRadio
|
|
100
|
+
[checked]="density() === 'comfortable'"
|
|
101
|
+
(triggered)="setDensity('comfortable')"
|
|
102
|
+
>
|
|
103
|
+
Comfortable
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
</bf-menu>
|
|
107
|
+
</ng-template>
|
|
108
|
+
`,
|
|
109
|
+
})
|
|
110
|
+
export class MenuOverviewExampleComponent {
|
|
111
|
+
readonly result = signal('');
|
|
112
|
+
readonly showGrid = signal(false);
|
|
113
|
+
readonly density = signal<Density>('compact');
|
|
114
|
+
|
|
115
|
+
reportGrid(shown: boolean): void {
|
|
116
|
+
this.result.set(shown ? 'Grid shown.' : 'Grid hidden.');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
setDensity(density: Density): void {
|
|
120
|
+
this.density.set(density);
|
|
121
|
+
this.result.set(density === 'compact' ? 'Compact rows.' : 'Comfortable rows.');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Overview
|
|
127
|
+
|
|
128
|
+
Menu panel of actions, choices and submenus on the Angular CDK menu (role `menu`, arrow keys,
|
|
129
|
+
Home/End, type-ahead, `Escape` returning focus to the trigger). Put it in an `ng-template` that
|
|
130
|
+
a `bf-menu-button` or a `[bfMenuTriggerFor]` opens, and fill it with `bfMenuItem`,
|
|
131
|
+
`bfMenuItemCheckbox`, `bfMenuItemRadio` (in a `bfMenuGroup`) and `bfMenuDivider` parts.
|
|
132
|
+
|
|
133
|
+
Menus of actions and choices for desktop and web apps, from `@bitforgehq/ui/overlay` (needs
|
|
134
|
+
`@angular/cdk`, no Capacitor). A `bf-menu` is the panel; put it in an `ng-template` and open it
|
|
135
|
+
from a `bf-menu-button` or from any element with `[bfMenuTriggerFor]`. Fill it with item parts:
|
|
136
|
+
`bfMenuItem` (an action or a link), `bfMenuItemCheckbox` (a toggle), `bfMenuItemRadio` (one
|
|
137
|
+
choice of a set, inside a `bfMenuGroup`) and `bfMenuDivider`. The menu is built on the Angular
|
|
138
|
+
CDK menu, which provides the roles and the keyboard behaviour of the
|
|
139
|
+
[APG menu button](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/) pattern. Menus open in
|
|
140
|
+
the CDK overlay container, so they work inside a `BfDialogService` dialog.
|
|
141
|
+
|
|
142
|
+
Import the parts you use from `@bitforgehq/ui/overlay`: `BfMenuButtonComponent`, `BfMenuComponent`,
|
|
143
|
+
`BfMenuDividerDirective`, `BfMenuGroupDirective`, `BfMenuItemCheckboxDirective`,
|
|
144
|
+
`BfMenuItemDirective`, `BfMenuItemRadioDirective` and `BfMenuTriggerDirective`.
|
|
145
|
+
|
|
146
|
+
## When to use
|
|
147
|
+
|
|
148
|
+
Use `bf-menu` for a short list of actions or view settings that opens from a button: Rename, Move to, Delete; Show grid, Density. Use [bf-select](https://bitforge-ui.web.app/components/select) instead to choose a form value, [BfDialogService](https://bitforge-ui.web.app/api/bf-dialog-service) when the choice needs fields or explanation, links in a `nav` for site navigation, and [BfActionSheetService](https://bitforge-ui.web.app/api/bf-action-sheet-service) in a Capacitor app.
|
|
149
|
+
|
|
150
|
+
## Basic usage
|
|
151
|
+
|
|
152
|
+
Items are native `<button>` elements. `triggered` fires when one is activated with a click,
|
|
153
|
+
`Enter` or `Space`; the menu then closes and focus returns to the button that opened it.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { Component, signal } from '@angular/core';
|
|
157
|
+
import {
|
|
158
|
+
BfMenuButtonComponent,
|
|
159
|
+
BfMenuComponent,
|
|
160
|
+
BfMenuItemDirective,
|
|
161
|
+
} from '@bitforgehq/ui/overlay';
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Row actions in a menu.
|
|
165
|
+
*
|
|
166
|
+
* A `bf-menu-button` opens the menu; each item reports its `triggered`, and Archive is disabled.
|
|
167
|
+
*/
|
|
168
|
+
@Component({
|
|
169
|
+
selector: 'app-example-menu-basic',
|
|
170
|
+
imports: [BfMenuButtonComponent, BfMenuComponent, BfMenuItemDirective],
|
|
171
|
+
template: `
|
|
172
|
+
<bf-menu-button [menu]="rowActions">Actions</bf-menu-button>
|
|
173
|
+
<p aria-live="polite" class="text-caption text-text-muted mt-3">{{ result() }}</p>
|
|
174
|
+
|
|
175
|
+
<ng-template #rowActions>
|
|
176
|
+
<bf-menu>
|
|
177
|
+
<button bfMenuItem (triggered)="result.set('Renamed.')">Rename</button>
|
|
178
|
+
<button bfMenuItem (triggered)="result.set('Duplicated.')">Duplicate</button>
|
|
179
|
+
<button bfMenuItem bfMenuItemDisabled>Archive</button>
|
|
180
|
+
</bf-menu>
|
|
181
|
+
</ng-template>
|
|
182
|
+
`,
|
|
183
|
+
})
|
|
184
|
+
export class MenuBasicExampleComponent {
|
|
185
|
+
protected readonly result = signal('');
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`bfMenuItemDisabled` disables an item. It stays focusable and is announced as disabled
|
|
190
|
+
(`aria-disabled`), but it does not activate. Put icons inside the item before the label; the
|
|
191
|
+
item lays them out with a 12 px gap. `bfMenuItemTypeahead` sets the text that type-ahead matches
|
|
192
|
+
when the label starts with something else (an emoji, a count).
|
|
193
|
+
|
|
194
|
+
Any element can open a menu with `[bfMenuTriggerFor]`, for example your own toolbar button.
|
|
195
|
+
`(bfMenuOpened)` and `(bfMenuClosed)` report its state, and `bfMenuTriggerData` passes a context
|
|
196
|
+
to the template.
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
import { Component, signal } from '@angular/core';
|
|
200
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
201
|
+
import {
|
|
202
|
+
BfMenuComponent,
|
|
203
|
+
BfMenuItemDirective,
|
|
204
|
+
BfMenuTriggerDirective,
|
|
205
|
+
} from '@bitforgehq/ui/overlay';
|
|
206
|
+
import { Settings2 } from 'lucide';
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* A menu opened from your own button.
|
|
210
|
+
*
|
|
211
|
+
* `[bfMenuTriggerFor]` turns any element into the trigger; `bfMenuClosed` reports when the menu closes.
|
|
212
|
+
*/
|
|
213
|
+
@Component({
|
|
214
|
+
selector: 'app-example-menu-trigger',
|
|
215
|
+
imports: [BfIconComponent, BfMenuComponent, BfMenuItemDirective, BfMenuTriggerDirective],
|
|
216
|
+
template: `
|
|
217
|
+
<button
|
|
218
|
+
type="button"
|
|
219
|
+
class="border-border-strong text-text rounded-control inline-flex h-10 items-center gap-2 border px-4"
|
|
220
|
+
[bfMenuTriggerFor]="options"
|
|
221
|
+
(bfMenuClosed)="result.set('Layout saved.')"
|
|
222
|
+
>
|
|
223
|
+
<bf-icon [icon]="settings" size="sm" />
|
|
224
|
+
Options
|
|
225
|
+
</button>
|
|
226
|
+
<p aria-live="polite" class="text-caption text-text-muted mt-3">{{ result() }}</p>
|
|
227
|
+
|
|
228
|
+
<ng-template #options>
|
|
229
|
+
<bf-menu>
|
|
230
|
+
<button bfMenuItem (triggered)="result.set('Pinned.')">Pin</button>
|
|
231
|
+
<button bfMenuItem (triggered)="result.set('Hidden.')">Hide</button>
|
|
232
|
+
</bf-menu>
|
|
233
|
+
</ng-template>
|
|
234
|
+
`,
|
|
235
|
+
})
|
|
236
|
+
export class MenuTriggerExampleComponent {
|
|
237
|
+
protected readonly settings = Settings2;
|
|
238
|
+
protected readonly result = signal('');
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Groups and dividers
|
|
243
|
+
|
|
244
|
+
`bfMenuDivider` draws a line between groups of items (role `separator`, skipped by the arrow
|
|
245
|
+
keys). `bfMenuGroup` groups items (role `group`). Name a group with `aria-label` when its items
|
|
246
|
+
share a topic.
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import { Component, signal } from '@angular/core';
|
|
250
|
+
import {
|
|
251
|
+
BfMenuButtonComponent,
|
|
252
|
+
BfMenuComponent,
|
|
253
|
+
BfMenuDividerDirective,
|
|
254
|
+
BfMenuItemDirective,
|
|
255
|
+
} from '@bitforgehq/ui/overlay';
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* A menu with a divider before the destructive item.
|
|
259
|
+
*
|
|
260
|
+
* Items in reading order: the clipboard actions, a `bfMenuDivider`, then Delete.
|
|
261
|
+
*/
|
|
262
|
+
@Component({
|
|
263
|
+
selector: 'app-example-menu-groups',
|
|
264
|
+
imports: [BfMenuButtonComponent, BfMenuComponent, BfMenuDividerDirective, BfMenuItemDirective],
|
|
265
|
+
template: `
|
|
266
|
+
<bf-menu-button [menu]="edit" variant="text">Edit</bf-menu-button>
|
|
267
|
+
<p aria-live="polite" class="text-caption text-text-muted mt-3">{{ result() }}</p>
|
|
268
|
+
|
|
269
|
+
<ng-template #edit>
|
|
270
|
+
<bf-menu>
|
|
271
|
+
<button bfMenuItem (triggered)="result.set('Cut.')">Cut</button>
|
|
272
|
+
<button bfMenuItem (triggered)="result.set('Copied.')">Copy</button>
|
|
273
|
+
<div bfMenuDivider></div>
|
|
274
|
+
<button bfMenuItem (triggered)="result.set('Deleted.')">Delete</button>
|
|
275
|
+
</bf-menu>
|
|
276
|
+
</ng-template>
|
|
277
|
+
`,
|
|
278
|
+
})
|
|
279
|
+
export class MenuGroupsExampleComponent {
|
|
280
|
+
protected readonly result = signal('');
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Checkbox and radio items
|
|
285
|
+
|
|
286
|
+
`bfMenuItemCheckbox` is a toggle (role `menuitemcheckbox`) with a check mark while `checked`.
|
|
287
|
+
`bfMenuItemRadio` is one choice of a set (role `menuitemradio`) with a dot while `checked`. Radio
|
|
288
|
+
items in the same `bfMenuGroup` are exclusive. Activating either one closes the menu.
|
|
289
|
+
|
|
290
|
+
A checkbox item flips its own `checked` when activated and emits `changed` (and the model's
|
|
291
|
+
`checkedChange`) with the new state, so bind it two-way, `[(checked)]="showGrid"`, and react in
|
|
292
|
+
`(changed)`. With a one-way `[checked]` binding, always
|
|
293
|
+
update your state in `(triggered)`: if you skip the update (a guarded toggle), the item's
|
|
294
|
+
`aria-checked` no longer matches your state. For radio items, bind `[checked]` and set your state
|
|
295
|
+
in `(triggered)`.
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
import { Component, signal } from '@angular/core';
|
|
299
|
+
import {
|
|
300
|
+
BfMenuButtonComponent,
|
|
301
|
+
BfMenuComponent,
|
|
302
|
+
BfMenuDividerDirective,
|
|
303
|
+
BfMenuGroupDirective,
|
|
304
|
+
BfMenuItemCheckboxDirective,
|
|
305
|
+
BfMenuItemRadioDirective,
|
|
306
|
+
} from '@bitforgehq/ui/overlay';
|
|
307
|
+
|
|
308
|
+
type Density = 'compact' | 'comfortable';
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Checkbox and radio items.
|
|
312
|
+
*
|
|
313
|
+
* "Show grid" is a two-way bound checkbox item; the density radio items share one `bfMenuGroup`.
|
|
314
|
+
*/
|
|
315
|
+
@Component({
|
|
316
|
+
selector: 'app-example-menu-checkable',
|
|
317
|
+
imports: [
|
|
318
|
+
BfMenuButtonComponent,
|
|
319
|
+
BfMenuComponent,
|
|
320
|
+
BfMenuDividerDirective,
|
|
321
|
+
BfMenuGroupDirective,
|
|
322
|
+
BfMenuItemCheckboxDirective,
|
|
323
|
+
BfMenuItemRadioDirective,
|
|
324
|
+
],
|
|
325
|
+
template: `
|
|
326
|
+
<bf-menu-button [menu]="view" variant="text">View</bf-menu-button>
|
|
327
|
+
<p class="text-caption text-text-muted mt-3">
|
|
328
|
+
Grid {{ showGrid() ? 'on' : 'off' }} · {{ density() }}
|
|
329
|
+
</p>
|
|
330
|
+
|
|
331
|
+
<ng-template #view>
|
|
332
|
+
<bf-menu>
|
|
333
|
+
<button bfMenuItemCheckbox [(checked)]="showGrid">Show grid</button>
|
|
334
|
+
<div bfMenuDivider></div>
|
|
335
|
+
<div bfMenuGroup aria-label="Density">
|
|
336
|
+
<button
|
|
337
|
+
bfMenuItemRadio
|
|
338
|
+
[checked]="density() === 'compact'"
|
|
339
|
+
(triggered)="density.set('compact')"
|
|
340
|
+
>
|
|
341
|
+
Compact
|
|
342
|
+
</button>
|
|
343
|
+
<button
|
|
344
|
+
bfMenuItemRadio
|
|
345
|
+
[checked]="density() === 'comfortable'"
|
|
346
|
+
(triggered)="density.set('comfortable')"
|
|
347
|
+
>
|
|
348
|
+
Comfortable
|
|
349
|
+
</button>
|
|
350
|
+
</div>
|
|
351
|
+
</bf-menu>
|
|
352
|
+
</ng-template>
|
|
353
|
+
`,
|
|
354
|
+
})
|
|
355
|
+
export class MenuCheckableExampleComponent {
|
|
356
|
+
protected readonly showGrid = signal(true);
|
|
357
|
+
protected readonly density = signal<Density>('comfortable');
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Submenus
|
|
362
|
+
|
|
363
|
+
Put `[bfMenuTriggerFor]` on a `bfMenuItem` to open another `bf-menu` from it. The item shows a
|
|
364
|
+
chevron at the inline end and gets `aria-haspopup="menu"` and `aria-expanded`. The submenu opens
|
|
365
|
+
beside the item, at its inline end (to the left in right-to-left pages), and flips when there is
|
|
366
|
+
no room. `ArrowRight` (`ArrowLeft` in RTL), `Enter`, `Space` or the pointer open it. `ArrowLeft`
|
|
367
|
+
(`ArrowRight` in RTL) or `Escape` close it and put focus back on the item. Activating an item in
|
|
368
|
+
a submenu closes every menu.
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
import { Component, signal } from '@angular/core';
|
|
372
|
+
import {
|
|
373
|
+
BfMenuButtonComponent,
|
|
374
|
+
BfMenuComponent,
|
|
375
|
+
BfMenuItemDirective,
|
|
376
|
+
BfMenuTriggerDirective,
|
|
377
|
+
} from '@bitforgehq/ui/overlay';
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* A menu item that opens a submenu.
|
|
381
|
+
*
|
|
382
|
+
* An item with `[bfMenuTriggerFor]` opens the next menu beside it (ArrowRight opens it too).
|
|
383
|
+
*/
|
|
384
|
+
@Component({
|
|
385
|
+
selector: 'app-example-menu-submenus',
|
|
386
|
+
imports: [BfMenuButtonComponent, BfMenuComponent, BfMenuItemDirective, BfMenuTriggerDirective],
|
|
387
|
+
template: `
|
|
388
|
+
<bf-menu-button [menu]="rowActions">Actions</bf-menu-button>
|
|
389
|
+
<p aria-live="polite" class="text-caption text-text-muted mt-3">{{ result() }}</p>
|
|
390
|
+
|
|
391
|
+
<ng-template #rowActions>
|
|
392
|
+
<bf-menu>
|
|
393
|
+
<button bfMenuItem (triggered)="result.set('Renamed.')">Rename</button>
|
|
394
|
+
<button bfMenuItem [bfMenuTriggerFor]="moveTo">Move to</button>
|
|
395
|
+
</bf-menu>
|
|
396
|
+
</ng-template>
|
|
397
|
+
|
|
398
|
+
<ng-template #moveTo>
|
|
399
|
+
<bf-menu>
|
|
400
|
+
<button bfMenuItem (triggered)="result.set('Moved to Inbox.')">Inbox</button>
|
|
401
|
+
<button bfMenuItem (triggered)="result.set('Moved to Archive.')">Archive</button>
|
|
402
|
+
</bf-menu>
|
|
403
|
+
</ng-template>
|
|
404
|
+
`,
|
|
405
|
+
})
|
|
406
|
+
export class MenuSubmenusExampleComponent {
|
|
407
|
+
protected readonly result = signal('');
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Links as items
|
|
412
|
+
|
|
413
|
+
A navigation item is a real link: put `bfMenuItem` on an `<a>` with `href`, or with your app's
|
|
414
|
+
`routerLink` (the library itself does not depend on the router). It keeps its link semantics for
|
|
415
|
+
the browser (open in a new tab, the status bar URL) and gets role `menuitem`. `Enter` and a click
|
|
416
|
+
follow it, and so does `Space`, which a plain link ignores. Activating an item closes the menu;
|
|
417
|
+
any navigation to another URL path (a link item, the browser's back button, a redirect) closes an
|
|
418
|
+
open menu too, while a query- or fragment-only change leaves it open. A disabled link item
|
|
419
|
+
(`bfMenuItemDisabled`) does not navigate on a click or `Enter`, with `href` or `routerLink`.
|
|
420
|
+
|
|
421
|
+
```ts
|
|
422
|
+
import { Component } from '@angular/core';
|
|
423
|
+
import { RouterLink } from '@angular/router';
|
|
424
|
+
import {
|
|
425
|
+
BfMenuButtonComponent,
|
|
426
|
+
BfMenuComponent,
|
|
427
|
+
BfMenuItemDirective,
|
|
428
|
+
} from '@bitforgehq/ui/overlay';
|
|
429
|
+
|
|
430
|
+
/** Links as menu items: a router link and an external link. */
|
|
431
|
+
@Component({
|
|
432
|
+
selector: 'app-example-menu-links',
|
|
433
|
+
imports: [RouterLink, BfMenuButtonComponent, BfMenuComponent, BfMenuItemDirective],
|
|
434
|
+
template: `
|
|
435
|
+
<bf-menu-button [menu]="help" variant="text">Help</bf-menu-button>
|
|
436
|
+
|
|
437
|
+
<ng-template #help>
|
|
438
|
+
<bf-menu>
|
|
439
|
+
<a bfMenuItem routerLink="/components/menu-button">Menu button docs</a>
|
|
440
|
+
<a bfMenuItem href="https://bitforge.ch">Bitforge</a>
|
|
441
|
+
</bf-menu>
|
|
442
|
+
</ng-template>
|
|
443
|
+
`,
|
|
444
|
+
})
|
|
445
|
+
export class MenuLinksExampleComponent {}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
## Placement
|
|
449
|
+
|
|
450
|
+
`bfMenuPlacement` on a trigger (`placement` on `bf-menu-button`) sets where the menu opens:
|
|
451
|
+
`'below-start'` (default), `'below-end'`, `'above-start'` or `'above-end'`. `start` and `end`
|
|
452
|
+
follow the text direction. When the menu would overflow the viewport, it flips to the other
|
|
453
|
+
side vertically first, then to the other alignment. The panel is at least 12 rem wide and
|
|
454
|
+
scrolls past 24 rem or the viewport height minus 2 rem.
|
|
455
|
+
|
|
456
|
+
```ts
|
|
457
|
+
import { Component, signal } from '@angular/core';
|
|
458
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
459
|
+
import {
|
|
460
|
+
BfMenuComponent,
|
|
461
|
+
BfMenuItemDirective,
|
|
462
|
+
BfMenuTriggerDirective,
|
|
463
|
+
} from '@bitforgehq/ui/overlay';
|
|
464
|
+
import { Columns3 } from 'lucide';
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* A menu aligned to the trigger's end.
|
|
468
|
+
*
|
|
469
|
+
* `bfMenuPlacement="below-end"` opens the menu under the trigger, aligned to its inline end.
|
|
470
|
+
*/
|
|
471
|
+
@Component({
|
|
472
|
+
selector: 'app-example-menu-placement',
|
|
473
|
+
imports: [BfIconComponent, BfMenuComponent, BfMenuItemDirective, BfMenuTriggerDirective],
|
|
474
|
+
template: `
|
|
475
|
+
<div class="flex justify-end">
|
|
476
|
+
<button
|
|
477
|
+
type="button"
|
|
478
|
+
class="border-border-strong text-text rounded-control inline-flex h-10 items-center gap-2 border px-4"
|
|
479
|
+
[bfMenuTriggerFor]="columns"
|
|
480
|
+
bfMenuPlacement="below-end"
|
|
481
|
+
>
|
|
482
|
+
<bf-icon [icon]="columnsIcon" size="sm" />
|
|
483
|
+
Columns
|
|
484
|
+
</button>
|
|
485
|
+
</div>
|
|
486
|
+
<p aria-live="polite" class="text-caption text-text-muted mt-3">{{ result() }}</p>
|
|
487
|
+
|
|
488
|
+
<ng-template #columns>
|
|
489
|
+
<bf-menu>
|
|
490
|
+
<button bfMenuItem (triggered)="result.set('Owner column shown.')">Owner</button>
|
|
491
|
+
<button bfMenuItem (triggered)="result.set('Due date column shown.')">Due date</button>
|
|
492
|
+
</bf-menu>
|
|
493
|
+
</ng-template>
|
|
494
|
+
`,
|
|
495
|
+
})
|
|
496
|
+
export class MenuPlacementExampleComponent {
|
|
497
|
+
protected readonly columnsIcon = Columns3;
|
|
498
|
+
protected readonly result = signal('');
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## Sizes
|
|
503
|
+
|
|
504
|
+
Items are 32 (`sm`), 36 (`md`) or 44 px (`lg`) tall. Unset, `size` follows `provideBfDefaults()`
|
|
505
|
+
(`controlSize`, `md` by default). Under a coarse pointer (touch) `sm` and `md` rows grow to 44 px.
|
|
506
|
+
|
|
507
|
+
```ts
|
|
508
|
+
import { Component, signal } from '@angular/core';
|
|
509
|
+
import {
|
|
510
|
+
BfMenuButtonComponent,
|
|
511
|
+
BfMenuComponent,
|
|
512
|
+
BfMenuItemDirective,
|
|
513
|
+
} from '@bitforgehq/ui/overlay';
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* A small menu for a dense toolbar.
|
|
517
|
+
*
|
|
518
|
+
* `size="sm"` sets the item height to 32 px, independent of the trigger's size.
|
|
519
|
+
*/
|
|
520
|
+
@Component({
|
|
521
|
+
selector: 'app-example-menu-sizes',
|
|
522
|
+
imports: [BfMenuButtonComponent, BfMenuComponent, BfMenuItemDirective],
|
|
523
|
+
template: `
|
|
524
|
+
<bf-menu-button [menu]="sort" size="sm" variant="text">Sort</bf-menu-button>
|
|
525
|
+
<p aria-live="polite" class="text-caption text-text-muted mt-3">{{ result() }}</p>
|
|
526
|
+
|
|
527
|
+
<ng-template #sort>
|
|
528
|
+
<bf-menu size="sm">
|
|
529
|
+
<button bfMenuItem (triggered)="result.set('Sorted by name.')">Name</button>
|
|
530
|
+
<button bfMenuItem (triggered)="result.set('Sorted by date.')">Date</button>
|
|
531
|
+
</bf-menu>
|
|
532
|
+
</ng-template>
|
|
533
|
+
`,
|
|
534
|
+
})
|
|
535
|
+
export class MenuSizesExampleComponent {
|
|
536
|
+
protected readonly result = signal('');
|
|
537
|
+
}
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
## Desktop
|
|
541
|
+
|
|
542
|
+
Built for desktop: hovering an item gives it the `surface-muted` fill and the pointer cursor (a disabled item shows `not-allowed` and no fill), and the focus ring is added for keyboard focus only. A submenu opens on hover and stays open while the pointer moves diagonally towards it. Items are 36 px (`md`) by default, 32 px at `sm` for dense apps, and 44 px under a coarse pointer.
|
|
543
|
+
|
|
544
|
+
## Theming
|
|
545
|
+
|
|
546
|
+
- Panel: `bg-surface`, `text-text`, a 1 px `border-border`, `rounded-card`, `shadow-floating`,
|
|
547
|
+
`p-1`.
|
|
548
|
+
- Item: `rounded-field`, `text-body` (`text-label-sm` at `sm`); hover and focus
|
|
549
|
+
`bg-surface-muted`, keyboard focus adds the `ring-ring` focus ring; disabled `text-disabled`.
|
|
550
|
+
- Divider: a 1 px `border-border` line. The check mark, the radio dot and the submenu chevron are
|
|
551
|
+
drawn in the item's text colour.
|
|
552
|
+
|
|
553
|
+
## Accessibility
|
|
554
|
+
|
|
555
|
+
- Roles: the panel is `role="menu"` (vertical). Items are `menuitem`, `menuitemcheckbox` or
|
|
556
|
+
`menuitemradio` with `aria-checked`, groups are `group` and dividers `separator`. The trigger
|
|
557
|
+
has `aria-haspopup="menu"`, `aria-expanded` and, while open, `aria-controls`. A menu is named
|
|
558
|
+
by the trigger that opened it (`aria-labelledby`; the trigger gets an id when it has none,
|
|
559
|
+
read each time the menu opens) unless you set `ariaLabel` or `ariaLabelledby`. A submenu is
|
|
560
|
+
named by its parent item.
|
|
561
|
+
- The check mark, the dot and the chevron are CSS drawings: they add nothing to an item's
|
|
562
|
+
accessible name, and they stay visible under forced colours (Windows High Contrast), where the
|
|
563
|
+
focused item is drawn in `Highlight` (a focused disabled item in `GrayText` with a `Highlight`
|
|
564
|
+
outline).
|
|
565
|
+
- Disabled items stay focusable, so screen-reader users find them, and announce
|
|
566
|
+
`aria-disabled="true"` (APG). They do not activate.
|
|
567
|
+
- Focus: opening the menu moves focus to its first item (to the last with `ArrowUp`). Closing it
|
|
568
|
+
with `Escape`, `Tab` or an item returns focus to the trigger. The items are not in the page's
|
|
569
|
+
tab order; `Tab` leaves the menu. A menu opened inside a dialog closes on `Escape` without
|
|
570
|
+
closing the dialog.
|
|
571
|
+
- Pointer: rows are at least 32 px tall, 44 px under a coarse pointer. An open submenu stays open
|
|
572
|
+
while the pointer moves diagonally towards it across other items (CDK menu aim).
|
|
573
|
+
|
|
574
|
+
### Keyboard
|
|
575
|
+
|
|
576
|
+
From the [APG menu button](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/) and
|
|
577
|
+
[menu](https://www.w3.org/WAI/ARIA/apg/patterns/menubar/) patterns:
|
|
578
|
+
|
|
579
|
+
| Key | Where | Action |
|
|
580
|
+
| ---------------------------------------------------------- | --------------- | --------------------------------------------------------------- |
|
|
581
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> / <kbd>ArrowDown</kbd> | Trigger | Opens the menu and focuses the first item |
|
|
582
|
+
| <kbd>ArrowUp</kbd> | Trigger | Opens the menu and focuses the last item |
|
|
583
|
+
| <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | Menu | Next / previous item, wrapping at the ends |
|
|
584
|
+
| <kbd>Home</kbd> / <kbd>End</kbd> | Menu | First / last item |
|
|
585
|
+
| <kbd>A</kbd>–<kbd>Z</kbd>, <kbd>0</kbd>–<kbd>9</kbd> | Menu | Next item whose label starts with the typed text |
|
|
586
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Item | Activates the item (toggles a checkbox, picks a radio), closes |
|
|
587
|
+
| <kbd>ArrowRight</kbd> (RTL <kbd>ArrowLeft</kbd>) | Submenu item | Opens the submenu and focuses its first item |
|
|
588
|
+
| <kbd>ArrowLeft</kbd> (RTL <kbd>ArrowRight</kbd>) | Submenu | Closes the submenu, focus back on its item |
|
|
589
|
+
| <kbd>Escape</kbd> | Menu or submenu | Closes it; focus back on the trigger (or the parent item) |
|
|
590
|
+
| <kbd>Tab</kbd> | Menu | Closes every menu, focus back on the trigger, then Tab moves on |
|
|
591
|
+
|
|
592
|
+
## Inputs
|
|
593
|
+
|
|
594
|
+
| Name | Type | Default | Description |
|
|
595
|
+
| ---------------- | ----------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
596
|
+
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Row height of its items: sm 32, md 36, lg 44 px (44 px under a coarse pointer); unset follows `provideBfDefaults()` (`controlSize`, `md` by default). |
|
|
597
|
+
| `ariaLabel` | `string` | `''` | Accessible name of the menu. Empty: the menu is named by the trigger that opened it (APG). |
|
|
598
|
+
| `ariaLabelledby` | `string` | `''` | Id of the element that names the menu; wins over the trigger. |
|
|
599
|
+
|
|
600
|
+
## Slots
|
|
601
|
+
|
|
602
|
+
- `default`: The items (`bfMenuItem`, `bfMenuItemCheckbox`, `bfMenuItemRadio`), groups and dividers.
|
|
603
|
+
|
|
604
|
+
## Notes
|
|
605
|
+
|
|
606
|
+
### Do
|
|
607
|
+
|
|
608
|
+
- Word items as verbs or destinations ("Rename", "Move to", "Settings") and keep the list short.
|
|
609
|
+
- Put destructive items last, after a divider.
|
|
610
|
+
- Use checkbox and radio items for view settings that apply at once ("Show grid", "Density").
|
|
611
|
+
- Use `<a bfMenuItem>` for items that navigate, so they are real links.
|
|
612
|
+
|
|
613
|
+
### Don't
|
|
614
|
+
|
|
615
|
+
- Don't put form fields, sliders or other widgets inside a menu; use a dialog.
|
|
616
|
+
- Don't nest submenus more than one level deep.
|
|
617
|
+
- Don't hide the only way to do something in a menu; frequent actions belong in the toolbar.
|
|
618
|
+
- Don't use a menu for site navigation (a list of pages); use links in a `nav`.
|
|
619
|
+
|
|
620
|
+
## Migration notes
|
|
621
|
+
|
|
622
|
+
### 0.3.0
|
|
623
|
+
|
|
624
|
+
New `changed` output on `bfMenuItemCheckbox`: the new `checked` state right after the user toggles
|
|
625
|
+
the item (after `triggered`, beside the model's `checkedChange`), so it has the one `changed` of
|
|
626
|
+
every value control.
|
|
627
|
+
|
|
628
|
+
New `bf-menu` and its parts in `@bitforgehq/ui/overlay`, on the Angular CDK menu (APG menu button
|
|
629
|
+
and menu patterns): `bfMenuItem` on a `<button>` or a link (`<a>` with `href` or `routerLink`),
|
|
630
|
+
`bfMenuItemCheckbox` and `bfMenuItemRadio` (`checked`, CSS-drawn check mark and dot),
|
|
631
|
+
`bfMenuGroup` (radio items exclusive), `bfMenuDivider` and `[bfMenuTriggerFor]` (on any element,
|
|
632
|
+
or on an item for a submenu with a CSS chevron, mirrored in RTL). Items take `bfMenuItemDisabled`
|
|
633
|
+
(focusable, `aria-disabled`) and `bfMenuItemTypeahead` and emit `triggered`; the trigger takes
|
|
634
|
+
`bfMenuTriggerData` and `bfMenuPlacement` (`below-start` (default), `below-end`, `above-start`,
|
|
635
|
+
`above-end`, flipping on overflow; submenus open beside their item) and emits `bfMenuOpened` and
|
|
636
|
+
`bfMenuClosed`. `bf-menu` takes `size` (item rows `sm` 32, `md` 36, `lg` 44 px, 44 px under a
|
|
637
|
+
coarse pointer; unset follows `provideBfDefaults()` `controlSize`), `ariaLabel` and
|
|
638
|
+
`ariaLabelledby`; without either it is named by its trigger. Keyboard: `Enter`, `Space`,
|
|
639
|
+
`ArrowDown` open on the first item, `ArrowUp` on the last; arrows wrap, `Home`/`End`, type-ahead,
|
|
640
|
+
`Escape` and `Tab` close with focus back on the trigger, `ArrowRight`/`ArrowLeft` open and close
|
|
641
|
+
submenus (swapped in RTL); `Space` also follows a link item (once, ignoring auto-repeat), and a
|
|
642
|
+
disabled link item never navigates, also with `routerLink`. `bfMenuItemCheckbox` emits
|
|
643
|
+
`checkedChange` with the state it flipped to, so `[(checked)]` binds it two-way. An open submenu
|
|
644
|
+
stays open while the pointer moves diagonally towards it (CDK menu aim). Under forced colours the
|
|
645
|
+
focused item is drawn in `Highlight`, a focused disabled one in `GrayText` on `Canvas` with a
|
|
646
|
+
`Highlight` outline. A navigation to another URL path closes an open menu (query- and
|
|
647
|
+
fragment-only changes do not); the trigger watches the URL only while its menu is open.
|
|
648
|
+
|
|
649
|
+
JSDoc of `menu-item.directive.ts` for the API reference: `@summary`/`@since` on the exported
|
|
650
|
+
symbols; `@binding` docs for the host-directive bindings `bfMenuItemDisabled`,
|
|
651
|
+
`bfMenuItemTypeahead`, `triggered`, `checked`. No behaviour change.
|
|
652
|
+
|
|
653
|
+
JSDoc of `menu-trigger.directive.ts` for the API reference: `@summary`/`@since` on the exported
|
|
654
|
+
symbols; `@binding` docs for the host-directive bindings `bfMenuTriggerFor`, `bfMenuTriggerData`,
|
|
655
|
+
`bfMenuOpened`, `bfMenuClosed`. No behaviour change.
|
|
656
|
+
|
|
657
|
+
JSDoc of `menu.component.ts` for the API reference: `@slot` docs for `default`. No behaviour change.
|
|
658
|
+
|
|
659
|
+
The two effects that write into the CDK host directives (`bfMenuItemCheckbox`'s `checked`,
|
|
660
|
+
`bfMenuTrigger`'s `menuPosition` and `ngOnChanges`) carry an `effect-justified` comment: the CDK
|
|
661
|
+
exposes no input they could derive from. No behaviour change.
|