@bitforgehq/ui 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -0
- package/README.md +4 -4
- package/ai/MANIFEST.index.json +36 -0
- package/ai/MANIFEST.json +414 -14
- package/ai/REFERENCE.index.json +35 -0
- package/ai/REFERENCE.json +184 -8
- package/ai/UI_GUIDE.md +25 -4
- package/ai/components/badge.md +39 -2
- package/ai/components/list-item.md +105 -9
- package/ai/components/step.md +112 -0
- package/ai/components/steps.md +124 -0
- package/ai/components/table.md +98 -2
- package/ai/components/timeline-event.md +144 -0
- package/ai/components/timeline.md +201 -0
- package/ai/components/toggle.md +11 -1
- package/ai/reference/bf-action-directive.md +3 -1
- package/ai/reference/bf-badge-emphasis.md +27 -0
- package/ai/reference/bf-labels.md +1 -0
- package/ai/reference/bf-step-heading-level.md +28 -0
- package/ai/reference/bf-table-align.md +1 -1
- package/ai/reference/bf-table-column-align.md +33 -0
- package/ai/reference/bf-table-column.md +7 -7
- package/ai/reference/bf-table-harness.md +12 -0
- package/ai/reference/bf-timeline-tone.md +31 -0
- package/ai/reference/bf-toggle-harness.md +55 -0
- package/ai/reference/provide-bf-labels.md +1 -1
- package/dark-ink.css +124 -0
- package/fesm2022/bitforgehq-ui-mobile.mjs +2 -1
- package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-testing.mjs +51 -2
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui.mjs +682 -32
- package/fesm2022/bitforgehq-ui.mjs.map +1 -1
- package/llms-full.txt +1072 -15
- package/llms.txt +9 -0
- package/package.json +2 -1
- package/theme.css +24 -0
- package/types/bitforgehq-ui-testing.d.ts +76 -1
- package/types/bitforgehq-ui.d.ts +288 -8
|
@@ -100,6 +100,7 @@ export class ListItemOverviewExampleComponent {
|
|
|
100
100
|
One row of a list: leading slot (avatar, icon tile), title and subtitle, trailing slot
|
|
101
101
|
(time, toggle, badge) and an optional chevron for rows that navigate. A navigation row puts a
|
|
102
102
|
`bfAction` link in the `title` slot, so the row stays a list item and the link covers it.
|
|
103
|
+
`unread` marks a row not yet opened with a dot and a bolder title.
|
|
103
104
|
|
|
104
105
|
A row of a list: a leading slot (an avatar, an icon), the title and subtitle, a trailing slot (a time, a toggle, a chip) and an optional chevron. Place rows in a [`bf-list`](https://bitforge-ui.web.app/components/list), which gives them the list semantics and the inset container; a row can also stand alone. A row is static text, a navigation link or an action button, never more than one of them.
|
|
105
106
|
|
|
@@ -230,6 +231,87 @@ export class ListItemActionsExampleComponent {
|
|
|
230
231
|
}
|
|
231
232
|
```
|
|
232
233
|
|
|
234
|
+
## Unread rows
|
|
235
|
+
|
|
236
|
+
`unread` marks a row the user has not opened yet, such as a new message or notification: a small `primary` dot at the row's inline start, on the title line, and the title in a bolder weight. It is not colour alone: the row also reads a visually hidden state before the title, from the `listItemUnread` label ("Unread"; set it with `provideBfLabels({ listItemUnread: 'Ungelesen' })` in a German app), so a screen reader says "Unread, Scan finished". The dot sits in the row's own inline-start padding (or the gap after a leading avatar or icon), so read and unread rows keep their text aligned and nothing moves when a row is marked read; without `unread` nothing changes. Clear `unread` when the row is opened.
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
import { Component, computed, signal } from '@angular/core';
|
|
240
|
+
import { BfActionDirective, BfListComponent, BfListItemComponent } from '@bitforgehq/ui';
|
|
241
|
+
|
|
242
|
+
interface Notice {
|
|
243
|
+
readonly id: string;
|
|
244
|
+
readonly title: string;
|
|
245
|
+
readonly subtitle: string;
|
|
246
|
+
readonly time: string;
|
|
247
|
+
readonly unread: boolean;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* An inbox with unread rows.
|
|
252
|
+
*
|
|
253
|
+
* Each title is a stretched `<button bfAction>`; opening a row marks it read.
|
|
254
|
+
*/
|
|
255
|
+
@Component({
|
|
256
|
+
selector: 'app-example-list-item-unread',
|
|
257
|
+
imports: [BfActionDirective, BfListComponent, BfListItemComponent],
|
|
258
|
+
template: `
|
|
259
|
+
<bf-list>
|
|
260
|
+
@for (notice of notices(); track notice.id) {
|
|
261
|
+
<bf-list-item [unread]="notice.unread" [subtitle]="notice.subtitle">
|
|
262
|
+
<button bfSlot="title" bfAction type="button" class="text-start" (click)="open(notice)">
|
|
263
|
+
{{ notice.title }}
|
|
264
|
+
</button>
|
|
265
|
+
<span bfSlot="trailing" class="text-caption text-text-muted">{{ notice.time }}</span>
|
|
266
|
+
</bf-list-item>
|
|
267
|
+
}
|
|
268
|
+
</bf-list>
|
|
269
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted mt-3">{{ status() }}</p>
|
|
270
|
+
`,
|
|
271
|
+
})
|
|
272
|
+
export class ListItemUnreadExampleComponent {
|
|
273
|
+
protected readonly notices = signal<readonly Notice[]>([
|
|
274
|
+
{
|
|
275
|
+
id: 'scan',
|
|
276
|
+
title: 'Scan finished',
|
|
277
|
+
subtitle: '3 new findings on api.example.com',
|
|
278
|
+
time: '9:41',
|
|
279
|
+
unread: true,
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: 'cert',
|
|
283
|
+
title: 'Certificate expires soon',
|
|
284
|
+
subtitle: 'shop.example.com, in 12 days',
|
|
285
|
+
time: '8:15',
|
|
286
|
+
unread: true,
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
id: 'report',
|
|
290
|
+
title: 'Weekly report',
|
|
291
|
+
subtitle: 'Ready to download',
|
|
292
|
+
time: 'Mon',
|
|
293
|
+
unread: false,
|
|
294
|
+
},
|
|
295
|
+
]);
|
|
296
|
+
private readonly opened = signal('');
|
|
297
|
+
|
|
298
|
+
protected readonly status = computed(() => {
|
|
299
|
+
const unread = this.notices().filter((notice) => notice.unread).length;
|
|
300
|
+
const opened = this.opened();
|
|
301
|
+
return opened ? `${unread} unread · Opened: ${opened}` : `${unread} unread`;
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
protected open(notice: Notice): void {
|
|
305
|
+
this.opened.set(notice.title);
|
|
306
|
+
this.notices.update((list) =>
|
|
307
|
+
list.map((item) => (item.id === notice.id ? { ...item, unread: false } : item)),
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
With a `bfAction` link or button in the `title` slot, the state becomes part of that element's accessible name: the directive sets `aria-labelledby` to the state text and the element itself (it gives the element an id if it has none, and keeps an `aria-labelledby` of its own after the state), so the link reads "Unread, Scan finished" when tabbed to or listed, and the state text is hidden from the tree so it is read once. An `interactive` row holds the state text inside its button, so its name starts with it too.
|
|
314
|
+
|
|
233
315
|
## Desktop
|
|
234
316
|
|
|
235
317
|
An `interactive` row and a row holding a `bfAction` link take a `surface-muted` fill and the pointer cursor while hovered; a static row has none. Hover styles apply only on devices that can hover, so a tap never leaves one stuck. The ring is drawn around the row on keyboard focus only. A navigation row keeps the browser's link behaviour: Ctrl/Cmd-click and a middle click open it in a new tab. The trailing slot sits above the stretched link, so a toggle or button there takes its own click. Rows have one height; they do not follow `provideBfDefaults()`.
|
|
@@ -240,10 +322,11 @@ An `interactive` row and a row holding a `bfAction` link take a `surface-muted`
|
|
|
240
322
|
- Text: the title, the title slot and the subtitle wrap (`wrap-anywhere`); with `truncate` each keeps one line (`truncate`).
|
|
241
323
|
- `interactive` rows and rows holding a `bfAction` link add a pointer, a `surface-muted` hover, a `surface-pressed` fill while pressed (the row's action, for a `bfAction` row) and a `ring` focus ring; `interactive` rows dim to `text-disabled` when `disabled` and have no pressed state then.
|
|
242
324
|
- The `chevron` mark uses `text-subtle`.
|
|
325
|
+
- `unread`: the dot is `primary` (`CanvasText` in forced colours), `rounded-full` and 8 px (`size-2`); the title turns `font-extrabold`.
|
|
243
326
|
|
|
244
327
|
## Accessibility
|
|
245
328
|
|
|
246
|
-
`bf-list-item` sets `role="listitem"` on its host only inside a `bf-list`; a standalone row has no role, so it never claims a list parent that is not there. A non-interactive row is a plain `<div>`, while `[interactive]="true"` renders it as a native `<button>` so it gets keyboard activation and the button role for free. A navigation row's `bfAction` link in the `title` slot is a real `<a>` inside the list item ([WAI-ARIA APG link pattern](https://www.w3.org/WAI/ARIA/apg/patterns/link/)): exposed as `link` with the title as its name, reached with Tab (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), followed with Enter (not Space), opened in a new tab with Ctrl/Cmd-click; under forced colours a system-coloured outline around the whole row marks the focused link. Making the host itself a link would take the row out of the list's `listitem` structure, so the link lives inside it. `chevron` is a purely visual CSS mark (an `::after` pseudo-element), so it adds nothing to the accessibility tree.
|
|
329
|
+
`bf-list-item` sets `role="listitem"` on its host only inside a `bf-list`; a standalone row has no role, so it never claims a list parent that is not there. A non-interactive row is a plain `<div>`, while `[interactive]="true"` renders it as a native `<button>` so it gets keyboard activation and the button role for free. A navigation row's `bfAction` link in the `title` slot is a real `<a>` inside the list item ([WAI-ARIA APG link pattern](https://www.w3.org/WAI/ARIA/apg/patterns/link/)): exposed as `link` with the title as its name, reached with Tab (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), followed with Enter (not Space), opened in a new tab with Ctrl/Cmd-click; under forced colours a system-coloured outline around the whole row marks the focused link. Making the host itself a link would take the row out of the list's `listitem` structure, so the link lives inside it. `chevron` is a purely visual CSS mark (an `::after` pseudo-element), so it adds nothing to the accessibility tree. An `unread` row's dot carries a visually hidden (`sr-only`) state text from the `listItemUnread` label, read before the title ("Unread, Scan finished"); on a row with a `bfAction` the text is `aria-hidden` and the action names itself with `aria-labelledby` from it and its own content, so the state is part of the link's or button's name and is read once. The row stays a `listitem`.
|
|
247
330
|
|
|
248
331
|
### Keyboard
|
|
249
332
|
|
|
@@ -255,14 +338,15 @@ An `interactive` row and a row holding a `bfAction` link take a `surface-muted`
|
|
|
255
338
|
|
|
256
339
|
## Inputs
|
|
257
340
|
|
|
258
|
-
| Name | Type | Default | Description
|
|
259
|
-
| ------------- | --------- | ------- |
|
|
260
|
-
| `title` | `string` | `''` | Primary text. For a navigation row, project an `<a bfSlot="title" bfAction>` instead.
|
|
261
|
-
| `subtitle` | `string` | `''` | Secondary text under the title.
|
|
262
|
-
| `interactive` | `boolean` | `false` | Render the row as a button that emits `clicked` (an action row; a row that navigates puts a `bfAction` link in the `title` slot instead).
|
|
263
|
-
| `chevron` | `boolean` | `false` | Show a trailing chevron (navigation rows).
|
|
264
|
-
| `disabled` | `boolean` | `false` | Disable an interactive row.
|
|
265
|
-
| `truncate` | `boolean` | `false` | Keep the title, the title slot and the subtitle on one line each, cut with an ellipsis. By default they wrap, so nothing is lost at large text sizes.
|
|
341
|
+
| Name | Type | Default | Description |
|
|
342
|
+
| ------------- | --------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
343
|
+
| `title` | `string` | `''` | Primary text. For a navigation row, project an `<a bfSlot="title" bfAction>` instead. |
|
|
344
|
+
| `subtitle` | `string` | `''` | Secondary text under the title. |
|
|
345
|
+
| `interactive` | `boolean` | `false` | Render the row as a button that emits `clicked` (an action row; a row that navigates puts a `bfAction` link in the `title` slot instead). |
|
|
346
|
+
| `chevron` | `boolean` | `false` | Show a trailing chevron (navigation rows). |
|
|
347
|
+
| `disabled` | `boolean` | `false` | Disable an interactive row. |
|
|
348
|
+
| `truncate` | `boolean` | `false` | Keep the title, the title slot and the subtitle on one line each, cut with an ellipsis. By default they wrap, so nothing is lost at large text sizes. |
|
|
349
|
+
| `unread` | `boolean` | `false` | Mark the row as not yet opened: a dot at the inline start, a bolder title and a visually hidden state (the `listItemUnread` label) read before the title. |
|
|
266
350
|
|
|
267
351
|
## Outputs
|
|
268
352
|
|
|
@@ -283,14 +367,26 @@ An `interactive` row and a row holding a `bfAction` link take a `surface-muted`
|
|
|
283
367
|
|
|
284
368
|
- Keep titles short; a long one wraps. Add `truncate` only where one line per row is the pattern (a mail list).
|
|
285
369
|
- Make a row that navigates a `bfAction` link in the `title` slot, and a row that acts on the page `interactive`; put secondary actions in the trailing slot.
|
|
370
|
+
- Set `unread` on rows not yet opened (messages, notifications) and clear it when the row is opened.
|
|
286
371
|
|
|
287
372
|
### Don't
|
|
288
373
|
|
|
289
374
|
- Don't nest interactive controls inside an `interactive` row (it is one button); use a navigation row with a trailing control instead.
|
|
290
375
|
- Don't wrap a `bf-list-item` in an `<a>`, and don't navigate from an `interactive` row's `(clicked)`.
|
|
376
|
+
- Don't mark unread rows with a coloured title or a badge of your own; `unread` gives the dot, the weight and the screen-reader state together.
|
|
291
377
|
|
|
292
378
|
## Migration notes
|
|
293
379
|
|
|
380
|
+
### 0.4.0
|
|
381
|
+
|
|
382
|
+
New `unread` input (boolean, default false) for a row not yet opened: a `primary` dot at the
|
|
383
|
+
row's inline start on the title line, a bolder title (`font-extrabold`) and a visually hidden
|
|
384
|
+
state read before the title, from the new label key `listItemUnread` ("Unread"). A row's
|
|
385
|
+
`bfAction` link or button then names itself `aria-labelledby="<state> <itself>"` ("Unread,
|
|
386
|
+
Report"; an id is added if it has none, and its own `aria-labelledby` is kept after the state).
|
|
387
|
+
The dot sits in the row's own inline-start padding, so read and unread rows stay aligned. Opt-in: without `unread` nothing
|
|
388
|
+
changes.
|
|
389
|
+
|
|
294
390
|
### 0.3.0
|
|
295
391
|
|
|
296
392
|
**Behaviour:** logical properties: text aligns to the inline start and the chevron sits at the
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# bf-step
|
|
2
|
+
|
|
3
|
+
One numbered step of an instruction list, with a title and its details
|
|
4
|
+
|
|
5
|
+
`BfStepComponent` from `@bitforgehq/ui` · Data display · since 0.4.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component } from '@angular/core';
|
|
11
|
+
import { BfStepComponent, BfStepsComponent } from '@bitforgehq/ui';
|
|
12
|
+
|
|
13
|
+
/** Steps with a short instruction as the title and the details in the content. */
|
|
14
|
+
@Component({
|
|
15
|
+
selector: 'app-example-step-overview',
|
|
16
|
+
imports: [BfStepComponent, BfStepsComponent],
|
|
17
|
+
template: `
|
|
18
|
+
<bf-steps>
|
|
19
|
+
<bf-step title="Export your contacts"
|
|
20
|
+
>In your old app, choose Export and save the file.</bf-step
|
|
21
|
+
>
|
|
22
|
+
<bf-step title="Import the file">
|
|
23
|
+
Pick the file you saved. Contacts that are already in your address book are merged, not
|
|
24
|
+
added twice.
|
|
25
|
+
</bf-step>
|
|
26
|
+
<bf-step title="Check the result" />
|
|
27
|
+
</bf-steps>
|
|
28
|
+
`,
|
|
29
|
+
})
|
|
30
|
+
export class StepOverviewExampleComponent {}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Overview
|
|
34
|
+
|
|
35
|
+
One step of a `bf-steps` list: a numbered marker, a short instruction as its title and the
|
|
36
|
+
step's details in the content.
|
|
37
|
+
|
|
38
|
+
One step of a [`bf-steps`](https://bitforge-ui.web.app/components/steps) list: `title` is the step's short instruction and the content holds its details (text, a code to type, an image or a button). The list numbers the steps by position; the step draws the numbered circle and the line to the next one. It works the same in both shells.
|
|
39
|
+
|
|
40
|
+
## When to use
|
|
41
|
+
|
|
42
|
+
Use `bf-step` for each instruction inside a `bf-steps` list. Use [bf-list-item](https://bitforge-ui.web.app/components/list-item) instead for a row the user opens or acts on, and [bf-description-list](https://bitforge-ui.web.app/components/description-list) for facts with no order.
|
|
43
|
+
|
|
44
|
+
## Headings
|
|
45
|
+
|
|
46
|
+
The title is text by default, which suits short steps. For a long guide whose steps hold screenshots or code, set `headingLevel` (`2`, `3` or `4`, a `BfStepHeadingLevel`) to the level that fits the page outline, so a screen-reader user can move from step to step by heading. The look does not change.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { Component } from '@angular/core';
|
|
50
|
+
import { BfStepComponent, BfStepsComponent } from '@bitforgehq/ui';
|
|
51
|
+
|
|
52
|
+
/** Long steps with level 3 headings, so a screen-reader user can move between them. */
|
|
53
|
+
@Component({
|
|
54
|
+
selector: 'app-example-step-headings',
|
|
55
|
+
imports: [BfStepComponent, BfStepsComponent],
|
|
56
|
+
template: `
|
|
57
|
+
<bf-steps>
|
|
58
|
+
<bf-step title="Create an API key" [headingLevel]="3">
|
|
59
|
+
In the console, open Settings, then API keys, and choose New key. Give it a name that says
|
|
60
|
+
where it is used, such as "CI pipeline", and copy it: it is shown only once.
|
|
61
|
+
</bf-step>
|
|
62
|
+
<bf-step title="Store the key as a secret" [headingLevel]="3">
|
|
63
|
+
Add it to your CI provider's secrets as BITFORGE_API_KEY. Never commit it to the repository.
|
|
64
|
+
</bf-step>
|
|
65
|
+
</bf-steps>
|
|
66
|
+
`,
|
|
67
|
+
})
|
|
68
|
+
export class StepHeadingsExampleComponent {}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Desktop
|
|
72
|
+
|
|
73
|
+
A step is not interactive: no hover, no pointer cursor, no focus stop; only a link or button in its content takes focus. It renders the same inside `provideBfShell()`.
|
|
74
|
+
|
|
75
|
+
## Theming
|
|
76
|
+
|
|
77
|
+
- Marker: `bg-primary-soft` / `text-primary-strong`, `text-label-sm` bold, a 24 px `rounded-full` circle; the line under it is `border-border`, hidden on the last step.
|
|
78
|
+
- Title: `text-label` bold in `text`, wrapping anywhere; content: `text-body` in `text-secondary`.
|
|
79
|
+
|
|
80
|
+
## Accessibility
|
|
81
|
+
|
|
82
|
+
Inside `bf-steps` the host is a `role="listitem"`, so its position is announced ("2 of 3"); the visible number is `aria-hidden`. The title is a `p`, or an `h2`, `h3` or `h4` with `headingLevel`. Outside `bf-steps` the host claims no role.
|
|
83
|
+
|
|
84
|
+
## Inputs
|
|
85
|
+
|
|
86
|
+
| Name | Type | Default | Description |
|
|
87
|
+
| -------------- | -------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------- |
|
|
88
|
+
| `title` | `string` | `''` | The step's short instruction ("Install the app"), shown next to the number. |
|
|
89
|
+
| `headingLevel` | `BfStepHeadingLevel \| undefined` (`2 \| 3 \| 4 \| undefined`) | `undefined` | Renders the title as a heading of this level instead of text; leave it off for short steps. |
|
|
90
|
+
|
|
91
|
+
## Slots
|
|
92
|
+
|
|
93
|
+
- `default`: The step's details: text, a code snippet, an image or a button.
|
|
94
|
+
|
|
95
|
+
## Notes
|
|
96
|
+
|
|
97
|
+
### Do
|
|
98
|
+
|
|
99
|
+
- Keep the title to one instruction and put everything else in the content.
|
|
100
|
+
|
|
101
|
+
### Don't
|
|
102
|
+
|
|
103
|
+
- Don't use `bf-step` outside a `bf-steps` list: it then has no number to show but 1.
|
|
104
|
+
|
|
105
|
+
## Migration notes
|
|
106
|
+
|
|
107
|
+
### 0.4.0
|
|
108
|
+
|
|
109
|
+
New `bf-step`: one step of a `bf-steps` list, with `title` (the short instruction, text by
|
|
110
|
+
default; `headingLevel` 2, 3 or 4, a `BfStepHeadingLevel`, makes it a heading) and the details
|
|
111
|
+
in the default slot. Its number is drawn in a `primary-soft` circle and hidden from assistive
|
|
112
|
+
technology, which reads the list position instead.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# bf-steps
|
|
2
|
+
|
|
3
|
+
Numbered instruction steps a user follows in order, with joined markers
|
|
4
|
+
|
|
5
|
+
`BfStepsComponent` from `@bitforgehq/ui` · Data display · since 0.4.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component } from '@angular/core';
|
|
11
|
+
import { RouterLink } from '@angular/router';
|
|
12
|
+
import { BfStepComponent, BfStepsComponent } from '@bitforgehq/ui';
|
|
13
|
+
|
|
14
|
+
/** Setting up the app on a phone, in four numbered steps with a code to type and a link. */
|
|
15
|
+
@Component({
|
|
16
|
+
selector: 'app-example-steps-overview',
|
|
17
|
+
imports: [RouterLink, BfStepComponent, BfStepsComponent],
|
|
18
|
+
template: `
|
|
19
|
+
<bf-steps>
|
|
20
|
+
<bf-step title="Install the app">
|
|
21
|
+
Download Bitforge Guard from the App Store or Google Play.
|
|
22
|
+
</bf-step>
|
|
23
|
+
<bf-step title="Sign in">Use your work email address and the password you chose.</bf-step>
|
|
24
|
+
<bf-step title="Pair your phone">
|
|
25
|
+
Open Settings, then Devices, and enter this code:
|
|
26
|
+
<code
|
|
27
|
+
class="rounded-tile bg-surface-sunken text-label-sm text-text mt-2 block w-fit px-3 py-1.5 font-mono"
|
|
28
|
+
>K7F-29Q</code
|
|
29
|
+
>
|
|
30
|
+
</bf-step>
|
|
31
|
+
<bf-step title="Turn on notifications">
|
|
32
|
+
Allow them when the app asks, or later in
|
|
33
|
+
<a routerLink="/components/steps" class="text-primary-text underline"
|
|
34
|
+
>notification settings</a
|
|
35
|
+
>.
|
|
36
|
+
</bf-step>
|
|
37
|
+
</bf-steps>
|
|
38
|
+
`,
|
|
39
|
+
})
|
|
40
|
+
export class StepsOverviewExampleComponent {}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Overview
|
|
44
|
+
|
|
45
|
+
Numbered instructions a user follows in order, such as setting up an app on a phone: an
|
|
46
|
+
ordered list of `bf-step` items, each with a numbered marker joined to the next by a line.
|
|
47
|
+
|
|
48
|
+
Numbered instructions a user follows in order: a `bf-steps` list of `bf-step` items, each with a numbered circle, a short instruction as its title and the details under it, the circles joined by a thin line. It works the same in a desktop web app and in the Capacitor shell.
|
|
49
|
+
|
|
50
|
+
## When to use
|
|
51
|
+
|
|
52
|
+
Use `bf-steps` for instructions the user carries out in order, outside the app or across it: "Set up the app on your phone", "Connect your domain". It is static content, not a wizard: a multi-page stepper that holds state and moves the user from step to step is a flow of pages (in the Capacitor shell, pages pushed in [bf-tabs](https://bitforge-ui.web.app/components/tabs) stacks), and a progress figure for such a flow is a [bf-progress](https://bitforge-ui.web.app/components/progress). Use [bf-list](https://bitforge-ui.web.app/components/list) instead for items that have no order.
|
|
53
|
+
|
|
54
|
+
## Continued lists
|
|
55
|
+
|
|
56
|
+
`start` sets the number of the first step, for a list continued after a paragraph or on another part of the page: `start="3"` numbers the steps 3, 4, 5. It takes a number or a plain attribute.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { Component } from '@angular/core';
|
|
60
|
+
import { BfStepComponent, BfStepsComponent } from '@bitforgehq/ui';
|
|
61
|
+
|
|
62
|
+
/** A list continued after a paragraph: the second list starts at 3. */
|
|
63
|
+
@Component({
|
|
64
|
+
selector: 'app-example-steps-continued',
|
|
65
|
+
imports: [BfStepComponent, BfStepsComponent],
|
|
66
|
+
template: `
|
|
67
|
+
<bf-steps>
|
|
68
|
+
<bf-step title="Open the admin console">Sign in with an administrator account.</bf-step>
|
|
69
|
+
<bf-step title="Add your domain">Enter it without https:// in front.</bf-step>
|
|
70
|
+
</bf-steps>
|
|
71
|
+
<p class="text-body text-text-muted my-4">The console now shows a TXT record for the domain.</p>
|
|
72
|
+
<bf-steps start="3">
|
|
73
|
+
<bf-step title="Add the TXT record">Paste it at your DNS provider.</bf-step>
|
|
74
|
+
<bf-step title="Verify the domain">Changes can take up to an hour to arrive.</bf-step>
|
|
75
|
+
</bf-steps>
|
|
76
|
+
`,
|
|
77
|
+
})
|
|
78
|
+
export class StepsContinuedExampleComponent {}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Desktop
|
|
82
|
+
|
|
83
|
+
Steps are not interactive: no hover, no pointer cursor and no focus stop of their own; only a link or button in a step's content takes focus, with its own focus ring. They render the same at every width: the marker column keeps its 24 px and the content wraps (a long word or URL breaks anywhere), so nothing scrolls sideways at 320 px. Put a code snippet that must not wrap in its own `overflow-x-auto` block.
|
|
84
|
+
|
|
85
|
+
## Theming
|
|
86
|
+
|
|
87
|
+
- Marker: `bg-primary-soft` / `text-primary-strong` (the tonal pair, ≥ 4.5:1 in both modes), `text-label-sm` bold in a 24 px circle (`size-6`, `rounded-full`); a `CanvasText` outline in forced colours.
|
|
88
|
+
- Line: `border-border`, 1 px, hidden after the last step.
|
|
89
|
+
- Title: `text-label` bold in `text`; content: `text-body` in `text-secondary`; `pb-6` between steps.
|
|
90
|
+
|
|
91
|
+
## Accessibility
|
|
92
|
+
|
|
93
|
+
`bf-steps` renders a `role="list"` container and each `bf-step` host is a `role="listitem"` directly inside it, so a screen reader announces the list and each step's position ("1 of 3"). The visible number is `aria-hidden`, so it is not read twice. A list with a `start` other than 1 also sets `aria-posinset` and `aria-setsize` on each step (the size counts the earlier steps too: steps 3 to 5 read "3 of 5"), so the announcement matches the number on screen. The title is text by default; give long steps `headingLevel` on `bf-step` so users can move between them by heading. A lone `bf-step` outside `bf-steps` claims no role.
|
|
94
|
+
|
|
95
|
+
## Inputs
|
|
96
|
+
|
|
97
|
+
| Name | Type | Default | Description |
|
|
98
|
+
| ------- | -------- | ------- | ----------------------------------------------------------------------- |
|
|
99
|
+
| `start` | `number` | `1` | The number of the first step, for a list continued from an earlier one. |
|
|
100
|
+
|
|
101
|
+
## Slots
|
|
102
|
+
|
|
103
|
+
- `default`: The steps, each a `bf-step`.
|
|
104
|
+
|
|
105
|
+
## Notes
|
|
106
|
+
|
|
107
|
+
### Do
|
|
108
|
+
|
|
109
|
+
- Start each title with a verb and keep it to one instruction ("Install the app").
|
|
110
|
+
- Put the details, a code to type or a link to open, in the step's content.
|
|
111
|
+
|
|
112
|
+
### Don't
|
|
113
|
+
|
|
114
|
+
- Don't use steps for a multi-page flow that tracks where the user is; that is a set of pages.
|
|
115
|
+
- Don't number steps yourself in the title; the marker shows the number.
|
|
116
|
+
|
|
117
|
+
## Migration notes
|
|
118
|
+
|
|
119
|
+
### 0.4.0
|
|
120
|
+
|
|
121
|
+
New `bf-steps`: numbered instructions a user follows in order ("Set up the app on your phone"),
|
|
122
|
+
a `role="list"` of `bf-step` items whose markers are joined by a line. `start` (a number, 1 by
|
|
123
|
+
default) continues a list; a continued list sets `aria-posinset` and `aria-setsize` on its steps
|
|
124
|
+
so the announced position matches the visible number.
|
package/ai/components/table.md
CHANGED
|
@@ -114,7 +114,7 @@ Use `bf-table` on a desktop page for records the user scans and compares column
|
|
|
114
114
|
|
|
115
115
|
## Columns and rows
|
|
116
116
|
|
|
117
|
-
`columns` is an array of `BfTableColumn`: the row property `key`, the header `label`, `sortable` and `align`. A default cell shows the row's `key` property as text when it is a string, a number or a boolean, and is empty otherwise (`null`, `undefined`, an object): render a date or an object with a custom cell. `align: 'end'` lines the header and cells up at the inline end and sets tabular figures, for numbers and amounts.
|
|
117
|
+
`columns` is an array of `BfTableColumn`: the row property `key`, the header `label`, `sortable` and `align`. A default cell shows the row's `key` property as text when it is a string, a number or a boolean, and is empty otherwise (`null`, `undefined`, an object): render a date or an object with a custom cell. `align: 'end'` lines the header and cells up at the inline end and sets tabular figures, for numbers and amounts; `align: 'center'` centres the header and each cell's control, for a column of toggles or checkboxes (see Toggle cells). `BfTableColumn.align` is a `BfTableColumnAlign`, the released `BfTableAlign` (`start`, `end`) plus `center`.
|
|
118
118
|
|
|
119
119
|
`rowKey` returns the string that identifies a row in `selection` and keeps each row's DOM when the rows change. By default it reads the row's `id` (a string or a number), and falls back to the row's index when a row has none; pass `[rowKey]="byEmail"` when the key is another property.
|
|
120
120
|
|
|
@@ -240,6 +240,90 @@ export class TableCustomCellsExampleComponent {
|
|
|
240
240
|
}
|
|
241
241
|
```
|
|
242
242
|
|
|
243
|
+
## Toggle cells
|
|
244
|
+
|
|
245
|
+
A settings matrix is a table of switches: rows are what the setting applies to (alert kinds), columns are the options (channels: Email, Push, Slack), and each cell is a `bf-toggle` from a `bfTableCell` template. Keep the row label as the first column, and give the toggle columns `align: 'center'`, which centres the header and the switch in each cell. A toggle in a cell shows no label of its own, so name each one after its column and row with `ariaLabel` (`[ariaLabel]="channel.label + ', ' + alert.label"`, "Push, New CVE"); a screen reader also announces the column header as it moves across. Write the change back from `(changed)` and save it right away, as for any toggle. With `stack="narrow"` (or `coarse` in the Capacitor shell) a phone shows each alert as a tile with the channel labels at the start and the toggles at the end, centred on their label line. In a test, `BfTableHarness.getCellHarness(row, 'Push', BfToggleHarness)` returns a cell's toggle.
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
import { Component, signal } from '@angular/core';
|
|
249
|
+
import {
|
|
250
|
+
BfTableCellDirective,
|
|
251
|
+
BfTableComponent,
|
|
252
|
+
BfToggleComponent,
|
|
253
|
+
type BfTableColumn,
|
|
254
|
+
} from '@bitforgehq/ui';
|
|
255
|
+
|
|
256
|
+
type Channel = 'email' | 'push' | 'slack';
|
|
257
|
+
|
|
258
|
+
interface AlertSetting {
|
|
259
|
+
id: string;
|
|
260
|
+
label: string;
|
|
261
|
+
email: boolean;
|
|
262
|
+
push: boolean;
|
|
263
|
+
slack: boolean;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const CHANNELS: readonly { key: Channel; label: string }[] = [
|
|
267
|
+
{ key: 'email', label: 'Email' },
|
|
268
|
+
{ key: 'push', label: 'Push' },
|
|
269
|
+
{ key: 'slack', label: 'Slack' },
|
|
270
|
+
];
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* A settings matrix of toggle cells.
|
|
274
|
+
*
|
|
275
|
+
* Rows are alert kinds and columns are channels, each cell a `bf-toggle` named after its column
|
|
276
|
+
* and row ("Push, New CVE") with `ariaLabel`, in centred columns (`align: 'center'`). The row label
|
|
277
|
+
* stays the first column, and `stack="narrow"` turns each row into a tile with the toggles at its
|
|
278
|
+
* end in a narrow panel or on a phone.
|
|
279
|
+
*/
|
|
280
|
+
@Component({
|
|
281
|
+
selector: 'app-example-table-toggle-matrix',
|
|
282
|
+
imports: [BfTableComponent, BfTableCellDirective, BfToggleComponent],
|
|
283
|
+
template: `
|
|
284
|
+
<div class="flex w-152 max-w-full flex-col gap-3">
|
|
285
|
+
<bf-table label="Alert channels" stack="narrow" [rows]="alerts()" [columns]="columns">
|
|
286
|
+
@for (channel of channels; track channel.key) {
|
|
287
|
+
<ng-template [bfTableCell]="channel.key" [bfTableCellOf]="alerts()" let-alert>
|
|
288
|
+
<bf-toggle
|
|
289
|
+
[ariaLabel]="channel.label + ', ' + alert.label"
|
|
290
|
+
[checked]="alert[channel.key]"
|
|
291
|
+
(changed)="set(alert, channel.key, $event)"
|
|
292
|
+
/>
|
|
293
|
+
</ng-template>
|
|
294
|
+
}
|
|
295
|
+
</bf-table>
|
|
296
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
297
|
+
</div>
|
|
298
|
+
`,
|
|
299
|
+
})
|
|
300
|
+
export class TableToggleMatrixExampleComponent {
|
|
301
|
+
readonly channels = CHANNELS;
|
|
302
|
+
readonly columns: readonly BfTableColumn[] = [
|
|
303
|
+
{ key: 'label', label: 'Alert' },
|
|
304
|
+
...CHANNELS.map((channel): BfTableColumn => ({ ...channel, align: 'center' })),
|
|
305
|
+
];
|
|
306
|
+
readonly alerts = signal<readonly AlertSetting[]>([
|
|
307
|
+
{ id: 'critical', label: 'Critical finding', email: true, push: true, slack: true },
|
|
308
|
+
{ id: 'cve', label: 'New CVE', email: true, push: false, slack: true },
|
|
309
|
+
{ id: 'ci', label: 'Failed CI run', email: false, push: false, slack: true },
|
|
310
|
+
{ id: 'digest', label: 'Weekly digest', email: true, push: false, slack: false },
|
|
311
|
+
]);
|
|
312
|
+
readonly status = signal('No change yet.');
|
|
313
|
+
|
|
314
|
+
/** Writes one cell back; a real app saves the setting right away. */
|
|
315
|
+
set(alert: AlertSetting, channel: Channel, on: boolean): void {
|
|
316
|
+
this.alerts.update((rows) =>
|
|
317
|
+
rows.map((row) => (row.id === alert.id ? { ...row, [channel]: on } : row)),
|
|
318
|
+
);
|
|
319
|
+
const name = CHANNELS.find((c) => c.key === channel)?.label ?? channel;
|
|
320
|
+
this.status.set(`${name} for ${alert.label} turned ${on ? 'on' : 'off'}.`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Under a coarse pointer every toggle is a 44 px target inside its own cell (a row grows to fit it), and a cell's horizontal padding keeps neighbouring toggles apart, so no two hit areas overlap. A `sm` toggle (40 px wide) takes a 44 px width under touch as well. ArrowUp and ArrowDown move between the toggles of one column.
|
|
326
|
+
|
|
243
327
|
## Empty state
|
|
244
328
|
|
|
245
329
|
While `rows` is empty the table shows one full-width row that holds the `empty` slot, such as a `bf-empty-state` with the action that creates the first record. The header stays, so the user still sees what the table holds.
|
|
@@ -586,7 +670,7 @@ Put the table on a `bf-card` (or another surface) yourself; it has no background
|
|
|
586
670
|
|
|
587
671
|
## Accessibility
|
|
588
672
|
|
|
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.
|
|
673
|
+
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. A control in a custom cell brings its own name: give a label-less one (a toggle in a settings matrix) `ariaLabel` from its column and row, such as "Email, Critical finding", so it is named the same in the table and in a stacked tile. The empty row's cell spans every column.
|
|
590
674
|
|
|
591
675
|
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
676
|
|
|
@@ -655,6 +739,7 @@ Selects or clears one row.
|
|
|
655
739
|
### Do
|
|
656
740
|
|
|
657
741
|
- Give every table a `label`, and every sortable column a short header.
|
|
742
|
+
- In a settings matrix, keep the row label as the first column, centre the toggle columns and name every toggle after its column and row.
|
|
658
743
|
- Sort and filter the rows in a `computed()` from `sort` and your filters.
|
|
659
744
|
- 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`.
|
|
660
745
|
|
|
@@ -666,6 +751,17 @@ Selects or clears one row.
|
|
|
666
751
|
|
|
667
752
|
## Migration notes
|
|
668
753
|
|
|
754
|
+
### 0.4.0
|
|
755
|
+
|
|
756
|
+
New: toggle cells for a settings matrix (rows of alert kinds, columns of channels, a `bf-toggle`
|
|
757
|
+
in each cell), documented with the `table/toggle-matrix` example. `BfTableColumn.align` takes
|
|
758
|
+
`'center'`: the header and each cell's control are centred, and while stacked the control sits at
|
|
759
|
+
the tile's end, centred on its label line. New type `BfTableColumnAlign` (`BfTableAlign` plus
|
|
760
|
+
`'center'`; `BfTableAlign` keeps its two members). A stacked cell of a start or end column keeps
|
|
761
|
+
its baseline alignment. `@bitforgehq/ui/testing`: new `BfTableHarness.getCellHarness(row, column,
|
|
762
|
+
query)` returns the harness of a control in one cell, and `selectRow()`, `getSelectedCount()`
|
|
763
|
+
read only the selection column's checkboxes, never a checkbox or toggle in a custom cell.
|
|
764
|
+
|
|
669
765
|
### 0.3.3
|
|
670
766
|
|
|
671
767
|
Fix: stacked rows (`stack="narrow"` and `stack="coarse"`) show their column labels again when the
|