@bitforgehq/ui 0.3.1 → 0.3.3
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 +157 -0
- package/README.md +9 -8
- package/ai/MANIFEST.index.json +18 -0
- package/ai/MANIFEST.json +380 -31
- package/ai/REFERENCE.index.json +63 -0
- package/ai/REFERENCE.json +265 -7
- package/ai/UI_GUIDE.md +59 -33
- package/ai/components/app-frame.md +335 -0
- package/ai/components/card.md +56 -2
- package/ai/components/empty-state.md +99 -6
- package/ai/components/header.md +14 -1
- package/ai/components/sidebar-toggle.md +287 -0
- package/ai/components/sidebar.md +188 -10
- package/ai/components/stat.md +74 -10
- package/ai/components/table.md +152 -5
- package/ai/components/tabs.md +5 -1
- package/ai/components/toolbar.md +69 -4
- package/ai/reference/bf-app-frame-breakpoint.md +27 -0
- package/ai/reference/bf-app-frame-content-width.md +29 -0
- package/ai/reference/bf-empty-state-heading-level.md +1 -1
- package/ai/reference/bf-empty-state-heading.md +33 -0
- package/ai/reference/bf-labels.md +7 -0
- package/ai/reference/bf-select-harness.md +4 -0
- package/ai/reference/bf-sidebar-state-service.md +21 -0
- package/ai/reference/bf-sidebar-toggle-harness.md +32 -0
- package/ai/reference/bf-size.md +1 -0
- package/ai/reference/bf-stat-appearance.md +33 -0
- package/ai/reference/bf-stat-variant.md +1 -1
- package/ai/reference/bf-table-harness.md +7 -1
- package/ai/reference/bf-table-stack.md +29 -0
- package/ai/reference/bf_sidebar_state_storage.md +20 -0
- package/ai/reference/provide-bf-labels.md +4 -3
- package/ai/reference/provide-bf-sidebar-state.md +20 -0
- package/bin/bf-ui.js +433 -3
- package/fesm2022/bitforgehq-ui-mobile.mjs +11 -2
- package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-overlay.mjs +571 -446
- package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-testing.mjs +22 -2
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui.mjs +777 -127
- package/fesm2022/bitforgehq-ui.mjs.map +1 -1
- package/llms-full.txt +1605 -43
- package/llms.txt +11 -0
- package/package.json +1 -1
- package/theme.css +13 -0
- package/types/bitforgehq-ui-overlay.d.ts +51 -0
- package/types/bitforgehq-ui-testing.d.ts +49 -1
- package/types/bitforgehq-ui.d.ts +284 -20
package/ai/components/sidebar.md
CHANGED
|
@@ -141,9 +141,9 @@ export class SidebarOverviewExampleComponent {
|
|
|
141
141
|
|
|
142
142
|
The vertical navigation of a desktop app: a `nav` landmark with a `header` slot (the brand),
|
|
143
143
|
the destinations (`a[bfNavItem]` links, optionally in `bf-nav-group`s) and a `footer` slot.
|
|
144
|
-
`collapsed` turns it into an icon rail
|
|
145
|
-
`[(collapsed)]
|
|
146
|
-
`bf-menu-button` from the same routes.
|
|
144
|
+
`collapsed` turns it into an icon rail: put a `bf-sidebar-toggle` (`@bitforgehq/ui/overlay`) in
|
|
145
|
+
it, or bind `[(collapsed)]` to your own button. Below a breakpoint hide it
|
|
146
|
+
(`class="hidden md:flex"`) and drive a `bf-menu-button` from the same routes.
|
|
147
147
|
|
|
148
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
149
|
|
|
@@ -157,11 +157,178 @@ Put `bfNavItem` on your own `<a routerLink routerLinkActive ariaCurrentWhenActiv
|
|
|
157
157
|
|
|
158
158
|
## Collapsing to an icon rail
|
|
159
159
|
|
|
160
|
-
`collapsed` is a model
|
|
160
|
+
`collapsed` is a model. Put a [`bf-sidebar-toggle`](https://bitforge-ui.web.app/components/sidebar-toggle) (`@bitforgehq/ui/overlay`) in the header or footer: it flips the model, names itself by what the next press does ("Collapse sidebar", "Expand sidebar") and takes its two icons as slots, since a widget never imports one. Bind `[(collapsed)]` to keep the state in your own signal, or bind it to your own button outside the sidebar. 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
|
+
## Remember the collapsed sidebar
|
|
163
|
+
|
|
164
|
+
`BfSidebarStateService` (from `@bitforgehq/ui`) holds a `collapsed` signal: bind the sidebar's `[(collapsed)]` to it (`sidebarState.collapsed`) and add `provideBfSidebarState()` to the app's providers, and every change is stored under `bf-sidebar-collapsed` (`localStorage` in the browser, `BF_SIDEBAR_STATE_STORAGE` to keep it elsewhere) and read back at the next start, so the app opens as the user left it. Without the provider the signal still holds the state but nothing is stored; on the server it starts expanded and reads nothing. The widget itself injects no service.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { Component, inject } from '@angular/core';
|
|
168
|
+
import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router';
|
|
169
|
+
import { BfNavItemComponent, BfSidebarComponent, BfSidebarStateService } from '@bitforgehq/ui';
|
|
170
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
171
|
+
import { BfSidebarToggleComponent } from '@bitforgehq/ui/overlay';
|
|
172
|
+
import { Inbox, PanelLeftClose, PanelLeftOpen } from 'lucide';
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* A sidebar that opens collapsed or expanded, as the user left it.
|
|
176
|
+
*
|
|
177
|
+
* `[(collapsed)]` binds `BfSidebarStateService.collapsed`; with `provideBfSidebarState()` in the
|
|
178
|
+
* app's providers every change is stored under `bf-sidebar-collapsed` (`localStorage` in the
|
|
179
|
+
* browser) and read back at the next start.
|
|
180
|
+
*
|
|
181
|
+
* @preview none
|
|
182
|
+
*/
|
|
183
|
+
@Component({
|
|
184
|
+
selector: 'app-example-sidebar-remembered',
|
|
185
|
+
imports: [
|
|
186
|
+
RouterLink,
|
|
187
|
+
RouterLinkActive,
|
|
188
|
+
RouterOutlet,
|
|
189
|
+
BfIconComponent,
|
|
190
|
+
BfNavItemComponent,
|
|
191
|
+
BfSidebarComponent,
|
|
192
|
+
BfSidebarToggleComponent,
|
|
193
|
+
],
|
|
194
|
+
template: `
|
|
195
|
+
<div class="flex min-h-dvh">
|
|
196
|
+
<bf-sidebar [(collapsed)]="sidebarState.collapsed" class="sticky top-0 h-dvh">
|
|
197
|
+
<a bfNavItem routerLink="/inbox" routerLinkActive ariaCurrentWhenActive="page">
|
|
198
|
+
<bf-icon bfSlot="icon" [icon]="inbox" />
|
|
199
|
+
Inbox
|
|
200
|
+
</a>
|
|
201
|
+
<bf-sidebar-toggle bfSlot="footer">
|
|
202
|
+
<bf-icon bfSlot="collapse-icon" [icon]="panelClose" />
|
|
203
|
+
<bf-icon bfSlot="expand-icon" [icon]="panelOpen" />
|
|
204
|
+
</bf-sidebar-toggle>
|
|
205
|
+
</bf-sidebar>
|
|
206
|
+
<main class="min-w-0 flex-1 p-6"><router-outlet /></main>
|
|
207
|
+
</div>
|
|
208
|
+
`,
|
|
209
|
+
})
|
|
210
|
+
export class SidebarRememberedExampleComponent {
|
|
211
|
+
// app.config.ts: providers: [provideBfSidebarState()]
|
|
212
|
+
protected readonly sidebarState = inject(BfSidebarStateService);
|
|
213
|
+
protected readonly inbox = Inbox;
|
|
214
|
+
protected readonly panelOpen = PanelLeftOpen;
|
|
215
|
+
protected readonly panelClose = PanelLeftClose;
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Footer
|
|
220
|
+
|
|
221
|
+
The footer slot takes several elements, stacked with a 4 px gap: a Settings link as an `a[bfNavItem]`, a row holding an icon-only text [`bf-theme-menu-button`](https://bitforge-ui.web.app/components/theme-menu-button) and the `bf-sidebar-toggle`, and an optional version line in `text-caption text-text-muted`. Expanded, the row puts the two buttons at either end (`flex items-center justify-between`). As an icon rail every footer element is centred: switch the row to a column (`flex flex-col items-center gap-1`) so both buttons fit the rail, and hide the version line (`[class.hidden]="collapsed()"`), which has no icon to shrink to. Open the theme menu upwards (`placement="above-start"`), since the footer sits at the bottom of the screen.
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
import { Component, computed, signal } from '@angular/core';
|
|
225
|
+
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
226
|
+
import { type BfColorScheme, BfNavItemComponent, BfSidebarComponent } from '@bitforgehq/ui';
|
|
227
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
228
|
+
import { BfSidebarToggleComponent, BfThemeMenuButtonComponent } from '@bitforgehq/ui/overlay';
|
|
229
|
+
import {
|
|
230
|
+
Inbox,
|
|
231
|
+
type IconNode,
|
|
232
|
+
Moon,
|
|
233
|
+
PanelLeftClose,
|
|
234
|
+
PanelLeftOpen,
|
|
235
|
+
Settings,
|
|
236
|
+
Sun,
|
|
237
|
+
SunMoon,
|
|
238
|
+
Users,
|
|
239
|
+
} from 'lucide';
|
|
240
|
+
|
|
241
|
+
interface NavLink {
|
|
242
|
+
readonly path: string;
|
|
243
|
+
readonly label: string;
|
|
244
|
+
readonly icon: IconNode;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* A sidebar footer: settings, theme, toggle and version.
|
|
249
|
+
*
|
|
250
|
+
* The footer slot holds a Settings link (`a[bfNavItem]`), a row with an icon-only
|
|
251
|
+
* `bf-theme-menu-button` and the `bf-sidebar-toggle`, and a version line. Expanded, the two
|
|
252
|
+
* buttons sit at either end of the row; as an icon rail the row stacks vertically and the version
|
|
253
|
+
* line hides. The theme binds a local signal so the docs site keeps its theme; an app binds
|
|
254
|
+
* `BfColorSchemeService.scheme`.
|
|
255
|
+
*/
|
|
256
|
+
@Component({
|
|
257
|
+
selector: 'app-example-sidebar-footer',
|
|
258
|
+
imports: [
|
|
259
|
+
RouterLink,
|
|
260
|
+
RouterLinkActive,
|
|
261
|
+
BfIconComponent,
|
|
262
|
+
BfNavItemComponent,
|
|
263
|
+
BfSidebarComponent,
|
|
264
|
+
BfSidebarToggleComponent,
|
|
265
|
+
BfThemeMenuButtonComponent,
|
|
266
|
+
],
|
|
267
|
+
template: `
|
|
268
|
+
<div class="border-border bg-bg rounded-card flex h-[26rem] overflow-hidden border">
|
|
269
|
+
<bf-sidebar label="Footer demo" [(collapsed)]="collapsed">
|
|
270
|
+
@for (link of links; track link.path) {
|
|
271
|
+
<a bfNavItem [routerLink]="link.path" routerLinkActive ariaCurrentWhenActive="page">
|
|
272
|
+
<bf-icon bfSlot="icon" [icon]="link.icon" />
|
|
273
|
+
{{ link.label }}
|
|
274
|
+
</a>
|
|
275
|
+
}
|
|
276
|
+
<a
|
|
277
|
+
bfSlot="footer"
|
|
278
|
+
bfNavItem
|
|
279
|
+
routerLink="/theming"
|
|
280
|
+
routerLinkActive
|
|
281
|
+
ariaCurrentWhenActive="page"
|
|
282
|
+
>
|
|
283
|
+
<bf-icon bfSlot="icon" [icon]="settings" />
|
|
284
|
+
Settings
|
|
285
|
+
</a>
|
|
286
|
+
<div bfSlot="footer" [class]="rowClasses()">
|
|
287
|
+
<bf-theme-menu-button iconOnly variant="text" placement="above-start" [(value)]="scheme">
|
|
288
|
+
<bf-icon bfSlot="icon" [icon]="themeIcon()" />
|
|
289
|
+
</bf-theme-menu-button>
|
|
290
|
+
<bf-sidebar-toggle>
|
|
291
|
+
<bf-icon bfSlot="collapse-icon" [icon]="panelClose" />
|
|
292
|
+
<bf-icon bfSlot="expand-icon" [icon]="panelOpen" />
|
|
293
|
+
</bf-sidebar-toggle>
|
|
294
|
+
</div>
|
|
295
|
+
<p bfSlot="footer" class="text-caption text-text-muted ps-3" [class.hidden]="collapsed()">
|
|
296
|
+
Version 0.3.2
|
|
297
|
+
</p>
|
|
298
|
+
</bf-sidebar>
|
|
299
|
+
<div class="min-w-0 flex-1 p-4">
|
|
300
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted wrap-anywhere">
|
|
301
|
+
{{ collapsed() ? 'Icon rail' : 'Expanded' }}, theme {{ scheme() }}.
|
|
302
|
+
</p>
|
|
303
|
+
</div>
|
|
304
|
+
</div>
|
|
305
|
+
`,
|
|
306
|
+
})
|
|
307
|
+
export class SidebarFooterExampleComponent {
|
|
308
|
+
readonly collapsed = signal(false);
|
|
309
|
+
/** Stands in for `BfColorSchemeService.scheme`. */
|
|
310
|
+
readonly scheme = signal<BfColorScheme>('system');
|
|
311
|
+
|
|
312
|
+
/** Expanded: one row, the buttons at either end. Collapsed: a column in the rail. */
|
|
313
|
+
protected readonly rowClasses = computed(() =>
|
|
314
|
+
this.collapsed() ? 'flex flex-col items-center gap-1' : 'flex items-center justify-between',
|
|
315
|
+
);
|
|
316
|
+
protected readonly themeIcon = computed(
|
|
317
|
+
() => ({ system: SunMoon, light: Sun, dark: Moon })[this.scheme()],
|
|
318
|
+
);
|
|
319
|
+
protected readonly links: readonly NavLink[] = [
|
|
320
|
+
{ path: '/components/nav-item', label: 'Inbox', icon: Inbox },
|
|
321
|
+
{ path: '/components/nav-group', label: 'Team', icon: Users },
|
|
322
|
+
];
|
|
323
|
+
protected readonly settings = Settings;
|
|
324
|
+
protected readonly panelOpen = PanelLeftOpen;
|
|
325
|
+
protected readonly panelClose = PanelLeftClose;
|
|
326
|
+
}
|
|
327
|
+
```
|
|
161
328
|
|
|
162
329
|
## Responsive layouts
|
|
163
330
|
|
|
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`.
|
|
331
|
+
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`. [`bf-app-frame`](https://bitforge-ui.web.app/components/app-frame) does this layout for you: it shows the sidebar in a sticky column from the breakpoint up and the menu button in a header bar below it.
|
|
165
332
|
|
|
166
333
|
```ts
|
|
167
334
|
import { Component } from '@angular/core';
|
|
@@ -319,7 +486,7 @@ The sidebar is a desktop pattern. Its items take a `surface-muted` fill and the
|
|
|
319
486
|
|
|
320
487
|
## Accessibility
|
|
321
488
|
|
|
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.
|
|
489
|
+
`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. `bf-sidebar-toggle` is a native button named by what it does ("Collapse sidebar"), with `aria-expanded` and `aria-controls` on the `nav`; your own toggle should do the same, and sit in the sidebar or right before it.
|
|
323
490
|
|
|
324
491
|
### Keyboard
|
|
325
492
|
|
|
@@ -330,10 +497,10 @@ The sidebar is a desktop pattern. Its items take a `surface-muted` fill and the
|
|
|
330
497
|
|
|
331
498
|
## Inputs
|
|
332
499
|
|
|
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.
|
|
500
|
+
| Name | Type | Default | Description |
|
|
501
|
+
| ----------- | --------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
502
|
+
| `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. |
|
|
503
|
+
| `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. A `bf-sidebar-toggle` inside flips it; bind `[(collapsed)]` to keep it in your state. |
|
|
337
504
|
|
|
338
505
|
## Slots
|
|
339
506
|
|
|
@@ -347,6 +514,7 @@ The sidebar is a desktop pattern. Its items take a `surface-muted` fill and the
|
|
|
347
514
|
|
|
348
515
|
- Mark the current page with `routerLinkActive` and `ariaCurrentWhenActive="page"` on every link.
|
|
349
516
|
- Drive the sidebar, the small-screen menu and the shell's tab bar from one array of destinations.
|
|
517
|
+
- Remember the collapsed state with `BfSidebarStateService` and `provideBfSidebarState()`.
|
|
350
518
|
|
|
351
519
|
### Don't
|
|
352
520
|
|
|
@@ -355,6 +523,16 @@ The sidebar is a desktop pattern. Its items take a `surface-muted` fill and the
|
|
|
355
523
|
|
|
356
524
|
## Migration notes
|
|
357
525
|
|
|
526
|
+
### 0.3.2
|
|
527
|
+
|
|
528
|
+
New `BfSidebarStateService` (a `collapsed` signal) and `provideBfSidebarState()` in
|
|
529
|
+
`@bitforgehq/ui`: bind the sidebar's `[(collapsed)]` to `sidebarState.collapsed` and the provider
|
|
530
|
+
stores every change under `bf-sidebar-collapsed` in `BF_SIDEBAR_STATE_STORAGE` (`localStorage` in
|
|
531
|
+
the browser, memory elsewhere), read back at the next start; without the provider nothing is
|
|
532
|
+
written, and on the server nothing is read. The sidebar's `nav` now has an id, for a toggle's
|
|
533
|
+
`aria-controls`. The docs gain "Remember the collapsed sidebar" and a "Footer" composition
|
|
534
|
+
(Settings link, theme menu, toggle, version line) that works expanded and as an icon rail.
|
|
535
|
+
|
|
358
536
|
### 0.3.1
|
|
359
537
|
|
|
360
538
|
New `bf-sidebar`: the vertical navigation of a desktop app, a `nav` landmark with `header`,
|
package/ai/components/stat.md
CHANGED
|
@@ -61,7 +61,7 @@ Use `bf-stat` for a few headline figures the reader should take in at a glance,
|
|
|
61
61
|
|
|
62
62
|
## Cards
|
|
63
63
|
|
|
64
|
-
`variant="card"` gives each stat its own outlined tile, the `flat` card surface. In a grid the cards stretch to the row's height, so a row of stats lines up even when one has a supporting line and another has none. Put an icon in the `icon` slot (`bfSlot="icon"`, a `bf-icon`) to show what the figure is about.
|
|
64
|
+
`variant="card"` gives each stat its own outlined tile, the `flat` card surface, for stats that sit on the page itself; inside a raised card use `tile` instead. In a grid the cards stretch to the row's height, so a row of stats lines up even when one has a supporting line and another has none. Put an icon in the `icon` slot (`bfSlot="icon"`, a `bf-icon`) to show what the figure is about.
|
|
65
65
|
|
|
66
66
|
```ts
|
|
67
67
|
import { Component } from '@angular/core';
|
|
@@ -103,6 +103,60 @@ export class StatOverviewExampleComponent {
|
|
|
103
103
|
}
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
+
## Tiles
|
|
107
|
+
|
|
108
|
+
`variant="tile"` is a sunken, borderless tile (`surface-sunken`, the `card` radius and padding) for a row of stats inside a raised [bf-card](https://bitforge-ui.web.app/components/card): a raised card holds sunken tiles, never outlined ones. On a tile the icon tile turns `surface`, so it still shows. Keep one surface style per level of a dashboard: raised cards on the page, tiles in each card.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { Component } from '@angular/core';
|
|
112
|
+
import { BfCardComponent, BfStatComponent } from '@bitforgehq/ui';
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A dashboard with one surface per level.
|
|
116
|
+
*
|
|
117
|
+
* The page holds raised cards, and every card holds sunken stat tiles: each level has one surface
|
|
118
|
+
* style, so the eye reads the cards as the groups and the tiles as their figures.
|
|
119
|
+
*/
|
|
120
|
+
@Component({
|
|
121
|
+
selector: 'app-example-stat-dashboard',
|
|
122
|
+
imports: [BfCardComponent, BfStatComponent],
|
|
123
|
+
template: `
|
|
124
|
+
<div class="grid gap-4 @lg:grid-cols-2">
|
|
125
|
+
<bf-card>
|
|
126
|
+
<h3 class="text-heading">Sales</h3>
|
|
127
|
+
<div class="grid grid-cols-[repeat(auto-fit,minmax(9rem,1fr))] gap-3">
|
|
128
|
+
<bf-stat label="Revenue" value="€48,200" variant="tile" delta="+12%" trend="up">
|
|
129
|
+
vs. last month
|
|
130
|
+
</bf-stat>
|
|
131
|
+
<bf-stat label="Orders" value="1,204" variant="tile" delta="+3%" trend="up">
|
|
132
|
+
vs. last month
|
|
133
|
+
</bf-stat>
|
|
134
|
+
</div>
|
|
135
|
+
</bf-card>
|
|
136
|
+
<bf-card>
|
|
137
|
+
<h3 class="text-heading">Support</h3>
|
|
138
|
+
<div class="grid grid-cols-[repeat(auto-fit,minmax(9rem,1fr))] gap-3">
|
|
139
|
+
<bf-stat
|
|
140
|
+
label="Open tickets"
|
|
141
|
+
value="18"
|
|
142
|
+
variant="tile"
|
|
143
|
+
delta="-4"
|
|
144
|
+
trend="down"
|
|
145
|
+
trendIntent="negative"
|
|
146
|
+
>
|
|
147
|
+
vs. last week
|
|
148
|
+
</bf-stat>
|
|
149
|
+
<bf-stat label="First reply" value="2 h" variant="tile" delta="0" trend="flat">
|
|
150
|
+
vs. last week
|
|
151
|
+
</bf-stat>
|
|
152
|
+
</div>
|
|
153
|
+
</bf-card>
|
|
154
|
+
</div>
|
|
155
|
+
`,
|
|
156
|
+
})
|
|
157
|
+
export class StatDashboardExampleComponent {}
|
|
158
|
+
```
|
|
159
|
+
|
|
106
160
|
## Plain
|
|
107
161
|
|
|
108
162
|
`variant="plain"` (the default) draws no surface, for stats that sit on a card or a page section of their own. The default slot is the supporting line under the value: a short trend ("+12% this week") or a `bf-badge`.
|
|
@@ -182,8 +236,9 @@ A stat is not interactive: no hover, no pointer cursor, no focus stop. Its size
|
|
|
182
236
|
- Label (`dt`): `text-caption` in `text-muted`. Value (`dd`): `text-title` in `text`, with tabular figures (`tabular-nums`) so numbers line up across stats.
|
|
183
237
|
- Supporting line: `text-caption` in `text-muted`.
|
|
184
238
|
- Delta pill: the badge's label shape (`control` radius, `text-micro` bold, tabular figures); good changes `success-soft` with `success` text, bad ones `danger-soft` with `danger` text, flat or no trend `neutral-soft` with `neutral` text. The arrow mark is drawn with borders in `currentColor`, so it takes the pill's text colour.
|
|
185
|
-
- Icon tile: `surface-sunken` background, `text-muted` icon, `tile` radius, 40 px.
|
|
239
|
+
- Icon tile: `surface-sunken` background (`surface` on a `tile` stat), `text-muted` icon, `tile` radius, 40 px.
|
|
186
240
|
- `card`: `surface`, the `card` radius, `--spacing-card` padding (`p-card`) and a 1.5 px `border-strong` edge, as the `flat` `bf-card`.
|
|
241
|
+
- `tile`: `surface-sunken`, the `card` radius and `--spacing-card` padding, no border.
|
|
187
242
|
|
|
188
243
|
## Accessibility
|
|
189
244
|
|
|
@@ -191,14 +246,14 @@ A stat is not interactive: no hover, no pointer cursor, no focus stop. Its size
|
|
|
191
246
|
|
|
192
247
|
## Inputs
|
|
193
248
|
|
|
194
|
-
| Name | Type | Default | Description
|
|
195
|
-
| ------------- | -------------------------------------------------------------------- | ------------ |
|
|
196
|
-
| `label` | `string` | required | What the figure measures, shown above it.
|
|
197
|
-
| `value` | `string` | required | The figure, already formatted (`45 min`, `1,204`, `98%`).
|
|
198
|
-
| `variant` | `
|
|
199
|
-
| `delta` | `string` | `''` | A change shown in a pill beside the value, already formatted (`+12%`); empty shows none.
|
|
200
|
-
| `trend` | `BfStatTrend \| undefined` (`'up' \| 'down' \| 'flat' \| undefined`) | `undefined` | The delta's direction: an arrow mark and a hidden word; left unset, the pill is neutral.
|
|
201
|
-
| `trendIntent` | `BfStatTrendIntent` (`'positive' \| 'negative'`) | `'positive'` | Whether up is good (positive, default) or down is good (negative, such as costs).
|
|
249
|
+
| Name | Type | Default | Description |
|
|
250
|
+
| ------------- | -------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------- |
|
|
251
|
+
| `label` | `string` | required | What the figure measures, shown above it. |
|
|
252
|
+
| `value` | `string` | required | The figure, already formatted (`45 min`, `1,204`, `98%`). |
|
|
253
|
+
| `variant` | `BfStatAppearance` (`'plain' \| 'card' \| 'tile'`) | `'plain'` | Surface: plain (default) on its container, card (an outlined tile) or tile (sunken, no border). |
|
|
254
|
+
| `delta` | `string` | `''` | A change shown in a pill beside the value, already formatted (`+12%`); empty shows none. |
|
|
255
|
+
| `trend` | `BfStatTrend \| undefined` (`'up' \| 'down' \| 'flat' \| undefined`) | `undefined` | The delta's direction: an arrow mark and a hidden word; left unset, the pill is neutral. |
|
|
256
|
+
| `trendIntent` | `BfStatTrendIntent` (`'positive' \| 'negative'`) | `'positive'` | Whether up is good (positive, default) or down is good (negative, such as costs). |
|
|
202
257
|
|
|
203
258
|
## Slots
|
|
204
259
|
|
|
@@ -215,12 +270,21 @@ A stat is not interactive: no hover, no pointer cursor, no focus stop. Its size
|
|
|
215
270
|
|
|
216
271
|
### Don't
|
|
217
272
|
|
|
273
|
+
- Don't put `card` stats (outlined) inside a raised `bf-card`; use `tile`.
|
|
218
274
|
- Don't use a stat for a value the user edits or acts on; give it its own control.
|
|
219
275
|
- Don't put long text in `value`; it is set at the `title` size for a short figure.
|
|
220
276
|
- Don't put the comparison period in `delta`; keep it to the change (`+12%`) and say "vs. last month" in the supporting line.
|
|
221
277
|
|
|
222
278
|
## Migration notes
|
|
223
279
|
|
|
280
|
+
### 0.3.2
|
|
281
|
+
|
|
282
|
+
New `variant="tile"`: a sunken, borderless tile (`surface-sunken`, the `card` radius and
|
|
283
|
+
padding) for a row of stats inside a raised `bf-card`; on it the icon tile turns `surface`.
|
|
284
|
+
`variant` now takes the new named type `BfStatAppearance` (`BfStatVariant | 'tile'`); the
|
|
285
|
+
released `BfStatVariant` keeps its two members, so `Record`s over it keep compiling. New
|
|
286
|
+
`stat/dashboard` example with one surface per level.
|
|
287
|
+
|
|
224
288
|
### 0.3.1
|
|
225
289
|
|
|
226
290
|
New: `bf-stat`, one labelled figure in its own `dl`, with the value at the `title` size in
|
package/ai/components/table.md
CHANGED
|
@@ -103,13 +103,14 @@ export class TableOverviewExampleComponent {
|
|
|
103
103
|
|
|
104
104
|
Data table for desktop: column headers that set a sort, optional row selection with
|
|
105
105
|
checkboxes, custom cells from `bfTableCell` templates and an empty slot. It never sorts or
|
|
106
|
-
filters itself; the consumer sorts the rows in a `computed()` from `sort`.
|
|
106
|
+
filters itself; the consumer sorts the rows in a `computed()` from `sort`. With `stack` it
|
|
107
|
+
renders each row as a tile of label/value pairs in a narrow space or under touch.
|
|
107
108
|
|
|
108
109
|
A data table for desktop pages: rows of records the user compares across columns, with headers that sort, an optional checkbox column that selects rows, custom cells and an empty state. `bf-table` is data-driven: you pass `rows` and `columns`, and it never sorts or filters the rows itself. Its headers set the `sort` model and you sort the rows in a `computed()`, on the client or on the server.
|
|
109
110
|
|
|
110
111
|
## When to use
|
|
111
112
|
|
|
112
|
-
Use `bf-table` on a desktop page for records the user scans and compares column by column: tasks, invoices, members. On a phone or in the Capacitor shell use [bf-list](https://bitforge-ui.web.app/components/list) rows instead. Use a [bf-list](https://bitforge-ui.web.app/components/list) on desktop too when each record is one line of text with a status, and a description list for the fields of a single record.
|
|
113
|
+
Use `bf-table` on a desktop page for records the user scans and compares column by column: tasks, invoices, members. On a phone or in the Capacitor shell use [bf-list](https://bitforge-ui.web.app/components/list) rows instead, or let the table stack its rows into tiles with `stack` (see Stacked rows). Use a [bf-list](https://bitforge-ui.web.app/components/list) on desktop too when each record is one line of text with a status, and a description list for the fields of a single record.
|
|
113
114
|
|
|
114
115
|
## Columns and rows
|
|
115
116
|
|
|
@@ -400,9 +401,116 @@ export class TablePagedExampleComponent {
|
|
|
400
401
|
|
|
401
402
|
`size` sets the row height: `sm` 40 px, `md` 48 px (default), `lg` 56 px, with a 32, 40 or 48 px header. Unset, it follows `provideBfDefaults()`'s `controlSize`, so `{ size: 'sm' }` in a page's `providers` makes a dense table. The checkboxes take the same step. Under a coarse pointer every row is at least 44 px tall, and the sortable header buttons are 44 px targets; a cell grows to fit its content and is never covered by a hit-area extension.
|
|
402
403
|
|
|
404
|
+
## Stacked rows
|
|
405
|
+
|
|
406
|
+
`stack` lets the table itself fit a narrow space. With `stack="narrow"` it stacks while the table is narrower than 36rem (576 px at the default root size, Tailwind's `xl` container size): a container query on the table's own width, so a table in a narrow column or a side panel stacks even on a wide screen. The host is then a size container (`container-type: inline-size`), so give the table its width from its parent (a block or `w-full` / `flex-1`), not from its columns. With `stack="coarse"` it stacks under a coarse pointer at any width (phones, tablets, the Capacitor shell). The default `never` keeps the table as it is.
|
|
407
|
+
|
|
408
|
+
Stacked, each row is a sunken tile (`surface-sunken`, `rounded-tile`, the tile a raised card holds; a selected row keeps `primary-soft`). Its checkbox and first cell head the tile in bold; every other cell shows its column label at the start and its value at the end. The header row is visually hidden, and a bar above the tiles holds a labelled Select all checkbox (with `selectable`) and a Sort by select, since the hidden header's buttons are not shown. Its first option, Not sorted, shows while `sort` is null and sets it back to null; then every sortable column, ascending and descending (`tableSortBy`, `tableSortNone`, `tableSortAscending`, `tableSortDescending` labels). Choosing a column option sets `sort`, as a header click does. In a test, `BfTableHarness.getSortSelect()` returns that select's `BfSelectHarness`.
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
import { Component, computed, signal } from '@angular/core';
|
|
412
|
+
import {
|
|
413
|
+
BfBadgeComponent,
|
|
414
|
+
BfSegmentedComponent,
|
|
415
|
+
BfTableCellDirective,
|
|
416
|
+
BfTableComponent,
|
|
417
|
+
type BfSegmentOption,
|
|
418
|
+
type BfTableColumn,
|
|
419
|
+
type BfTableSort,
|
|
420
|
+
} from '@bitforgehq/ui';
|
|
421
|
+
|
|
422
|
+
interface Task {
|
|
423
|
+
id: string;
|
|
424
|
+
title: string;
|
|
425
|
+
project: string;
|
|
426
|
+
status: 'Open' | 'Done';
|
|
427
|
+
hours: number;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const TASKS: readonly Task[] = [
|
|
431
|
+
{ id: 'brief', title: 'Write the brief', project: 'Atlas', status: 'Done', hours: 3 },
|
|
432
|
+
{ id: 'moodboard', title: 'Bloom moodboard', project: 'Bloom', status: 'Open', hours: 6 },
|
|
433
|
+
{ id: 'deck', title: 'Pitch deck', project: 'Atlas', status: 'Open', hours: 9 },
|
|
434
|
+
];
|
|
435
|
+
|
|
436
|
+
type Width = 'phone' | 'full';
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Stacked rows on a narrow table.
|
|
440
|
+
*
|
|
441
|
+
* `stack="narrow"` turns each row into a tile of label/value pairs while the table is narrower
|
|
442
|
+
* than 36rem: the selection checkbox and the first cell head the tile, and a bar above the tiles
|
|
443
|
+
* holds Select all and a Sort by select, whose first option, Not sorted, shows while `sort` is
|
|
444
|
+
* null and clears it. Switch the box between phone width and full width.
|
|
445
|
+
*/
|
|
446
|
+
@Component({
|
|
447
|
+
selector: 'app-example-table-stacked',
|
|
448
|
+
imports: [BfTableComponent, BfTableCellDirective, BfBadgeComponent, BfSegmentedComponent],
|
|
449
|
+
template: `
|
|
450
|
+
<!-- A stacking table is a size container: its width comes from its parent, never its columns. -->
|
|
451
|
+
<div class="flex w-152 max-w-full flex-col gap-3">
|
|
452
|
+
<bf-segmented label="Box width" [options]="widths" [(value)]="width" />
|
|
453
|
+
<div [class]="width() === 'phone' ? 'w-full max-w-sm' : 'w-full'">
|
|
454
|
+
<bf-table
|
|
455
|
+
label="Stacked tasks"
|
|
456
|
+
stack="narrow"
|
|
457
|
+
selectable
|
|
458
|
+
[rows]="rows()"
|
|
459
|
+
[columns]="columns"
|
|
460
|
+
[(sort)]="sort"
|
|
461
|
+
[(selection)]="selection"
|
|
462
|
+
>
|
|
463
|
+
<ng-template bfTableCell="status" [bfTableCellOf]="rows()" let-task>
|
|
464
|
+
<bf-badge [variant]="task.status === 'Done' ? 'success' : 'neutral'">
|
|
465
|
+
{{ task.status }}
|
|
466
|
+
</bf-badge>
|
|
467
|
+
</ng-template>
|
|
468
|
+
</bf-table>
|
|
469
|
+
</div>
|
|
470
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
471
|
+
</div>
|
|
472
|
+
`,
|
|
473
|
+
})
|
|
474
|
+
export class TableStackedExampleComponent {
|
|
475
|
+
readonly widths: BfSegmentOption<Width>[] = [
|
|
476
|
+
{ value: 'phone', label: 'Phone' },
|
|
477
|
+
{ value: 'full', label: 'Full width' },
|
|
478
|
+
];
|
|
479
|
+
readonly width = signal<Width | null>('phone');
|
|
480
|
+
readonly columns: readonly BfTableColumn[] = [
|
|
481
|
+
{ key: 'title', label: 'Task', sortable: true },
|
|
482
|
+
{ key: 'project', label: 'Project' },
|
|
483
|
+
{ key: 'status', label: 'Status' },
|
|
484
|
+
{ key: 'hours', label: 'Hours', sortable: true, align: 'end' },
|
|
485
|
+
];
|
|
486
|
+
readonly sort = signal<BfTableSort | null>(null);
|
|
487
|
+
readonly selection = signal<readonly string[]>([]);
|
|
488
|
+
|
|
489
|
+
/** The table never sorts: the page sorts its rows from `sort`. */
|
|
490
|
+
readonly rows = computed(() => {
|
|
491
|
+
const sort = this.sort();
|
|
492
|
+
if (!sort) {
|
|
493
|
+
return TASKS;
|
|
494
|
+
}
|
|
495
|
+
const factor = sort.direction === 'ascending' ? 1 : -1;
|
|
496
|
+
return [...TASKS].sort((a, b) =>
|
|
497
|
+
sort.column === 'hours'
|
|
498
|
+
? (a.hours - b.hours) * factor
|
|
499
|
+
: a.title.localeCompare(b.title) * factor,
|
|
500
|
+
);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
readonly status = computed(() => {
|
|
504
|
+
const sort = this.sort();
|
|
505
|
+
const sorted = sort ? `Sorted by ${sort.column}, ${sort.direction}` : 'Unsorted';
|
|
506
|
+
return `${sorted} · ${this.selection().length} selected.`;
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
```
|
|
510
|
+
|
|
403
511
|
## Narrow screens and touch
|
|
404
512
|
|
|
405
|
-
A table needs width.
|
|
513
|
+
A table needs width. When stacked tiles are not what a phone should show (long records, a summary per row), render the same rows as a divided `bf-list` of `bf-list-item`s below the `md` breakpoint (a `md:hidden` wrapper) and the table only from `md` up (a `hidden md:block` wrapper), from the one `rows` array. Inside the Capacitor shell use the list alone, or a table with `stack="coarse"`. The table's wrapper scrolls sideways when its columns cannot fit, but that is a last resort, not a layout for phones.
|
|
406
514
|
|
|
407
515
|
```ts
|
|
408
516
|
import { Component } from '@angular/core';
|
|
@@ -471,6 +579,7 @@ A selectable table tints a row `surface-muted` while hovered (a selected row `pr
|
|
|
471
579
|
- Rows: `text` on the page's surface, divided by `border` lines; hover (selectable only) `surface-muted`; selected `primary-soft`, hover `primary-soft-hover`.
|
|
472
580
|
- Sort arrow: a 1.5 px CSS chevron in the header's text colour.
|
|
473
581
|
- Focus ring: `ring`. Header buttons: `rounded-field`.
|
|
582
|
+
- Stacked rows: tiles on `surface-sunken` with `rounded-tile`, labels `text-label-xs` bold in `text-muted`.
|
|
474
583
|
- Text: `text-body` (`text-label-sm` at `sm`), `tabular-nums` in `end` columns.
|
|
475
584
|
|
|
476
585
|
Put the table on a `bf-card` (or another surface) yourself; it has no background or border of its own.
|
|
@@ -479,6 +588,8 @@ Put the table on a `bf-card` (or another surface) yourself; it has no background
|
|
|
479
588
|
|
|
480
589
|
A native `table` with a visually hidden `caption` from `label`, `th scope="col"` headers and one `tr` per row. A sorted column's `th` carries `aria-sort="ascending"` or `"descending"`; the others carry none. A sortable header is a native `button` inside the `th`, so its name is the column label; the arrow is `aria-hidden`. The header checkbox is named by `selectAllLabel`, else `provideBfLabels()`'s `tableSelectAll` ("Select all rows"), and is mixed (`indeterminate`) while some rows are selected. Each row checkbox is named by the row's first data cell through `aria-labelledby`, so it reads as the row's title. The empty row's cell spans every column.
|
|
481
590
|
|
|
591
|
+
A table that can stack (`stack` other than `never`) sets explicit `table`, `rowgroup`, `row`, `columnheader` and `cell` roles and names the table from its caption with `aria-labelledby`, because Safari and some screen readers drop a table's semantics once CSS changes its display; so a stacked table still reads as a table with its column headers. Stacked, the header row is visually hidden but stays in the accessibility tree; its sort buttons and checkbox are hidden (a plain label takes a sortable header's place), so no control is focusable where nobody can see it, and the bar's Select all checkbox and Sort by select replace them. A cell's visible label is CSS generated content with empty alternative text (`content: attr(data-label) / ""`, the `content-data-label` utility of `theme.css`), so a screen reader reads the column header once, not the label as well.
|
|
592
|
+
|
|
482
593
|
### Keyboard
|
|
483
594
|
|
|
484
595
|
| Key | Action |
|
|
@@ -489,6 +600,8 @@ A native `table` with a visually hidden `caption` from `label`, `th scope="col"`
|
|
|
489
600
|
| <kbd>Space</kbd> | Toggles the focused checkbox, or sorts by the focused header |
|
|
490
601
|
| <kbd>Enter</kbd> | Sorts by the focused header |
|
|
491
602
|
|
|
603
|
+
A stacked table's Sort by select is a native select with its own keys.
|
|
604
|
+
|
|
492
605
|
Text fields, selects and composite widgets (radios, comboboxes, sliders) in a cell keep their own arrow keys.
|
|
493
606
|
|
|
494
607
|
## Inputs
|
|
@@ -502,6 +615,7 @@ Text fields, selects and composite widgets (radios, comboboxes, sliders) in a ce
|
|
|
502
615
|
| `selectable` | `boolean` | `false` | Adds a first column of checkboxes that select rows. |
|
|
503
616
|
| `selectAllLabel` | `string` | `''` | Accessible name of the header checkbox; empty falls back to `provideBfLabels()`'s `tableSelectAll` ("Select all rows"). |
|
|
504
617
|
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Row height step: sm 40, md 48, lg 56 px (the header 32/40/48; at least 44 px under a coarse pointer); unset follows `provideBfDefaults()` (`controlSize`, `md` by default). |
|
|
618
|
+
| `stack` | `BfTableStack` (`'never' \| 'narrow' \| 'coarse'`) | `'never'` | When the rows render as cards of label/value pairs: `never`, `narrow` (the table is narrower than 36rem) or `coarse` (under a coarse pointer, at any width). |
|
|
505
619
|
| `sort` | `BfTableSort \| null` | `null` | The sorted column and direction, `null` while unsorted; two-way bindable. A sortable header sets it, and the consumer sorts `rows` from it. Two-way: `[(sort)]`. |
|
|
506
620
|
| `selection` | `readonly string[]` | `[]` | The keys (`rowKey`) of the selected rows; two-way bindable. Two-way: `[(selection)]`. |
|
|
507
621
|
|
|
@@ -542,15 +656,48 @@ Selects or clears one row.
|
|
|
542
656
|
|
|
543
657
|
- Give every table a `label`, and every sortable column a short header.
|
|
544
658
|
- Sort and filter the rows in a `computed()` from `sort` and your filters.
|
|
545
|
-
-
|
|
659
|
+
- Use `stack="narrow"` for a table that may sit in a narrow column, or `stack="coarse"` in the Capacitor shell; otherwise render the rows as `bf-list-item`s below `md`.
|
|
546
660
|
|
|
547
661
|
### Don't
|
|
548
662
|
|
|
549
|
-
- Don't put a table on a phone screen that scrolls sideways; use the list.
|
|
663
|
+
- Don't put a table on a phone screen that scrolls sideways; stack it or use the list.
|
|
664
|
+
- Don't put a stacking table in a parent that sizes it from its content: the container query needs a width from outside.
|
|
550
665
|
- Don't make a whole row clickable; put a link or button in a custom cell.
|
|
551
666
|
|
|
552
667
|
## Migration notes
|
|
553
668
|
|
|
669
|
+
### 0.3.3
|
|
670
|
+
|
|
671
|
+
Fix: stacked rows (`stack="narrow"` and `stack="coarse"`) show their column labels again when the
|
|
672
|
+
package is installed from npm. 0.3.2 wrote the label as `content-[attr(data-label)_/_""]`; the
|
|
673
|
+
library build escaped its quotes in the FESM (`\"\"`), so Tailwind built the rule for a class no
|
|
674
|
+
cell carries and every label `::before` computed to `none`. The cells now carry
|
|
675
|
+
`before:content-data-label`, a utility `theme.css` defines (below). Apps that added the
|
|
676
|
+
workaround `@source inline('@max-xl:before:content-[attr(data-label)_/_""] pointer-coarse:before:content-[attr(data-label)_/_""]')`
|
|
677
|
+
can remove it. consumer-smoke now opens a stacked table built from the packed package in
|
|
678
|
+
Chromium, under a fine and a coarse pointer, and fails if any FESM class string holds a
|
|
679
|
+
backslash escape.
|
|
680
|
+
|
|
681
|
+
The stacked Sort by select's first option is Not sorted (new label key `tableSortNone`, "Not
|
|
682
|
+
sorted"): it shows while `sort` is null, so the select is no longer an empty box, and choosing it
|
|
683
|
+
sets `sort` back to null. New `BfTableHarness.getSortSelect()` returns that select's
|
|
684
|
+
`BfSelectHarness` (`null` when the table cannot stack or has no sortable column).
|
|
685
|
+
|
|
686
|
+
### 0.3.2
|
|
687
|
+
|
|
688
|
+
New opt-in `stack` input (`BfTableStack`: `'never'` by default, so existing tables do not
|
|
689
|
+
change, `'narrow'` or `'coarse'`): while the table is narrower than 36rem (a container query on
|
|
690
|
+
the host, which then gets `@container`) or, for `'coarse'`, under a coarse pointer at any width,
|
|
691
|
+
each body row renders as a sunken tile of label/value pairs. The markup stays a `table` with
|
|
692
|
+
explicit `table`, `rowgroup`, `row`, `columnheader` and `cell` roles (some browsers drop a
|
|
693
|
+
table's semantics once its display changes), the header row is visually hidden but still read,
|
|
694
|
+
the selection checkbox and the first cell head each tile, and every other cell shows its column
|
|
695
|
+
label (`data-label`, a `::before` with empty alternative text, so it is not read twice). A bar
|
|
696
|
+
above the tiles holds a labelled Select all checkbox and a Sort by `bf-select`, since the hidden
|
|
697
|
+
header's controls are not shown. New label keys `tableSortBy` ("Sort by"), `tableSortAscending`
|
|
698
|
+
and `tableSortDescending` (`"Hours, ascending"`). `BfTableHarness` reads and drives a stacked
|
|
699
|
+
table the same way. New `table/stacked` example and a Stacked rows docs section.
|
|
700
|
+
|
|
554
701
|
### 0.3.1
|
|
555
702
|
|
|
556
703
|
New `bf-table` and `ng-template[bfTableCell]` (`BfTableCellDirective`) in `@bitforgehq/ui`, a
|
package/ai/components/tabs.md
CHANGED
|
@@ -194,7 +194,7 @@ fires no keyboard event).
|
|
|
194
194
|
|
|
195
195
|
## Desktop
|
|
196
196
|
|
|
197
|
-
`bf-tabs` is part of the Capacitor shell (`provideBfShell()`). On a desktop web app, put the top-level destinations in a [bf-top-nav](https://bitforge-ui.web.app/components/top-nav) (a few sections) or a [bf-sidebar](https://bitforge-ui.web.app/components/sidebar) (more, in groups, collapsible to an icon rail) of `a[bfNavItem]` links, each with `routerLink`, `routerLinkActive` and `ariaCurrentWhenActive="page"`, above a plain `router-outlet`. The router sets `aria-current="page"` on the link of the current route and the item shows it. Drive both from one array of destinations: inside the shell the same array feeds the `bf-tab`s of a [bf-tab-bar](https://bitforge-ui.web.app/components/tab-bar), and below a breakpoint a [bf-menu-button](https://bitforge-ui.web.app/components/menu-button) of `a[bfMenuItem]` links (see bf-sidebar, Responsive). Inside the shell under a mouse, the tab links take the `bf-tab` hover and focus styles.
|
|
197
|
+
`bf-tabs` is part of the Capacitor shell (`provideBfShell()`). On a desktop web app, put the top-level destinations in a [bf-top-nav](https://bitforge-ui.web.app/components/top-nav) (a few sections) or a [bf-sidebar](https://bitforge-ui.web.app/components/sidebar) (more, in groups, collapsible to an icon rail) of `a[bfNavItem]` links, each with `routerLink`, `routerLinkActive` and `ariaCurrentWhenActive="page"`, above a plain `router-outlet`. The router sets `aria-current="page"` on the link of the current route and the item shows it. Drive both from one array of destinations: inside the shell the same array feeds the `bf-tab`s of a [bf-tab-bar](https://bitforge-ui.web.app/components/tab-bar), and below a breakpoint a [bf-menu-button](https://bitforge-ui.web.app/components/menu-button) of `a[bfMenuItem]` links (see bf-sidebar, Responsive). Wrap the desktop app in a [bf-app-frame](https://bitforge-ui.web.app/components/app-frame): it holds the sidebar from `md` up and, below it, a header bar with that menu button. Inside the shell under a mouse, the tab links take the `bf-tab` hover and focus styles.
|
|
198
198
|
|
|
199
199
|
```ts
|
|
200
200
|
import { Component } from '@angular/core';
|
|
@@ -365,6 +365,10 @@ content to the top.
|
|
|
365
365
|
|
|
366
366
|
## Migration notes
|
|
367
367
|
|
|
368
|
+
### 0.3.2
|
|
369
|
+
|
|
370
|
+
The `## Desktop` docs point at `bf-app-frame` for the frame around the desktop navigation.
|
|
371
|
+
|
|
368
372
|
### 0.3.1
|
|
369
373
|
|
|
370
374
|
The `## Desktop` docs name the desktop replacement: a `bf-top-nav` or `bf-sidebar` of
|