@bitforgehq/ui 0.3.0 → 0.3.2

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 (158) hide show
  1. package/CHANGELOG.md +598 -0
  2. package/README.md +18 -11
  3. package/ai/MANIFEST.index.json +306 -0
  4. package/ai/MANIFEST.json +8915 -3788
  5. package/ai/REFERENCE.index.json +476 -0
  6. package/ai/REFERENCE.json +5810 -2905
  7. package/ai/UI_GUIDE.md +212 -58
  8. package/ai/components/app-frame.md +328 -0
  9. package/ai/components/back-button.md +42 -2
  10. package/ai/components/badge.md +17 -7
  11. package/ai/components/bar-chart.md +188 -0
  12. package/ai/components/breadcrumb-item.md +121 -0
  13. package/ai/components/breadcrumb.md +277 -0
  14. package/ai/components/button.md +82 -15
  15. package/ai/components/calendar.md +300 -0
  16. package/ai/components/card.md +119 -4
  17. package/ai/components/chart-card.md +283 -0
  18. package/ai/components/combobox.md +411 -0
  19. package/ai/components/date-input.md +396 -0
  20. package/ai/components/datetime-input.md +215 -0
  21. package/ai/components/description-item.md +111 -0
  22. package/ai/components/description-list.md +160 -0
  23. package/ai/components/donut-chart.md +169 -0
  24. package/ai/components/empty-state.md +99 -6
  25. package/ai/components/header.md +71 -2
  26. package/ai/components/icon-button.md +74 -16
  27. package/ai/components/icon.md +63 -7
  28. package/ai/components/line-chart.md +234 -0
  29. package/ai/components/list.md +70 -6
  30. package/ai/components/menu.md +5 -0
  31. package/ai/components/multi-combobox.md +257 -0
  32. package/ai/components/nav-group.md +178 -0
  33. package/ai/components/nav-item.md +210 -0
  34. package/ai/components/page-header.md +237 -0
  35. package/ai/components/pagination.md +173 -0
  36. package/ai/components/popover.md +250 -0
  37. package/ai/components/side-panel.md +391 -0
  38. package/ai/components/sidebar-toggle.md +287 -0
  39. package/ai/components/sidebar.md +541 -0
  40. package/ai/components/skeleton.md +215 -0
  41. package/ai/components/sparkline.md +188 -0
  42. package/ai/components/stat.md +297 -0
  43. package/ai/components/tab-group.md +226 -0
  44. package/ai/components/tab-panel.md +163 -0
  45. package/ai/components/table.md +697 -0
  46. package/ai/components/tabs.md +46 -2
  47. package/ai/components/theme-menu-button.md +182 -0
  48. package/ai/components/theme-switch.md +170 -0
  49. package/ai/components/time-input.md +240 -0
  50. package/ai/components/toolbar.md +354 -0
  51. package/ai/components/tooltip.md +230 -0
  52. package/ai/components/top-nav.md +159 -0
  53. package/ai/reference/bf-app-frame-breakpoint.md +27 -0
  54. package/ai/reference/bf-app-frame-content-width.md +29 -0
  55. package/ai/reference/bf-badge-appearance.md +36 -0
  56. package/ai/reference/bf-badge-variant.md +1 -1
  57. package/ai/reference/bf-bar-chart-orientation.md +27 -0
  58. package/ai/reference/bf-button-appearance.md +36 -0
  59. package/ai/reference/bf-button-variant.md +1 -1
  60. package/ai/reference/bf-calendar-disabled-date.md +23 -0
  61. package/ai/reference/bf-calendar-harness.md +51 -0
  62. package/ai/reference/bf-calendar-weekday.md +31 -0
  63. package/ai/reference/bf-chart-card-heading-level.md +26 -0
  64. package/ai/reference/bf-chart-curve.md +28 -0
  65. package/ai/reference/bf-chart-harness.md +51 -0
  66. package/ai/reference/bf-chart-legend-position.md +30 -0
  67. package/ai/reference/bf-chart-series.md +23 -0
  68. package/ai/reference/bf-chart-value-format.md +23 -0
  69. package/ai/reference/bf-color-scheme-service.md +30 -0
  70. package/ai/reference/bf-color-scheme.md +31 -0
  71. package/ai/reference/bf-combobox-filter.md +27 -0
  72. package/ai/reference/bf-combobox-harness.md +89 -0
  73. package/ai/reference/bf-combobox-option.md +33 -0
  74. package/ai/reference/bf-compare-with.md +2 -0
  75. package/ai/reference/bf-date-input-harness.md +67 -0
  76. package/ai/reference/bf-date-input-picker.md +27 -0
  77. package/ai/reference/bf-date-time-input-harness.md +76 -0
  78. package/ai/reference/bf-date-time-input-picker.md +27 -0
  79. package/ai/reference/bf-description-list-layout.md +26 -0
  80. package/ai/reference/bf-donut-slice.md +22 -0
  81. package/ai/reference/bf-empty-state-heading-level.md +1 -1
  82. package/ai/reference/bf-empty-state-heading.md +33 -0
  83. package/ai/reference/bf-labels.md +73 -20
  84. package/ai/reference/bf-menu-button-variant.md +1 -0
  85. package/ai/reference/bf-menu-placement.md +1 -0
  86. package/ai/reference/bf-overlay-options.md +28 -27
  87. package/ai/reference/bf-overlay-placement.md +34 -0
  88. package/ai/reference/bf-overlay-position.md +1 -1
  89. package/ai/reference/bf-overlay-service.md +5 -3
  90. package/ai/reference/bf-page-header-heading-level.md +26 -0
  91. package/ai/reference/bf-pagination-harness.md +61 -0
  92. package/ai/reference/bf-platform-service.md +17 -15
  93. package/ai/reference/bf-popover-harness.md +42 -0
  94. package/ai/reference/bf-popover-placement.md +28 -0
  95. package/ai/reference/bf-popover-trigger-directive.md +56 -0
  96. package/ai/reference/bf-side-panel-config.md +35 -0
  97. package/ai/reference/bf-side-panel-harness.md +34 -0
  98. package/ai/reference/bf-side-panel-ref.md +44 -0
  99. package/ai/reference/bf-side-panel-service.md +43 -0
  100. package/ai/reference/bf-side-panel-template-context.md +32 -0
  101. package/ai/reference/bf-side-panel-width.md +28 -0
  102. package/ai/reference/bf-sidebar-state-service.md +21 -0
  103. package/ai/reference/bf-sidebar-toggle-harness.md +32 -0
  104. package/ai/reference/bf-size.md +13 -0
  105. package/ai/reference/bf-skeleton-shape.md +28 -0
  106. package/ai/reference/bf-stat-appearance.md +33 -0
  107. package/ai/reference/bf-stat-trend-intent.md +27 -0
  108. package/ai/reference/bf-stat-trend.md +27 -0
  109. package/ai/reference/bf-stat-variant.md +26 -0
  110. package/ai/reference/bf-tab-group-harness.md +44 -0
  111. package/ai/reference/bf-table-align.md +26 -0
  112. package/ai/reference/bf-table-cell-context.md +24 -0
  113. package/ai/reference/bf-table-cell-directive.md +22 -0
  114. package/ai/reference/bf-table-column.md +28 -0
  115. package/ai/reference/bf-table-harness.md +77 -0
  116. package/ai/reference/bf-table-row-key.md +21 -0
  117. package/ai/reference/bf-table-sort-direction.md +26 -0
  118. package/ai/reference/bf-table-sort.md +27 -0
  119. package/ai/reference/bf-table-stack.md +29 -0
  120. package/ai/reference/bf-theme.md +5 -4
  121. package/ai/reference/bf-time-input-harness.md +67 -0
  122. package/ai/reference/bf-time-input-hour-cycle.md +27 -0
  123. package/ai/reference/bf-time-input-picker.md +27 -0
  124. package/ai/reference/bf-tooltip-directive.md +43 -0
  125. package/ai/reference/bf-tooltip-harness.md +43 -0
  126. package/ai/reference/bf-tooltip-placement.md +28 -0
  127. package/ai/reference/bf_color_scheme_storage.md +20 -0
  128. package/ai/reference/bf_side_panel_data.md +17 -0
  129. package/ai/reference/bf_sidebar_state_storage.md +20 -0
  130. package/ai/reference/bf_theme_storage.md +3 -1
  131. package/ai/reference/provide-bf-color-scheme.md +20 -0
  132. package/ai/reference/provide-bf-labels.md +13 -4
  133. package/ai/reference/provide-bf-sidebar-state.md +20 -0
  134. package/ai/reference/slide-in-from-end.md +24 -0
  135. package/ai/reference/slide-out-to-end.md +24 -0
  136. package/bin/bf-ui.js +433 -3
  137. package/fesm2022/bitforgehq-ui-charts.mjs +3045 -0
  138. package/fesm2022/bitforgehq-ui-charts.mjs.map +1 -0
  139. package/fesm2022/bitforgehq-ui-icons.mjs +107 -3
  140. package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -1
  141. package/fesm2022/bitforgehq-ui-mobile.mjs +14 -59
  142. package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -1
  143. package/fesm2022/bitforgehq-ui-overlay.mjs +6566 -38
  144. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
  145. package/fesm2022/bitforgehq-ui-testing.mjs +634 -1
  146. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
  147. package/fesm2022/bitforgehq-ui.mjs +5708 -137
  148. package/fesm2022/bitforgehq-ui.mjs.map +1 -1
  149. package/llms-full.txt +26766 -14470
  150. package/llms.txt +106 -1
  151. package/package.json +7 -3
  152. package/theme.css +101 -0
  153. package/types/bitforgehq-ui-charts.d.ts +967 -0
  154. package/types/bitforgehq-ui-icons.d.ts +13 -1
  155. package/types/bitforgehq-ui-mobile.d.ts +25 -18
  156. package/types/bitforgehq-ui-overlay.d.ts +1895 -7
  157. package/types/bitforgehq-ui-testing.d.ts +782 -1
  158. package/types/bitforgehq-ui.d.ts +2393 -45
@@ -0,0 +1,121 @@
1
+ # li[bfBreadcrumbItem]
2
+
3
+ One level of a breadcrumb trail: a link, or the current page as text
4
+
5
+ `BfBreadcrumbItemComponent` from `@bitforgehq/ui` · Navigation · since 0.3.1
6
+
7
+ ## Example
8
+
9
+ ```ts
10
+ import { Component } from '@angular/core';
11
+ import { RouterLink } from '@angular/router';
12
+ import { BfBreadcrumbComponent, BfBreadcrumbItemComponent } from '@bitforgehq/ui';
13
+
14
+ /**
15
+ * The li[bfBreadcrumbItem] overview.
16
+ *
17
+ * Three levels of a `bf-breadcrumb`: two ancestor pages as router links, each followed by a
18
+ * chevron, and the current page as plain text, marked `aria-current="page"`.
19
+ */
20
+ @Component({
21
+ selector: 'app-example-breadcrumb-item-overview',
22
+ imports: [RouterLink, BfBreadcrumbComponent, BfBreadcrumbItemComponent],
23
+ template: `
24
+ <bf-breadcrumb label="Component breadcrumb">
25
+ <li bfBreadcrumbItem><a routerLink="/components">Components</a></li>
26
+ <li bfBreadcrumbItem><a routerLink="/components/breadcrumb">Breadcrumb</a></li>
27
+ <li bfBreadcrumbItem>Breadcrumb item</li>
28
+ </bf-breadcrumb>
29
+ `,
30
+ })
31
+ export class BreadcrumbItemOverviewExampleComponent {}
32
+ ```
33
+
34
+ ## Overview
35
+
36
+ One level of a `bf-breadcrumb` trail, on the consumer's own `<li>` so the list stays `ol > li`.
37
+ Project an `<a routerLink>` for an ancestor page, or plain text for the current page; the last
38
+ item is the current page (`aria-current="page"`, no link look, no separator), every other one is
39
+ followed by a chevron.
40
+
41
+ One level of a [`bf-breadcrumb`](https://bitforge-ui.web.app/components/breadcrumb) trail, as an attribute on the app's own `<li>`. It styles the link projected into it, adds the chevron after it and, on the last level, marks the current page. An item lives only inside a `bf-breadcrumb`, which decides which level is current and which ones a collapsed trail hides.
42
+
43
+ ## When to use
44
+
45
+ Use `li[bfBreadcrumbItem]` for each level of a [bf-breadcrumb](https://bitforge-ui.web.app/components/breadcrumb), and only there. On mobile, in the Capacitor shell, use [bf-back-button](https://bitforge-ui.web.app/components/back-button) in [bf-header](https://bitforge-ui.web.app/components/header) instead.
46
+
47
+ ## Links and the current page
48
+
49
+ Put an `<a routerLink>` (or `href`) straight inside the `li` for a page above the current one: as a direct child it takes the link look, the hover underline and the focus ring. Put plain text in the last item, the current page: it is wrapped in a span with `aria-current="page"` and shown semibold, and no chevron follows it.
50
+
51
+ ```ts
52
+ import { Component } from '@angular/core';
53
+ import { RouterLink } from '@angular/router';
54
+ import { BfBreadcrumbComponent, BfBreadcrumbItemComponent } from '@bitforgehq/ui';
55
+
56
+ /**
57
+ * A breadcrumb trail.
58
+ *
59
+ * Each level is the app's own `<li bfBreadcrumbItem>` holding a router link; the last one is the
60
+ * current page as plain text. `label` names the `nav`; this one is not the page's only breadcrumb
61
+ * (the docs site has its own), so it gets a name of its own.
62
+ */
63
+ @Component({
64
+ selector: 'app-example-breadcrumb-basic',
65
+ imports: [RouterLink, BfBreadcrumbComponent, BfBreadcrumbItemComponent],
66
+ template: `
67
+ <bf-breadcrumb label="Docs breadcrumb">
68
+ <li bfBreadcrumbItem><a routerLink="/">Docs</a></li>
69
+ <li bfBreadcrumbItem><a routerLink="/components">Components</a></li>
70
+ <li bfBreadcrumbItem>Breadcrumb</li>
71
+ </bf-breadcrumb>
72
+ `,
73
+ })
74
+ export class BreadcrumbBasicExampleComponent {}
75
+ ```
76
+
77
+ ## Desktop
78
+
79
+ A linked level turns from `text-muted` to `text` and is underlined on hover, with the pointer cursor; the ring shows on keyboard focus only. A link is at least 24 px tall under a fine pointer and 44 px under a coarse one (`pointer-coarse:min-h-hit`), through a min height rather than an `::after`, because it is your content. See the [bf-breadcrumb](https://bitforge-ui.web.app/components/breadcrumb) page for collapsing.
80
+
81
+ ## Theming
82
+
83
+ - Ancestor level: `text-muted`, `text` on hover with an underline (`underline-offset-4`); `rounded-tile` with the `ring` focus ring.
84
+ - Current page: `text`, semibold.
85
+ - Separator: a 1.5 px CSS chevron in `text-subtle`, pointing to the inline end and mirrored under `dir="rtl"`.
86
+
87
+ ## Accessibility
88
+
89
+ The host is your native `<li>`, so the trail stays an ordered list. Only the last item carries `aria-current="page"`. The chevron is an empty `aria-hidden` element, never announced. A level a collapsed trail hides carries `hidden`; when the ellipsis shows it and it has no link, the item takes focus itself (`tabindex="-1"`).
90
+
91
+ ### Keyboard
92
+
93
+ | Key | Action |
94
+ | ---------------- | ------------------------- |
95
+ | <kbd>Tab</kbd> | Moves to the level's link |
96
+ | <kbd>Enter</kbd> | Follows the focused link |
97
+
98
+ ## Slots
99
+
100
+ - `default`: The level: an `<a>` link to the page, or plain text for the current page.
101
+
102
+ ## Notes
103
+
104
+ ### Do
105
+
106
+ - Project the link as the direct child of the `li`, so it takes the link look.
107
+ - Keep the current page plain text.
108
+
109
+ ### Don't
110
+
111
+ - Don't use `li[bfBreadcrumbItem]` outside a `bf-breadcrumb`.
112
+ - Don't put more than one link, or a control, in one level.
113
+
114
+ ## Migration notes
115
+
116
+ ### 0.3.1
117
+
118
+ New `li[bfBreadcrumbItem]` in `@bitforgehq/ui`: one level of a `bf-breadcrumb` on the app's own
119
+ `<li>`. A projected link takes the muted link look, the hover underline and the focus ring (at
120
+ least 24 px tall, 44 px under a coarse pointer); the last level is the current page, wrapped in
121
+ `aria-current="page"` with no chevron.
@@ -0,0 +1,277 @@
1
+ # bf-breadcrumb
2
+
3
+ Desktop breadcrumb trail of links to the current page, with optional collapsing
4
+
5
+ `BfBreadcrumbComponent` from `@bitforgehq/ui` · Navigation · since 0.3.1
6
+
7
+ ## Example
8
+
9
+ ```ts
10
+ import { Component, signal } from '@angular/core';
11
+ import { RouterLink } from '@angular/router';
12
+ import { BfBreadcrumbComponent, BfBreadcrumbItemComponent } from '@bitforgehq/ui';
13
+
14
+ /**
15
+ * The bf-breadcrumb overview.
16
+ *
17
+ * Live demo of `bf-breadcrumb`: a five-level trail collapsed to three with `maxItems`; the ellipsis
18
+ * shows the hidden levels in place and moves focus to the first one, which the result line
19
+ * reports. The demo's links stay on this page (a fragment per level); an app links each level to
20
+ * its route.
21
+ */
22
+ @Component({
23
+ selector: 'app-example-breadcrumb-overview',
24
+ imports: [RouterLink, BfBreadcrumbComponent, BfBreadcrumbItemComponent],
25
+ template: `
26
+ <div class="flex flex-col gap-3" (focusin)="onFocus($event)">
27
+ <bf-breadcrumb label="Release breadcrumb" [maxItems]="3">
28
+ @for (level of trail; track level.id) {
29
+ <li bfBreadcrumbItem>
30
+ <a routerLink="." [fragment]="level.id">{{ level.label }}</a>
31
+ </li>
32
+ }
33
+ <li bfBreadcrumbItem>Release 2.4</li>
34
+ </bf-breadcrumb>
35
+ <p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
36
+ </div>
37
+ `,
38
+ })
39
+ export class BreadcrumbOverviewExampleComponent {
40
+ readonly status = signal('Two levels are hidden.');
41
+ readonly trail: readonly { id: string; label: string }[] = [
42
+ { id: 'workspace', label: 'Workspace' },
43
+ { id: 'projects', label: 'Projects' },
44
+ { id: 'atlas', label: 'Atlas' },
45
+ { id: 'releases', label: 'Releases' },
46
+ ];
47
+
48
+ onFocus(event: FocusEvent): void {
49
+ if (event.target instanceof HTMLAnchorElement) {
50
+ this.status.set(`Focus on ${event.target.text.trim()}.`);
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Overview
57
+
58
+ Desktop breadcrumb trail: a `nav` named "Breadcrumb" around an ordered list of
59
+ `li[bfBreadcrumbItem]` levels, the last one the current page, a CSS chevron between them. With
60
+ `maxItems` set and a longer trail it keeps the first level and the last `maxItems - 1`, and hides
61
+ the middle ones behind an ellipsis button that shows them in place, or behind the consumer's own
62
+ `bfSlot="overflow"` content (a menu button of the hidden levels).
63
+
64
+ A breadcrumb trail for desktop web apps: a `nav` named "Breadcrumb" around an ordered list of the pages above the current one, each a link, with the current page last. Every level is the app's own `<li bfBreadcrumbItem>`, so the list stays `ol > li` and the links stay the app's `routerLink`s; the widget draws the chevrons between them, marks the current page and, with `maxItems`, collapses a long trail behind an ellipsis.
65
+
66
+ ## When to use
67
+
68
+ Use `bf-breadcrumb` on a desktop page that sits deep in a hierarchy (a client's billing settings, a file in a folder), so the user sees where they are and can go up any number of levels. Don't use it for a flat app with one level, where the sidebar or the top nav already says where the user is. On mobile, in the Capacitor shell, use [bf-back-button](https://bitforge-ui.web.app/components/back-button) in [bf-header](https://bitforge-ui.web.app/components/header) instead.
69
+
70
+ ## Basic usage
71
+
72
+ Put one `li[bfBreadcrumbItem]` per level inside `bf-breadcrumb`, from the root down: an `<a routerLink>` for each page above, plain text for the current page. The last item is always the current page; it gets `aria-current="page"` and no chevron. Import `BfBreadcrumbComponent` and `BfBreadcrumbItemComponent` from `@bitforgehq/ui`. The `nav` is named "Breadcrumb"; when a page shows more than one breadcrumb (as this page does, beside the site's own), give each a distinct `label`.
73
+
74
+ ```ts
75
+ import { Component } from '@angular/core';
76
+ import { RouterLink } from '@angular/router';
77
+ import { BfBreadcrumbComponent, BfBreadcrumbItemComponent } from '@bitforgehq/ui';
78
+
79
+ /**
80
+ * A breadcrumb trail.
81
+ *
82
+ * Each level is the app's own `<li bfBreadcrumbItem>` holding a router link; the last one is the
83
+ * current page as plain text. `label` names the `nav`; this one is not the page's only breadcrumb
84
+ * (the docs site has its own), so it gets a name of its own.
85
+ */
86
+ @Component({
87
+ selector: 'app-example-breadcrumb-basic',
88
+ imports: [RouterLink, BfBreadcrumbComponent, BfBreadcrumbItemComponent],
89
+ template: `
90
+ <bf-breadcrumb label="Docs breadcrumb">
91
+ <li bfBreadcrumbItem><a routerLink="/">Docs</a></li>
92
+ <li bfBreadcrumbItem><a routerLink="/components">Components</a></li>
93
+ <li bfBreadcrumbItem>Breadcrumb</li>
94
+ </bf-breadcrumb>
95
+ `,
96
+ })
97
+ export class BreadcrumbBasicExampleComponent {}
98
+ ```
99
+
100
+ ## Collapsing a long trail
101
+
102
+ Set `maxItems` to collapse a trail with more levels than that: the first level and the last `maxItems - 1` stay, and the middle ones are hidden behind an ellipsis button ("Show 2 more levels", `provideBfLabels()`'s `breadcrumbExpand`). Pressing it shows every level in place and moves focus to the first one it revealed, so a keyboard user carries on from there. The trail collapses again when its levels change (on the next page). `0`, the default, never collapses; a value below 2 acts as 2, so the first level and the current page always stay.
103
+
104
+ ```ts
105
+ import { Component } from '@angular/core';
106
+ import { RouterLink } from '@angular/router';
107
+ import { BfBreadcrumbComponent, BfBreadcrumbItemComponent } from '@bitforgehq/ui';
108
+
109
+ /**
110
+ * A long trail collapsed in place.
111
+ *
112
+ * `maxItems="4"` keeps the first level and the last three; the ellipsis button shows the two
113
+ * hidden ones in place and moves focus to the first of them. The demo's links stay on this page.
114
+ */
115
+ @Component({
116
+ selector: 'app-example-breadcrumb-collapsed',
117
+ imports: [RouterLink, BfBreadcrumbComponent, BfBreadcrumbItemComponent],
118
+ template: `
119
+ <bf-breadcrumb label="Invoice breadcrumb" maxItems="4">
120
+ @for (level of trail; track level.id) {
121
+ <li bfBreadcrumbItem>
122
+ <a routerLink="." [fragment]="level.id">{{ level.label }}</a>
123
+ </li>
124
+ }
125
+ <li bfBreadcrumbItem>Invoice INV-204</li>
126
+ </bf-breadcrumb>
127
+ `,
128
+ })
129
+ export class BreadcrumbCollapsedExampleComponent {
130
+ readonly trail: readonly { id: string; label: string }[] = [
131
+ { id: 'home', label: 'Home' },
132
+ { id: 'clients', label: 'Clients' },
133
+ { id: 'meadow', label: 'Meadow Studio' },
134
+ { id: 'billing', label: 'Billing' },
135
+ { id: 'invoices', label: 'Invoices' },
136
+ ];
137
+ }
138
+ ```
139
+
140
+ ## A menu of the hidden levels
141
+
142
+ To keep the trail on one line, project your own control in `bfSlot="overflow"`: it takes the ellipsis button's place, after the first level. A `bf-menu-button` from `@bitforgehq/ui/overlay` with an `a[bfMenuItem]` link per hidden level is the usual one (a root widget cannot open a menu itself). The hidden levels are a slice of the array you already render: `trail.slice(1, count - maxItems + 1)`, where `count` includes the current page. Name the menu button with its `label`.
143
+
144
+ ```ts
145
+ import { Component, computed } from '@angular/core';
146
+ import { RouterLink } from '@angular/router';
147
+ import { BfBreadcrumbComponent, BfBreadcrumbItemComponent } from '@bitforgehq/ui';
148
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
149
+ import {
150
+ BfMenuButtonComponent,
151
+ BfMenuComponent,
152
+ BfMenuItemDirective,
153
+ } from '@bitforgehq/ui/overlay';
154
+ import { Ellipsis } from 'lucide';
155
+
156
+ /**
157
+ * A long trail collapsed behind a menu.
158
+ *
159
+ * A `bf-menu-button` in `bfSlot="overflow"` replaces the ellipsis button: its menu links to the
160
+ * levels the trail hides, the same slice of the app's own array (`maxItems` 3 keeps the first
161
+ * level and the last two). The demo's links stay on this page.
162
+ */
163
+ @Component({
164
+ selector: 'app-example-breadcrumb-collapsed-menu',
165
+ imports: [
166
+ RouterLink,
167
+ BfBreadcrumbComponent,
168
+ BfBreadcrumbItemComponent,
169
+ BfIconComponent,
170
+ BfMenuButtonComponent,
171
+ BfMenuComponent,
172
+ BfMenuItemDirective,
173
+ ],
174
+ template: `
175
+ <bf-breadcrumb label="Client breadcrumb" [maxItems]="maxItems">
176
+ <bf-menu-button
177
+ bfSlot="overflow"
178
+ [menu]="hiddenLevels"
179
+ iconOnly
180
+ label="Hidden levels"
181
+ variant="text"
182
+ size="sm"
183
+ >
184
+ <bf-icon [icon]="ellipsis" />
185
+ </bf-menu-button>
186
+ @for (level of trail; track level.id) {
187
+ <li bfBreadcrumbItem>
188
+ <a routerLink="." [fragment]="level.id">{{ level.label }}</a>
189
+ </li>
190
+ }
191
+ <li bfBreadcrumbItem>Invoice INV-204</li>
192
+ </bf-breadcrumb>
193
+
194
+ <ng-template #hiddenLevels>
195
+ <bf-menu>
196
+ @for (level of hidden(); track level.id) {
197
+ <a bfMenuItem routerLink="." [fragment]="level.id">{{ level.label }}</a>
198
+ }
199
+ </bf-menu>
200
+ </ng-template>
201
+ `,
202
+ })
203
+ export class BreadcrumbCollapsedMenuExampleComponent {
204
+ readonly ellipsis = Ellipsis;
205
+ readonly maxItems = 3;
206
+ readonly trail: readonly { id: string; label: string }[] = [
207
+ { id: 'home', label: 'Home' },
208
+ { id: 'clients', label: 'Clients' },
209
+ { id: 'meadow', label: 'Meadow Studio' },
210
+ { id: 'billing', label: 'Billing' },
211
+ { id: 'invoices', label: 'Invoices' },
212
+ ];
213
+ /** The levels between the first and the last `maxItems - 1` (the current page counts as one). */
214
+ readonly hidden = computed(() =>
215
+ this.trail.slice(1, this.trail.length + 1 - (this.maxItems - 1)),
216
+ );
217
+ }
218
+ ```
219
+
220
+ ## Desktop
221
+
222
+ `bf-breadcrumb` is a desktop pattern. A link turns from `text-muted` to `text` and is underlined on hover, with the pointer cursor; the ellipsis button takes a `surface-muted` fill on hover. The focus ring shows on keyboard focus only. Links keep the browser's behaviour: Ctrl/Cmd-click and a middle click open a level in a new tab. Under a coarse pointer (a touch laptop) every link is at least 44 px tall (`pointer-coarse:min-h-hit`) and the ellipsis button's hit area extends to 44 px. The trail wraps onto more lines when it does not fit; collapse it with `maxItems` to keep it short.
223
+
224
+ ## Theming
225
+
226
+ - Text: `text-label-sm`; ancestor levels `text-muted`, `text` on hover; the current page `text`, semibold.
227
+ - Separators: a 1.5 px CSS chevron in `text-subtle`, mirrored under `dir="rtl"`.
228
+ - Links and the ellipsis button: `rounded-tile` and the `ring` focus ring; the ellipsis button's hover fill is `surface-muted`, its pressed fill `surface-pressed`.
229
+
230
+ ## Accessibility
231
+
232
+ The host renders a `nav` landmark named by `label` (default "Breadcrumb", `provideBfLabels()`'s `breadcrumb`) around an `ol`, so a screen reader announces the number of levels; give a second breadcrumb on the same page another `label`. The current page is wrapped in a span with `aria-current="page"`. The chevrons are empty, `aria-hidden` elements, so the trail reads as its level names only. Hidden levels carry `hidden`, so they leave the accessibility tree until the ellipsis shows them; the ellipsis is a native `<button>` named "Show N more levels", and pressing it moves focus to the first revealed level's link (a level without a link takes focus itself). Content in `bfSlot="overflow"` brings its own name and keyboard handling.
233
+
234
+ ### Keyboard
235
+
236
+ | Key | Action |
237
+ | ----------------------------------- | ----------------------------------------------------------------------------- |
238
+ | <kbd>Tab</kbd> | Moves through the links of the visible levels and the ellipsis button |
239
+ | <kbd>Enter</kbd> | Follows the focused link |
240
+ | <kbd>Enter</kbd> / <kbd>Space</kbd> | On the ellipsis button: shows the hidden levels and focuses the first of them |
241
+
242
+ ## Inputs
243
+
244
+ | Name | Type | Default | Description |
245
+ | ---------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
246
+ | `label` | `string` | `''` | Accessible name of the navigation; empty falls back to `provideBfLabels()`'s `breadcrumb` ("Breadcrumb"). |
247
+ | `maxItems` | `number` | `0` | Collapse a trail longer than this: keep the first level and the last `maxItems - 1` and hide the middle ones behind an ellipsis. `0` never collapses; values below 2 act as 2, so the current page always stays. |
248
+
249
+ ## Slots
250
+
251
+ - `overflow`: Replaces the ellipsis button of a collapsed trail, e.g. a menu of the hidden levels.
252
+ - `default`: The levels, each a `li[bfBreadcrumbItem]`, the current page last.
253
+
254
+ ## Notes
255
+
256
+ ### Do
257
+
258
+ - Keep the labels short, the page titles users know, and the current page last as plain text.
259
+ - Collapse a trail deeper than four or five levels with `maxItems`.
260
+
261
+ ### Don't
262
+
263
+ - Don't link the current page, and don't put anything but `li[bfBreadcrumbItem]` levels (and the `overflow` slot) inside `bf-breadcrumb`.
264
+ - Don't use a breadcrumb as the app's main navigation, and don't show it in the Capacitor shell.
265
+
266
+ ## Migration notes
267
+
268
+ ### 0.3.1
269
+
270
+ New `bf-breadcrumb` in `@bitforgehq/ui`, the desktop breadcrumb trail: a `nav` (named by
271
+ `label`, default `provideBfLabels()`'s new `breadcrumb` key, "Breadcrumb") around an `ol` of
272
+ `li[bfBreadcrumbItem]` levels, with CSS chevrons between them that are never announced.
273
+ `maxItems` collapses a longer trail to the first level and the last `maxItems - 1`, behind an
274
+ ellipsis button (the new `breadcrumbExpand` label, "Show 2 more levels") that shows the hidden
275
+ levels in place and focuses the first one; `bfSlot="overflow"` replaces that button with the app's
276
+ own control, such as a `bf-menu-button` of the hidden levels. It replaces the hand-built
277
+ breadcrumb the back-button docs showed.
@@ -95,12 +95,12 @@ export class ButtonOverviewExampleComponent {
95
95
 
96
96
  Also matches: `a[bfButton]`
97
97
 
98
- Pill-shaped action button with five variants, three sizes, a loading state and icon slots. Put
98
+ Pill-shaped action button with six variants, three sizes, a loading state and icon slots. Put
99
99
  the label in the default slot and icons in the `icon-start` / `icon-end` slots; never place a
100
100
  `bf-button` inside another interactive element. For navigation, put `bfButton` on your own `<a>`
101
101
  (with `href` or `routerLink`): it renders as a real link that looks like the button.
102
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.
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, `ghost` for quiet neutral actions and navigation.
104
104
 
105
105
  ## When to use
106
106
 
@@ -140,13 +140,13 @@ export class ButtonBasicExampleComponent {
140
140
 
141
141
  ## Variants
142
142
 
143
- `primary` (filled sage), `tonal` (soft tint), `outline`, `text` and `danger`.
143
+ `primary` (filled), `tonal` (soft tint), `outline`, `text`, `danger` and `ghost`. `ghost` is the quiet neutral one: muted text with no fill at rest and a neutral surface on hover, with the `text` variant's padding. Use it for secondary toolbar actions and navigation rows, where a row of `text` buttons would read as a row of coloured links and `tonal` would tint every one. `variant` takes a `BfButtonAppearance` (the five `BfButtonVariant` styles plus `ghost`).
144
144
 
145
145
  ```ts
146
146
  import { Component } from '@angular/core';
147
147
  import { BfButtonComponent } from '@bitforgehq/ui';
148
148
 
149
- /** The five button variants. */
149
+ /** The six button variants. */
150
150
  @Component({
151
151
  selector: 'app-example-button-variants',
152
152
  imports: [BfButtonComponent],
@@ -157,6 +157,7 @@ import { BfButtonComponent } from '@bitforgehq/ui';
157
157
  <bf-button variant="outline">Outline</bf-button>
158
158
  <bf-button variant="text">Text</bf-button>
159
159
  <bf-button variant="danger">Delete</bf-button>
160
+ <bf-button variant="ghost">Ghost</bf-button>
160
161
  </div>
161
162
  `,
162
163
  })
@@ -300,9 +301,57 @@ export class ButtonLinkExampleComponent {
300
301
  }
301
302
  ```
302
303
 
304
+ ## Navigation
305
+
306
+ A row of `a[bfButton variant="ghost"]` is a top navigation or a sidebar. The link of the current page shows it: `ghost` and `text` links with `aria-current="page"` take the tonal fill (`outline` draws its border in primary), so the template needs no `[variant]` switch. With the router, add `routerLinkActive` and `ariaCurrentWhenActive="page"` to each link and the router sets `aria-current` for you. Put the links in a `<nav>` with an `aria-label`.
307
+
308
+ ```ts
309
+ import { Component, signal } from '@angular/core';
310
+ import { BfButtonComponent } from '@bitforgehq/ui';
311
+
312
+ interface NavLink {
313
+ readonly id: string;
314
+ readonly label: string;
315
+ }
316
+
317
+ /**
318
+ * A top navigation of ghost links.
319
+ *
320
+ * The current page carries `aria-current="page"` and shows it; with the router,
321
+ * `routerLinkActive` and `ariaCurrentWhenActive="page"` set it instead of the click handler.
322
+ */
323
+ @Component({
324
+ selector: 'app-example-button-nav',
325
+ imports: [BfButtonComponent],
326
+ template: `
327
+ <nav aria-label="Main" class="flex flex-wrap items-center gap-1">
328
+ @for (link of links; track link.id) {
329
+ <a
330
+ bfButton
331
+ variant="ghost"
332
+ [href]="'#' + link.id"
333
+ [attr.aria-current]="link.id === current() ? 'page' : null"
334
+ (click)="$event.preventDefault(); current.set(link.id)"
335
+ >{{ link.label }}</a
336
+ >
337
+ }
338
+ </nav>
339
+ `,
340
+ })
341
+ export class ButtonNavExampleComponent {
342
+ protected readonly links: readonly NavLink[] = [
343
+ { id: 'overview', label: 'Overview' },
344
+ { id: 'projects', label: 'Projects' },
345
+ { id: 'team', label: 'Team' },
346
+ { id: 'settings', label: 'Settings' },
347
+ ];
348
+ protected readonly current = signal('overview');
349
+ }
350
+ ```
351
+
303
352
  ## Desktop
304
353
 
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)).
354
+ Hover darkens the fill (`primary`, `tonal`, `danger`), the border (`outline`) or the text (`text`), or lays a neutral surface under `ghost`, 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
355
 
307
356
  ## Theming
308
357
 
@@ -313,10 +362,16 @@ Consumes tokens by variant, rest → hover:
313
362
  - `outline`: `surface` / `text` / `border-strong` (the control boundary, ≥ 3:1) → `text-muted` border; focus swaps the border to `primary`
314
363
  - `text`: `primary-text` (primary as text, ≥ 4.5:1 on `bg` and `surface`) → `primary-strong`
315
364
  - `danger`: `danger-soft` / `danger` → `danger` / `danger-soft`
365
+ - `ghost`: transparent / `text-muted` → `surface-muted` / `text`
366
+
367
+ Current page (`a[bfButton]` with `aria-current="page"`): `ghost` and `text` take `primary-soft` /
368
+ `primary-strong` (→ `primary-soft-hover` on hover, `primary-soft-pressed` while pressed), `outline` a `primary` border with
369
+ `primary-strong` text; under forced colours a `LinkText` outline marks it instead.
316
370
 
317
371
  Pressed (while the mouse button, a touch or the Space key holds it down): `primary` →
318
372
  `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
373
+ `primary-strong`); `danger` → `danger-pressed` with `danger-soft` text; `ghost` →
374
+ `surface-pressed` with `text`. `a[bfButton]` presses the
320
375
  same way. A disabled or loading button has no pressed state, and under forced colours the system
321
376
  colours win.
322
377
 
@@ -328,7 +383,7 @@ Icon and label sit 8 px apart. A label longer than its container truncates with
328
383
 
329
384
  `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
385
 
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.
386
+ `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. The current page is the `aria-current="page"` you (or `routerLinkActive` with `ariaCurrentWhenActive`) set: screen readers announce it ("current page"), and the fill only mirrors it.
332
387
 
333
388
  ### Keyboard
334
389
 
@@ -340,14 +395,14 @@ Icon and label sit 8 px apart. A label longer than its container truncates with
340
395
 
341
396
  ## Inputs
342
397
 
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. |
398
+ | Name | Type | Default | Description |
399
+ | ----------- | ------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
400
+ | `variant` | `BfButtonAppearance` (`'primary' \| 'tonal' \| 'outline' \| 'text' \| 'danger' \| 'ghost'`) | `'primary'` | Visual style: primary (filled), tonal (soft tint), outline, text, danger, or ghost (quiet neutral). On `a[bfButton]`, `ghost`, `text` and `outline` also show `aria-current="page"`. |
401
+ | `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Height step: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()` (`controlSize`, `md` by default). |
402
+ | `type` | `BfButtonType` (`'button' \| 'submit' \| 'reset'`) | `'button'` | Native button type. Use `submit` inside a form. Ignored on `a[bfButton]`. |
403
+ | `disabled` | `boolean` | `false` | Disables the control and swallows clicks. Ignored on `a[bfButton]`: a link is never disabled; render a disabled `bf-button` instead. |
404
+ | `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]`. |
405
+ | `fullWidth` | `boolean` | `false` | Stretches the button to its container's width. |
351
406
 
352
407
  ## Outputs
353
408
 
@@ -368,10 +423,12 @@ Icon and label sit 8 px apart. A label longer than its container truncates with
368
423
  - 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
424
  - Bind `[loading]` to the pending state of the action so the user gets feedback and cannot double-submit.
370
425
  - Use `type="submit"` inside a `<form>` so Enter submits.
426
+ - Build a top nav or a sidebar from `a[bfButton variant="ghost"]` with `routerLinkActive` and `ariaCurrentWhenActive="page"`.
371
427
 
372
428
  ### Don't
373
429
 
374
430
  - 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="…">`).
431
+ - Don't switch `[variant]` to mark the current page; set `aria-current="page"` and the link shows it.
375
432
  - Don't disable a link; render a disabled `bf-button` while the destination is unavailable.
376
433
  - Don't add `dark:` classes or colors on the element; the theme handles both.
377
434
  - Don't put a `bf-icon` in the default slot; use the `icon-start` / `icon-end` slots so spacing stays right.
@@ -379,6 +436,16 @@ Icon and label sit 8 px apart. A label longer than its container truncates with
379
436
 
380
437
  ## Migration notes
381
438
 
439
+ ### 0.3.1
440
+
441
+ New `ghost` variant: quiet and neutral (`text-muted` label, no fill at rest, `surface-muted` on
442
+ hover, `surface-pressed` on press), with the `text` variant's sizes, padding and disabled state,
443
+ for secondary toolbar actions and navigation rows. `a[bfButton]` with `aria-current="page"` shows
444
+ the current page: `ghost` and `text` links take the tonal fill, `outline` a primary border, so a
445
+ top nav built with `routerLinkActive` and `ariaCurrentWhenActive="page"` needs no `[variant]`
446
+ switch. `variant` is now typed `BfButtonAppearance` (the five `BfButtonVariant` styles plus
447
+ `ghost`); `BfButtonVariant` keeps its five members, so code that maps over it keeps compiling.
448
+
382
449
  ### 0.3.0
383
450
 
384
451
  **Behaviour:** `size` is a `BfSize` and, unset, follows `provideBfDefaults()`'s `controlSize`