@bitforgehq/ui 0.3.0 → 0.3.1

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 (144) hide show
  1. package/CHANGELOG.md +482 -0
  2. package/README.md +17 -11
  3. package/ai/MANIFEST.index.json +288 -0
  4. package/ai/MANIFEST.json +8716 -3926
  5. package/ai/REFERENCE.index.json +413 -0
  6. package/ai/REFERENCE.json +4710 -2047
  7. package/ai/UI_GUIDE.md +185 -57
  8. package/ai/components/back-button.md +42 -2
  9. package/ai/components/badge.md +17 -7
  10. package/ai/components/bar-chart.md +188 -0
  11. package/ai/components/breadcrumb-item.md +121 -0
  12. package/ai/components/breadcrumb.md +277 -0
  13. package/ai/components/button.md +82 -15
  14. package/ai/components/calendar.md +300 -0
  15. package/ai/components/card.md +63 -2
  16. package/ai/components/chart-card.md +283 -0
  17. package/ai/components/combobox.md +411 -0
  18. package/ai/components/date-input.md +396 -0
  19. package/ai/components/datetime-input.md +215 -0
  20. package/ai/components/description-item.md +111 -0
  21. package/ai/components/description-list.md +160 -0
  22. package/ai/components/donut-chart.md +169 -0
  23. package/ai/components/header.md +67 -2
  24. package/ai/components/icon-button.md +74 -16
  25. package/ai/components/icon.md +63 -7
  26. package/ai/components/line-chart.md +234 -0
  27. package/ai/components/list.md +70 -6
  28. package/ai/components/menu.md +5 -0
  29. package/ai/components/multi-combobox.md +257 -0
  30. package/ai/components/nav-group.md +178 -0
  31. package/ai/components/nav-item.md +210 -0
  32. package/ai/components/page-header.md +237 -0
  33. package/ai/components/pagination.md +173 -0
  34. package/ai/components/popover.md +250 -0
  35. package/ai/components/side-panel.md +391 -0
  36. package/ai/components/sidebar.md +363 -0
  37. package/ai/components/skeleton.md +215 -0
  38. package/ai/components/sparkline.md +188 -0
  39. package/ai/components/stat.md +233 -0
  40. package/ai/components/tab-group.md +226 -0
  41. package/ai/components/tab-panel.md +163 -0
  42. package/ai/components/table.md +568 -0
  43. package/ai/components/tabs.md +42 -2
  44. package/ai/components/theme-menu-button.md +182 -0
  45. package/ai/components/theme-switch.md +170 -0
  46. package/ai/components/time-input.md +240 -0
  47. package/ai/components/toolbar.md +289 -0
  48. package/ai/components/tooltip.md +230 -0
  49. package/ai/components/top-nav.md +159 -0
  50. package/ai/reference/bf-badge-appearance.md +36 -0
  51. package/ai/reference/bf-badge-variant.md +1 -1
  52. package/ai/reference/bf-bar-chart-orientation.md +27 -0
  53. package/ai/reference/bf-button-appearance.md +36 -0
  54. package/ai/reference/bf-button-variant.md +1 -1
  55. package/ai/reference/bf-calendar-disabled-date.md +23 -0
  56. package/ai/reference/bf-calendar-harness.md +51 -0
  57. package/ai/reference/bf-calendar-weekday.md +31 -0
  58. package/ai/reference/bf-chart-card-heading-level.md +26 -0
  59. package/ai/reference/bf-chart-curve.md +28 -0
  60. package/ai/reference/bf-chart-harness.md +51 -0
  61. package/ai/reference/bf-chart-legend-position.md +30 -0
  62. package/ai/reference/bf-chart-series.md +23 -0
  63. package/ai/reference/bf-chart-value-format.md +23 -0
  64. package/ai/reference/bf-color-scheme-service.md +30 -0
  65. package/ai/reference/bf-color-scheme.md +31 -0
  66. package/ai/reference/bf-combobox-filter.md +27 -0
  67. package/ai/reference/bf-combobox-harness.md +89 -0
  68. package/ai/reference/bf-combobox-option.md +33 -0
  69. package/ai/reference/bf-compare-with.md +2 -0
  70. package/ai/reference/bf-date-input-harness.md +67 -0
  71. package/ai/reference/bf-date-input-picker.md +27 -0
  72. package/ai/reference/bf-date-time-input-harness.md +76 -0
  73. package/ai/reference/bf-date-time-input-picker.md +27 -0
  74. package/ai/reference/bf-description-list-layout.md +26 -0
  75. package/ai/reference/bf-donut-slice.md +22 -0
  76. package/ai/reference/bf-labels.md +67 -20
  77. package/ai/reference/bf-menu-button-variant.md +1 -0
  78. package/ai/reference/bf-menu-placement.md +1 -0
  79. package/ai/reference/bf-overlay-options.md +28 -27
  80. package/ai/reference/bf-overlay-placement.md +34 -0
  81. package/ai/reference/bf-overlay-position.md +1 -1
  82. package/ai/reference/bf-overlay-service.md +5 -3
  83. package/ai/reference/bf-page-header-heading-level.md +26 -0
  84. package/ai/reference/bf-pagination-harness.md +61 -0
  85. package/ai/reference/bf-platform-service.md +17 -15
  86. package/ai/reference/bf-popover-harness.md +42 -0
  87. package/ai/reference/bf-popover-placement.md +28 -0
  88. package/ai/reference/bf-popover-trigger-directive.md +56 -0
  89. package/ai/reference/bf-side-panel-config.md +35 -0
  90. package/ai/reference/bf-side-panel-harness.md +34 -0
  91. package/ai/reference/bf-side-panel-ref.md +44 -0
  92. package/ai/reference/bf-side-panel-service.md +43 -0
  93. package/ai/reference/bf-side-panel-template-context.md +32 -0
  94. package/ai/reference/bf-side-panel-width.md +28 -0
  95. package/ai/reference/bf-size.md +12 -0
  96. package/ai/reference/bf-skeleton-shape.md +28 -0
  97. package/ai/reference/bf-stat-trend-intent.md +27 -0
  98. package/ai/reference/bf-stat-trend.md +27 -0
  99. package/ai/reference/bf-stat-variant.md +26 -0
  100. package/ai/reference/bf-tab-group-harness.md +44 -0
  101. package/ai/reference/bf-table-align.md +26 -0
  102. package/ai/reference/bf-table-cell-context.md +24 -0
  103. package/ai/reference/bf-table-cell-directive.md +22 -0
  104. package/ai/reference/bf-table-column.md +28 -0
  105. package/ai/reference/bf-table-harness.md +77 -0
  106. package/ai/reference/bf-table-row-key.md +21 -0
  107. package/ai/reference/bf-table-sort-direction.md +26 -0
  108. package/ai/reference/bf-table-sort.md +27 -0
  109. package/ai/reference/bf-theme.md +5 -4
  110. package/ai/reference/bf-time-input-harness.md +67 -0
  111. package/ai/reference/bf-time-input-hour-cycle.md +27 -0
  112. package/ai/reference/bf-time-input-picker.md +27 -0
  113. package/ai/reference/bf-tooltip-directive.md +43 -0
  114. package/ai/reference/bf-tooltip-harness.md +43 -0
  115. package/ai/reference/bf-tooltip-placement.md +28 -0
  116. package/ai/reference/bf_color_scheme_storage.md +20 -0
  117. package/ai/reference/bf_side_panel_data.md +17 -0
  118. package/ai/reference/bf_theme_storage.md +3 -1
  119. package/ai/reference/provide-bf-color-scheme.md +20 -0
  120. package/ai/reference/provide-bf-labels.md +12 -4
  121. package/ai/reference/slide-in-from-end.md +24 -0
  122. package/ai/reference/slide-out-to-end.md +24 -0
  123. package/fesm2022/bitforgehq-ui-charts.mjs +3045 -0
  124. package/fesm2022/bitforgehq-ui-charts.mjs.map +1 -0
  125. package/fesm2022/bitforgehq-ui-icons.mjs +107 -3
  126. package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -1
  127. package/fesm2022/bitforgehq-ui-mobile.mjs +14 -59
  128. package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -1
  129. package/fesm2022/bitforgehq-ui-overlay.mjs +6436 -33
  130. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
  131. package/fesm2022/bitforgehq-ui-testing.mjs +618 -1
  132. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
  133. package/fesm2022/bitforgehq-ui.mjs +5092 -153
  134. package/fesm2022/bitforgehq-ui.mjs.map +1 -1
  135. package/llms-full.txt +25177 -14398
  136. package/llms.txt +95 -1
  137. package/package.json +7 -3
  138. package/theme.css +101 -0
  139. package/types/bitforgehq-ui-charts.d.ts +967 -0
  140. package/types/bitforgehq-ui-icons.d.ts +13 -1
  141. package/types/bitforgehq-ui-mobile.d.ts +25 -18
  142. package/types/bitforgehq-ui-overlay.d.ts +1844 -7
  143. package/types/bitforgehq-ui-testing.d.ts +748 -1
  144. package/types/bitforgehq-ui.d.ts +2136 -44
@@ -0,0 +1,363 @@
1
+ # bf-sidebar
2
+
3
+ Vertical app navigation with brand, grouped links, footer and an icon rail mode
4
+
5
+ `BfSidebarComponent` from `@bitforgehq/ui` · Navigation · since 0.3.1
6
+
7
+ ## Example
8
+
9
+ ```ts
10
+ import { Component, computed, signal } from '@angular/core';
11
+ import { RouterLink, RouterLinkActive } from '@angular/router';
12
+ import {
13
+ BfIconButtonComponent,
14
+ BfNavGroupComponent,
15
+ BfNavItemComponent,
16
+ BfSidebarComponent,
17
+ } from '@bitforgehq/ui';
18
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
19
+ import {
20
+ BookOpen,
21
+ Layers,
22
+ type IconNode,
23
+ PanelLeftClose,
24
+ PanelLeftOpen,
25
+ PanelTop,
26
+ Rows3,
27
+ Settings,
28
+ Smartphone,
29
+ } from 'lucide';
30
+
31
+ interface NavLink {
32
+ readonly path: string;
33
+ readonly label: string;
34
+ readonly icon: IconNode;
35
+ readonly badge?: number;
36
+ }
37
+
38
+ /**
39
+ * The bf-sidebar overview.
40
+ *
41
+ * Live demo of `bf-sidebar`: a brand and a collapse toggle in the header, two `bf-nav-group`s of
42
+ * `a[bfNavItem]` router links (this page is the current one, one item has a count badge) and a
43
+ * footer link. The toggle binds `[(collapsed)]`; collapsed, the items show their icon only and
44
+ * name themselves in a tooltip. The result line reports the state.
45
+ */
46
+ @Component({
47
+ selector: 'app-example-sidebar-overview',
48
+ imports: [
49
+ RouterLink,
50
+ RouterLinkActive,
51
+ BfIconComponent,
52
+ BfIconButtonComponent,
53
+ BfNavGroupComponent,
54
+ BfNavItemComponent,
55
+ BfSidebarComponent,
56
+ ],
57
+ template: `
58
+ <div class="border-border bg-bg rounded-card flex h-96 overflow-hidden border">
59
+ <bf-sidebar label="Docs" [(collapsed)]="collapsed">
60
+ <div bfSlot="header" class="flex items-center justify-between gap-2">
61
+ @if (!collapsed()) {
62
+ <span class="text-heading text-text ps-3">Atlas</span>
63
+ }
64
+ <bf-icon-button
65
+ variant="text"
66
+ [label]="collapsed() ? 'Expand sidebar' : 'Collapse sidebar'"
67
+ (clicked)="collapsed.set(!collapsed())"
68
+ >
69
+ <bf-icon [icon]="collapsed() ? panelOpen : panelClose" />
70
+ </bf-icon-button>
71
+ </div>
72
+ <bf-nav-group label="Desktop">
73
+ @for (link of desktop; track link.path) {
74
+ <a
75
+ bfNavItem
76
+ [routerLink]="link.path"
77
+ routerLinkActive
78
+ ariaCurrentWhenActive="page"
79
+ [badge]="link.badge ?? null"
80
+ >
81
+ <bf-icon bfSlot="icon" [icon]="link.icon" />
82
+ {{ link.label }}
83
+ </a>
84
+ }
85
+ </bf-nav-group>
86
+ <bf-nav-group label="Shell">
87
+ @for (link of shell; track link.path) {
88
+ <a
89
+ bfNavItem
90
+ [routerLink]="link.path"
91
+ routerLinkActive
92
+ ariaCurrentWhenActive="page"
93
+ [badge]="link.badge ?? null"
94
+ >
95
+ <bf-icon bfSlot="icon" [icon]="link.icon" />
96
+ {{ link.label }}
97
+ </a>
98
+ }
99
+ </bf-nav-group>
100
+ <a
101
+ bfSlot="footer"
102
+ bfNavItem
103
+ routerLink="/theming"
104
+ routerLinkActive
105
+ ariaCurrentWhenActive="page"
106
+ >
107
+ <bf-icon bfSlot="icon" [icon]="settings" />
108
+ Theming
109
+ </a>
110
+ </bf-sidebar>
111
+ <div class="min-w-0 flex-1 p-4">
112
+ <p data-status aria-live="polite" class="text-caption text-text-muted wrap-anywhere">
113
+ {{ status() }}
114
+ </p>
115
+ </div>
116
+ </div>
117
+ `,
118
+ })
119
+ export class SidebarOverviewExampleComponent {
120
+ readonly collapsed = signal(false);
121
+ readonly status = computed(() =>
122
+ this.collapsed() ? 'Sidebar collapsed to an icon rail.' : 'Sidebar expanded.',
123
+ );
124
+
125
+ protected readonly desktop: readonly NavLink[] = [
126
+ { path: '/components/sidebar', label: 'Sidebar', icon: PanelTop },
127
+ { path: '/components/top-nav', label: 'Top nav', icon: Rows3 },
128
+ { path: '/components/nav-group', label: 'Nav group', icon: Layers },
129
+ ];
130
+ protected readonly shell: readonly NavLink[] = [
131
+ { path: '/components/tabs', label: 'Tabs', icon: Smartphone, badge: 3 },
132
+ { path: '/mobile', label: 'Mobile guide', icon: BookOpen },
133
+ ];
134
+ protected readonly settings = Settings;
135
+ protected readonly panelOpen = PanelLeftOpen;
136
+ protected readonly panelClose = PanelLeftClose;
137
+ }
138
+ ```
139
+
140
+ ## Overview
141
+
142
+ The vertical navigation of a desktop app: a `nav` landmark with a `header` slot (the brand),
143
+ the destinations (`a[bfNavItem]` links, optionally in `bf-nav-group`s) and a `footer` slot.
144
+ `collapsed` turns it into an icon rail; the consumer owns the toggle button and binds
145
+ `[(collapsed)]`. Below a breakpoint hide it (`class="hidden md:flex"`) and drive a
146
+ `bf-menu-button` from the same routes.
147
+
148
+ The vertical navigation of a desktop web app: a `nav` landmark down the inline-start edge with a `header` slot for the brand, the destinations as [`a[bfNavItem]`](https://bitforge-ui.web.app/components/nav-item) links (optionally in titled [`bf-nav-group`](https://bitforge-ui.web.app/components/nav-group)s) and a `footer` slot for settings or the account. `collapsed` turns it into an icon rail. Its horizontal sibling for apps with few sections is [`bf-top-nav`](https://bitforge-ui.web.app/components/top-nav); both share the same item, so the current page, the badge and the focus ring look the same in either.
149
+
150
+ ## When to use
151
+
152
+ Use `bf-sidebar` for the top-level destinations of a desktop web app with more than four or five sections, or with sections that fall into groups. Use [bf-top-nav](https://bitforge-ui.web.app/components/top-nav) instead for an app with a handful of sections, and [bf-segmented](https://bitforge-ui.web.app/components/segmented) to switch a view in place. In the Capacitor shell use [bf-tabs](https://bitforge-ui.web.app/components/tabs) with [bf-tab-bar](https://bitforge-ui.web.app/components/tab-bar) instead.
153
+
154
+ ## Basic usage
155
+
156
+ Put `bfNavItem` on your own `<a routerLink routerLinkActive ariaCurrentWhenActive="page">`: the router sets `aria-current="page"` on the current page's link and the item shows it in the tonal fill. The widget never imports the router. Group the links with `bf-nav-group`, put the brand in `bfSlot="header"` and secondary links in `bfSlot="footer"`. Name the landmark with `label` (it falls back to `provideBfLabels()`'s `navigation`, "Main").
157
+
158
+ ## Collapsing to an icon rail
159
+
160
+ `collapsed` is a model: bind `[(collapsed)]` to your own toggle button, such as a text `bf-icon-button` in the header whose label says what the next press does ("Collapse sidebar", "Expand sidebar"). The widget has no toggle of its own, because the button needs an icon and a widget never imports one. Collapsed, every item shows only its icon, centred in a square; its label stays in the DOM as screen-reader text, so its accessible name does not change, and becomes the link's `title`, the tooltip a pointer user sees on hover. A count badge becomes a small dot that is still announced, and the group headings are hidden visually but still name their groups. The width follows the rail (`w-auto`); expanded the sidebar is `w-60` (240 px).
161
+
162
+ ## Responsive layouts
163
+
164
+ Below a breakpoint the sidebar does not turn itself into a menu: a root widget cannot render `bf-menu`. Keep one array of destinations (ideally derived from your route config) and drive every surface from it: the sidebar from `md` up (`class="hidden md:flex"` on `bf-sidebar`; its own `flex` loses to your `hidden`), and below it a [`bf-menu-button`](https://bitforge-ui.web.app/components/menu-button) of `a[bfMenuItem]` router links (`class="md:hidden"`, from `@bitforgehq/ui/overlay`). Each surface keeps the router's `routerLinkActive`.
165
+
166
+ ```ts
167
+ import { Component } from '@angular/core';
168
+ import { RouterLink, RouterLinkActive } from '@angular/router';
169
+ import { BfNavItemComponent, BfSidebarComponent } from '@bitforgehq/ui';
170
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
171
+ import {
172
+ BfMenuButtonComponent,
173
+ BfMenuComponent,
174
+ BfMenuItemDirective,
175
+ } from '@bitforgehq/ui/overlay';
176
+ import { type IconNode, Layers, Menu, PanelTop, Rows3 } from 'lucide';
177
+
178
+ /** One top-level destination of the app. */
179
+ interface Destination {
180
+ readonly path: string;
181
+ readonly label: string;
182
+ readonly icon: IconNode;
183
+ }
184
+
185
+ /**
186
+ * One route array, two surfaces.
187
+ *
188
+ * The same destinations drive the sidebar from the `md` breakpoint up (`hidden md:flex`) and a
189
+ * `bf-menu-button` of `a[bfMenuItem]` router links below it (`md:hidden`). Each surface keeps
190
+ * the router's `routerLinkActive`; resize the window to switch between them.
191
+ */
192
+ @Component({
193
+ selector: 'app-example-sidebar-responsive',
194
+ imports: [
195
+ RouterLink,
196
+ RouterLinkActive,
197
+ BfIconComponent,
198
+ BfMenuButtonComponent,
199
+ BfMenuComponent,
200
+ BfMenuItemDirective,
201
+ BfNavItemComponent,
202
+ BfSidebarComponent,
203
+ ],
204
+ template: `
205
+ <div class="flex flex-col gap-3 md:flex-row">
206
+ <bf-sidebar label="Desktop components" class="hidden md:flex">
207
+ @for (destination of destinations; track destination.path) {
208
+ <a
209
+ bfNavItem
210
+ [routerLink]="destination.path"
211
+ routerLinkActive
212
+ ariaCurrentWhenActive="page"
213
+ >
214
+ <bf-icon bfSlot="icon" [icon]="destination.icon" />
215
+ {{ destination.label }}
216
+ </a>
217
+ }
218
+ </bf-sidebar>
219
+ <bf-menu-button class="md:hidden" [menu]="navMenu">
220
+ <bf-icon bfSlot="icon-start" [icon]="menu" />
221
+ Sections
222
+ </bf-menu-button>
223
+ <p class="text-caption text-text-muted min-w-0 flex-1">
224
+ The page content sits here, beside the sidebar or under the menu button.
225
+ </p>
226
+ </div>
227
+
228
+ <ng-template #navMenu>
229
+ <bf-menu>
230
+ @for (destination of destinations; track destination.path) {
231
+ <a
232
+ bfMenuItem
233
+ [routerLink]="destination.path"
234
+ routerLinkActive
235
+ ariaCurrentWhenActive="page"
236
+ >{{ destination.label }}</a
237
+ >
238
+ }
239
+ </bf-menu>
240
+ </ng-template>
241
+ `,
242
+ })
243
+ export class SidebarResponsiveExampleComponent {
244
+ /** In an app, derive this from the route config, so a new route appears on every surface. */
245
+ protected readonly destinations: readonly Destination[] = [
246
+ { path: '/components/sidebar', label: 'Sidebar', icon: PanelTop },
247
+ { path: '/components/top-nav', label: 'Top nav', icon: Rows3 },
248
+ { path: '/components/nav-group', label: 'Nav group', icon: Layers },
249
+ ];
250
+ protected readonly menu = Menu;
251
+ }
252
+ ```
253
+
254
+ Inside the Capacitor shell the same array drives the `bf-tab`s of a `bf-tab-bar` in `bf-tabs`; each destination's path is a tab id and a child route of the tabs.
255
+
256
+ ```ts
257
+ import { Component } from '@angular/core';
258
+ import { BfTabBarComponent, BfTabComponent } from '@bitforgehq/ui';
259
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
260
+ import { BfStackOutletComponent, BfTabsComponent } from '@bitforgehq/ui/mobile';
261
+ import { Folder, type IconNode, Inbox, Settings } from 'lucide';
262
+
263
+ /** One top-level destination: `path` is the route under the tabs, and the tab's id. */
264
+ interface Destination {
265
+ readonly path: string;
266
+ readonly label: string;
267
+ readonly icon: IconNode;
268
+ }
269
+
270
+ /**
271
+ * The same destinations inside the Capacitor shell.
272
+ *
273
+ * In the shell the destinations array that drives the desktop sidebar drives a `bf-tab-bar`
274
+ * inside `bf-tabs` instead: each destination's `path` is a child route of `app` and the tab's id.
275
+ * On desktop the same array feeds `a[bfNavItem]` links (`routerLink="/app/{path}"`).
276
+ *
277
+ * @preview none
278
+ */
279
+ @Component({
280
+ selector: 'app-example-sidebar-shell',
281
+ imports: [
282
+ BfIconComponent,
283
+ BfStackOutletComponent,
284
+ BfTabBarComponent,
285
+ BfTabComponent,
286
+ BfTabsComponent,
287
+ ],
288
+ template: `
289
+ <bf-tabs tabsPath="app">
290
+ <bf-stack-outlet />
291
+ <bf-tab-bar>
292
+ @for (destination of destinations; track destination.path) {
293
+ <bf-tab [id]="destination.path" [label]="destination.label"
294
+ ><bf-icon [icon]="destination.icon"
295
+ /></bf-tab>
296
+ }
297
+ </bf-tab-bar>
298
+ </bf-tabs>
299
+ `,
300
+ })
301
+ export class SidebarShellExampleComponent {
302
+ protected readonly destinations: readonly Destination[] = [
303
+ { path: 'inbox', label: 'Inbox', icon: Inbox },
304
+ { path: 'projects', label: 'Projects', icon: Folder },
305
+ { path: 'settings', label: 'Settings', icon: Settings },
306
+ ];
307
+ }
308
+ ```
309
+
310
+ ## Desktop
311
+
312
+ The sidebar is a desktop pattern. Its items take a `surface-muted` fill and the pointer on hover, a `surface-pressed` fill while pressed and the focus ring on keyboard focus only (`focus-visible`); the current page keeps its tonal fill with its own hover and pressed steps. The destinations scroll on their own when they outgrow the height, while the header and footer stay put; a 4 px inset keeps the focus ring inside the scroll box. Item height follows `provideBfDefaults()` (`controlSize`); under a coarse pointer (a touchscreen laptop) every item is at least 44 px tall. Collapsed, the `title` tooltip names each item on hover. A link keeps the browser's behaviour: Ctrl/Cmd-click and a middle click open it in a new tab.
313
+
314
+ ## Theming
315
+
316
+ - Surface: `surface` with a `border` line on its inline-end edge (`border-e`); 12 px padding and 24 px between the header, the groups and the footer.
317
+ - Width: `w-60` (240 px) expanded, the icon rail's own width collapsed.
318
+ - The items' and the groups' tokens are on the [nav item](https://bitforge-ui.web.app/components/nav-item#theming) and [nav group](https://bitforge-ui.web.app/components/nav-group#theming) pages.
319
+
320
+ ## Accessibility
321
+
322
+ `bf-sidebar` renders a native `<nav>` landmark named by `label`, else `provideBfLabels()`'s `navigation` ("Main"); give each landmark on a page a different name. The header, the destinations and the footer all sit inside it, in that DOM order. The current page is the link the router marks with `aria-current="page"`; the tonal fill is never the only cue under forced colours, where an outline marks it. Collapsing changes only what is visible: every item keeps its label as screen-reader text and each group its name, so the accessible names stay the same. The consumer's toggle button is a native button; name it by what it does ("Collapse sidebar") or give it `aria-pressed`, and keep it in the sidebar or right before it.
323
+
324
+ ### Keyboard
325
+
326
+ | Key | Action |
327
+ | ------------------------------------------------ | -------------------------------------------------- |
328
+ | <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves through the header, the links and the footer |
329
+ | <kbd>Enter</kbd> | Follows the focused link |
330
+
331
+ ## Inputs
332
+
333
+ | Name | Type | Default | Description |
334
+ | ----------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
335
+ | `label` | `string` | `''` | Accessible name of the navigation; empty falls back to `provideBfLabels()`'s `navigation` ("Main"). Name each landmark differently when a page has more than one. |
336
+ | `collapsed` | `boolean` | `false` | Collapses the sidebar to an icon rail: items show their icon only, their label stays for screen readers and becomes a tooltip. Bind `[(collapsed)]` to your own toggle button. |
337
+
338
+ ## Slots
339
+
340
+ - `header`: The brand or app switcher above the destinations.
341
+ - `default`: The destinations: `a[bfNavItem]` links and `bf-nav-group`s.
342
+ - `footer`: Content under the destinations, such as settings or the account.
343
+
344
+ ## Notes
345
+
346
+ ### Do
347
+
348
+ - Mark the current page with `routerLinkActive` and `ariaCurrentWhenActive="page"` on every link.
349
+ - Drive the sidebar, the small-screen menu and the shell's tab bar from one array of destinations.
350
+
351
+ ### Don't
352
+
353
+ - Don't put actions (Create, Log out) among the destinations; a sidebar is for places, so put an action in the header or footer as a button.
354
+ - Don't use `bf-sidebar` inside the Capacitor shell; use `bf-tabs` with a `bf-tab-bar` there.
355
+
356
+ ## Migration notes
357
+
358
+ ### 0.3.1
359
+
360
+ New `bf-sidebar`: the vertical navigation of a desktop app, a `nav` landmark with `header`,
361
+ default and `footer` slots, named by `label` (else the new `navigation` label, "Main").
362
+ `collapsed` (a model the app binds to its own toggle button) turns it into an icon rail. Below a
363
+ breakpoint, hide it with `hidden md:flex` and drive a `bf-menu-button` from the same routes.
@@ -0,0 +1,215 @@
1
+ # bf-skeleton
2
+
3
+ Loading placeholder shaped as text lines, a rectangle or a circle, with a shimmer
4
+
5
+ `BfSkeletonComponent` from `@bitforgehq/ui` · Feedback · since 0.3.1
6
+
7
+ ## Example
8
+
9
+ ```ts
10
+ import { Component } from '@angular/core';
11
+ import { BfSkeletonComponent } from '@bitforgehq/ui';
12
+
13
+ /**
14
+ * The bf-skeleton overview.
15
+ *
16
+ * Live demo of `bf-skeleton`: the three shapes (a circle for an avatar, text lines with a shorter
17
+ * last line, a rect for an image) inside a region marked `aria-busy="true"` while it loads.
18
+ */
19
+ @Component({
20
+ selector: 'app-example-skeleton-overview',
21
+ imports: [BfSkeletonComponent],
22
+ template: `
23
+ <div aria-busy="true" class="flex max-w-md flex-col gap-4">
24
+ <div class="flex items-center gap-3">
25
+ <bf-skeleton shape="circle" />
26
+ <div class="flex min-w-0 flex-1 flex-col gap-2">
27
+ <bf-skeleton width="40%" height="0.875rem" />
28
+ <bf-skeleton width="25%" />
29
+ </div>
30
+ </div>
31
+ <bf-skeleton [lines]="3" height="0.875rem" />
32
+ <bf-skeleton shape="rect" height="8rem" />
33
+ </div>
34
+ `,
35
+ })
36
+ export class SkeletonOverviewExampleComponent {}
37
+ ```
38
+
39
+ ## Overview
40
+
41
+ Loading placeholder in the shape of the content to come: text lines, a rectangle or a circle,
42
+ on the sunken surface with a shimmer. It is hidden from assistive technology; mark the region it
43
+ stands in for with `aria-busy="true"` while it loads.
44
+
45
+ A skeleton is a placeholder in the shape of the content that is still loading: a few text lines, a rectangle for an image or a chart, a circle for an avatar. It sits on the `surface-sunken` fill with a soft shimmer, so the page keeps its layout while the data arrives and nothing jumps when it does. `bf-skeleton` is hidden from assistive technology; the region it stands in for says that it is busy. It works the same in both shells: in a desktop web app and in the Capacitor shell.
46
+
47
+ ## When to use
48
+
49
+ Use `bf-skeleton` while a screen or a section loads content whose shape you know: a card, a list, the rows of a table. Use [bf-spinner](https://bitforge-ui.web.app/components/spinner) instead for a short wait with no known shape, or inside a button, and [bf-progress](https://bitforge-ui.web.app/components/progress) when you can say how far the work is. Once loading has failed, replace the skeletons with a [bf-alert](https://bitforge-ui.web.app/components/alert); when it returned nothing, with a [bf-empty-state](https://bitforge-ui.web.app/components/empty-state).
50
+
51
+ ## Shapes and sizes
52
+
53
+ `shape` picks the placeholder. `text` (the default) draws one line, fully rounded; `lines` draws several, the last one shorter, as a paragraph's last line is. `rect` is one bar with the field radius, for an image, a chart or a field. `circle` is a disc for an avatar.
54
+
55
+ `width` and `height` take CSS lengths (`12rem`, `60%`, `48px`). Without a `width` a text or rect skeleton fills its container and a circle is 40 px; a circle's height always follows its width. `height` is the height of each text line (default `0.75rem`) or of the rect (default `6rem`); a circle ignores it. Match the size of the content to come, such as `0.875rem` lines for `text-body`, so the page does not move when it arrives.
56
+
57
+ ## A card while it loads
58
+
59
+ Build the placeholder from the same layout as the loaded card: the same `bf-card`, the same gaps, a skeleton where each piece of content goes. Mark the region the skeletons stand in for with `aria-busy="true"` while it loads, and drop the attribute (or set it to `false`) when the content is in.
60
+
61
+ ```ts
62
+ import { Component } from '@angular/core';
63
+ import { BfAvatarComponent, BfCardComponent, BfSkeletonComponent } from '@bitforgehq/ui';
64
+
65
+ /**
66
+ * A card while it loads, beside the loaded card.
67
+ *
68
+ * The loading card has the loaded one's layout, with a skeleton where each piece of content goes:
69
+ * a 40 px circle for the avatar, two short lines for the name and the role, three lines of text.
70
+ * Only the loading card carries `aria-busy="true"`.
71
+ */
72
+ @Component({
73
+ selector: 'app-example-skeleton-card',
74
+ imports: [BfCardComponent, BfAvatarComponent, BfSkeletonComponent],
75
+ template: `
76
+ <div class="grid grid-cols-[repeat(auto-fit,minmax(15rem,1fr))] gap-4">
77
+ <bf-card aria-busy="true">
78
+ <div class="flex flex-col gap-4">
79
+ <div class="flex items-center gap-3">
80
+ <bf-skeleton shape="circle" />
81
+ <div class="flex min-w-0 flex-1 flex-col gap-2">
82
+ <bf-skeleton width="55%" height="1rem" />
83
+ <bf-skeleton width="35%" height="0.75rem" />
84
+ </div>
85
+ </div>
86
+ <bf-skeleton [lines]="3" height="0.875rem" />
87
+ </div>
88
+ </bf-card>
89
+ <bf-card>
90
+ <div class="flex flex-col gap-4">
91
+ <div class="flex items-center gap-3">
92
+ <bf-avatar initials="MK" />
93
+ <div class="flex min-w-0 flex-col">
94
+ <p class="text-label text-text">Mara Keller</p>
95
+ <p class="text-caption text-text-muted">Design lead</p>
96
+ </div>
97
+ </div>
98
+ <p class="text-body text-text-muted">
99
+ Runs the Atlas rebrand and the design reviews on Thursdays. Ask her about the type
100
+ scale.
101
+ </p>
102
+ </div>
103
+ </bf-card>
104
+ </div>
105
+ `,
106
+ })
107
+ export class SkeletonCardExampleComponent {}
108
+ ```
109
+
110
+ ## Table rows
111
+
112
+ A loading `bf-table` keeps its real header and shows skeleton rows: pass a few placeholder rows and render a `bf-skeleton` in each column through `bfTableCell` templates, with widths that differ from row to row. The header tells the user what is coming; the table's wrapper carries `aria-busy="true"`. In a list, render a few `bf-list-item`s with a circle and two lines the same way.
113
+
114
+ ```ts
115
+ import { Component } from '@angular/core';
116
+ import {
117
+ BfSkeletonComponent,
118
+ BfTableCellDirective,
119
+ BfTableComponent,
120
+ type BfTableColumn,
121
+ } from '@bitforgehq/ui';
122
+
123
+ interface PlaceholderRow {
124
+ id: string;
125
+ /** The width of each column's skeleton in this row, so the rows do not look stamped. */
126
+ widths: Readonly<Record<'title' | 'client' | 'amount', string>>;
127
+ }
128
+
129
+ /**
130
+ * Skeleton rows in a bf-table.
131
+ *
132
+ * The table keeps its real header while the rows load: four placeholder rows render a
133
+ * `bf-skeleton` in every column through `bfTableCell` templates, at widths that differ from row to
134
+ * row, and the wrapper carries `aria-busy="true"`.
135
+ */
136
+ @Component({
137
+ selector: 'app-example-skeleton-table',
138
+ imports: [BfTableComponent, BfTableCellDirective, BfSkeletonComponent],
139
+ template: `
140
+ <div aria-busy="true">
141
+ <bf-table label="Invoices" [rows]="rows" [columns]="columns">
142
+ <ng-template bfTableCell="title" [bfTableCellOf]="rows" let-row>
143
+ <bf-skeleton [width]="row.widths.title" height="0.875rem" />
144
+ </ng-template>
145
+ <ng-template bfTableCell="client" [bfTableCellOf]="rows" let-row>
146
+ <bf-skeleton [width]="row.widths.client" height="0.875rem" />
147
+ </ng-template>
148
+ <ng-template bfTableCell="amount" [bfTableCellOf]="rows" let-row>
149
+ <bf-skeleton class="ms-auto" [width]="row.widths.amount" height="0.875rem" />
150
+ </ng-template>
151
+ </bf-table>
152
+ </div>
153
+ `,
154
+ })
155
+ export class SkeletonTableExampleComponent {
156
+ readonly columns: readonly BfTableColumn[] = [
157
+ { key: 'title', label: 'Invoice' },
158
+ { key: 'client', label: 'Client' },
159
+ { key: 'amount', label: 'Amount', align: 'end' },
160
+ ];
161
+ readonly rows: readonly PlaceholderRow[] = [
162
+ { id: 'a', widths: { title: '70%', client: '55%', amount: '4rem' } },
163
+ { id: 'b', widths: { title: '55%', client: '70%', amount: '3rem' } },
164
+ { id: 'c', widths: { title: '80%', client: '45%', amount: '4.5rem' } },
165
+ { id: 'd', widths: { title: '60%', client: '60%', amount: '3.5rem' } },
166
+ ];
167
+ }
168
+ ```
169
+
170
+ ## Desktop
171
+
172
+ A skeleton is not interactive: no hover, no pointer cursor, no focus stop, and its size does not follow `provideBfDefaults()`. On desktop and in the Capacitor shell it looks and behaves the same. Show skeletons only for a wait that lasts: when a load usually finishes in a few hundred milliseconds, start showing them after a short delay (a timer in the state service), so a fast connection never sees a flash of placeholders.
173
+
174
+ ## Theming
175
+
176
+ - Fill: `surface-sunken`. Shimmer: a `surface` highlight at 70 % opacity that sweeps across from the inline start, with `--animate-skeleton-shimmer` (1.6 s, ease-in-out, endless) and `--animate-skeleton-shimmer-rtl` under `dir="rtl"`.
177
+ - Radius: text lines and the circle `rounded-full`; the rect `rounded-field`.
178
+ - Under `prefers-reduced-motion: reduce` the shimmer is hidden and the placeholder stays still. Under forced colours a `GrayText` border draws the shape, as backgrounds are dropped there.
179
+
180
+ ## Accessibility
181
+
182
+ Every `bf-skeleton` carries `aria-hidden="true"`: a placeholder has nothing to say, and a screen reader that read out a row of shapes would only make noise. Say that the content is loading on the region it stands in for instead: `aria-busy="true"` on that element tells assistive technology the region is being updated, and dropping it lets the finished content be read. For a load the user started and waits for (a search), also put a short polite status in the page, such as "Loading results", from a `role="status"` element or a `bf-spinner`, which announces its `label` (default "Loading"). The skeleton takes no focus, so keyboard focus stays where the user left it.
183
+
184
+ ## Inputs
185
+
186
+ | Name | Type | Default | Description |
187
+ | -------- | -------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
188
+ | `shape` | `BfSkeletonShape` (`'text' \| 'rect' \| 'circle'`) | `'text'` | What it stands in for: `text` lines (default), a `rect` (an image, a chart) or a `circle` (an avatar). |
189
+ | `lines` | `number` | `1` | How many lines a `text` skeleton draws, the last one shorter when there are several; ignored by the other shapes. |
190
+ | `width` | `string` | `''` | Width as a CSS length (`12rem`, `60%`); empty fills the container, a circle is 40 px. A circle's height follows its width. |
191
+ | `height` | `string` | `''` | Height as a CSS length: of each line of `text` (default `0.75rem`) or of the `rect` (default `6rem`); a circle ignores it. |
192
+
193
+ ## Notes
194
+
195
+ ### Do
196
+
197
+ - Shape the skeleton like the content to come, at its size, so nothing jumps when it loads.
198
+ - Put `aria-busy="true"` on the loading region, and remove it once the content is there.
199
+ - Show a few rows or cards, not a whole page of them.
200
+
201
+ ### Don't
202
+
203
+ - Don't show a skeleton for a wait that has no known shape; use a `bf-spinner`.
204
+ - Don't leave skeletons up after a load fails or returns nothing; show the error or the empty state.
205
+
206
+ ## Migration notes
207
+
208
+ ### 0.3.1
209
+
210
+ New `bf-skeleton` in `@bitforgehq/ui`, a loading placeholder for both shells: `shape` (the new
211
+ `BfSkeletonShape`: `text` lines, a `rect` or a `circle`), `lines` for several text lines with a
212
+ shorter last one, and `width` / `height` as CSS lengths. It draws the `surface-sunken` fill with a
213
+ `surface` shimmer that sweeps from the inline start (hidden under reduced motion) and carries
214
+ `aria-hidden="true"`; the docs mark the loading region with `aria-busy="true"` and show a card and
215
+ a table of skeleton rows.