@bitforgehq/ui 0.3.3 → 0.5.0-next.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 +394 -0
- package/README.md +14 -14
- package/ai/MANIFEST.index.json +101 -2
- package/ai/MANIFEST.json +2630 -746
- package/ai/REFERENCE.index.json +196 -0
- package/ai/REFERENCE.json +1625 -140
- package/ai/UI_GUIDE.md +83 -21
- package/ai/components/app-frame.md +1 -1
- package/ai/components/badge.md +39 -2
- package/ai/components/chat-composer.md +357 -0
- package/ai/components/chat-message.md +251 -0
- package/ai/components/chat-thread.md +521 -0
- package/ai/components/checkbox.md +7 -0
- package/ai/components/combobox.md +12 -0
- package/ai/components/date-input.md +9 -0
- package/ai/components/datetime-input.md +2 -0
- package/ai/components/field.md +10 -0
- package/ai/components/file-input.md +498 -0
- package/ai/components/header.md +1 -1
- package/ai/components/image-input.md +368 -0
- package/ai/components/image-viewer.md +309 -0
- package/ai/components/list-item.md +105 -9
- package/ai/components/media-card.md +478 -0
- package/ai/components/menu-button.md +2 -0
- package/ai/components/menu.md +10 -0
- package/ai/components/multi-combobox.md +7 -0
- package/ai/components/nav-group.md +10 -3
- package/ai/components/nav-item.md +11 -2
- package/ai/components/number-input.md +2 -7
- package/ai/components/popover.md +13 -0
- package/ai/components/radio-group.md +6 -0
- package/ai/components/segmented.md +14 -2
- package/ai/components/select.md +100 -42
- package/ai/components/sidebar-toggle.md +16 -9
- package/ai/components/sidebar.md +41 -13
- package/ai/components/step.md +112 -0
- package/ai/components/steps.md +124 -0
- package/ai/components/tab-group.md +18 -2
- package/ai/components/table.md +115 -4
- package/ai/components/theme-menu-button.md +1 -0
- package/ai/components/time-input.md +2 -0
- package/ai/components/timeline-event.md +144 -0
- package/ai/components/timeline.md +201 -0
- package/ai/components/toggle.md +11 -1
- package/ai/components/toolbar.md +7 -18
- package/ai/reference/bf-action-directive.md +3 -1
- package/ai/reference/bf-badge-emphasis.md +27 -0
- package/ai/reference/bf-chat-composer-harness.md +64 -0
- package/ai/reference/bf-chat-message-role.md +27 -0
- package/ai/reference/bf-chat-message-status.md +28 -0
- package/ai/reference/bf-fade-in-directive.md +18 -0
- package/ai/reference/bf-file-input-harness.md +67 -0
- package/ai/reference/bf-file-input-variant.md +27 -0
- package/ai/reference/bf-image-input-harness.md +67 -0
- package/ai/reference/bf-image-input-shape.md +27 -0
- package/ai/reference/bf-image-viewer-actions-context.md +29 -0
- package/ai/reference/bf-image-viewer-harness.md +87 -0
- package/ai/reference/bf-image-viewer-image.md +25 -0
- package/ai/reference/bf-image-viewer-options.md +33 -0
- package/ai/reference/bf-image-viewer-origin.md +23 -0
- package/ai/reference/bf-image-viewer-ref.md +55 -0
- package/ai/reference/bf-image-viewer-service.md +32 -0
- package/ai/reference/bf-labels.md +105 -74
- package/ai/reference/bf-media-card-aspect.md +28 -0
- package/ai/reference/bf-media-card-harness.md +66 -0
- package/ai/reference/bf-motion-list-directive.md +22 -0
- package/ai/reference/bf-prose-directive.md +16 -0
- package/ai/reference/bf-rejected-file-reason.md +28 -0
- package/ai/reference/bf-rejected-file.md +28 -0
- package/ai/reference/bf-select-harness.md +14 -5
- package/ai/reference/bf-select-option.md +2 -0
- package/ai/reference/bf-size.md +3 -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/bf_table_sort_select.md +23 -0
- package/ai/reference/provide-bf-labels.md +10 -3
- package/ai/reference/provide-bf-select.md +20 -0
- package/bin/bf-ui.js +1 -0
- 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-overlay.mjs +2649 -606
- package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-testing.mjs +406 -11
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui.mjs +11149 -6957
- package/fesm2022/bitforgehq-ui.mjs.map +1 -1
- package/llms-full.txt +5219 -141
- package/llms.txt +40 -1
- package/package.json +2 -1
- package/theme.css +84 -0
- package/types/bitforgehq-ui-overlay.d.ts +684 -7
- package/types/bitforgehq-ui-testing.d.ts +510 -7
- package/types/bitforgehq-ui.d.ts +1757 -186
|
@@ -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.
|
|
@@ -148,6 +148,8 @@ export class TabGroupLazyExampleComponent {
|
|
|
148
148
|
|
|
149
149
|
`[(selected)]` holds the shown panel's `value`; `(changed)` emits the new value after a user change, never for a write of `selected`. While `selected` matches no panel (unset, or a value that went away), the first enabled panel shows; `selected` itself keeps its value until the user picks a tab.
|
|
150
150
|
|
|
151
|
+
The underline slides to the newly selected tab, changing width as it goes (200 ms, `--bf-duration-layout`); it lands in place at once on the first render and after a resize, and always under `prefers-reduced-motion`. The panel a user switches to fades in over 200 ms (`animate-panel-in`); the first panel shown, and every panel under reduced motion, appears at once.
|
|
152
|
+
|
|
151
153
|
## Sizes and width
|
|
152
154
|
|
|
153
155
|
`size` sets the tabs' height: `sm` 32 px, `md` 40 px, `lg` 48 px. Unset, it follows `provideBfDefaults()`'s `controlSize`, `md` by default. `fitted` stretches the tabs to share the group's width (two or three tabs on a phone). When the tabs do not fit, the row scrolls sideways; focusing a tab with the keyboard scrolls it into view.
|
|
@@ -159,7 +161,7 @@ Under a mouse an unselected tab takes a `surface-muted` fill and `text` on hover
|
|
|
159
161
|
## Theming
|
|
160
162
|
|
|
161
163
|
- Tabs: `text-muted` at rest, hover `surface-muted` with `text`, pressed `surface-pressed`; top corners `rounded-tile`.
|
|
162
|
-
- Selected tab: `primary-text` with a 2 px `primary` underline on its bottom edge, so the selection never rests on colour alone (`CanvasText` under forced colours; `text-disabled` on a disabled selected tab).
|
|
164
|
+
- Selected tab: `primary-text` with a 2 px `primary` underline on its bottom edge, so the selection never rests on colour alone (`CanvasText` under forced colours; `text-disabled` on a disabled selected tab). It slides over `--bf-duration-layout` with the `ease-md` curve.
|
|
163
165
|
- Disabled tab: `text-disabled` (never opacity).
|
|
164
166
|
- The row: a 1 px `border` line under the tabs, across the group's width.
|
|
165
167
|
- Badge: `primary` with `on-primary` text, `rounded-control`, `text-micro`.
|
|
@@ -168,7 +170,7 @@ Under a mouse an unselected tab takes a `surface-muted` fill and `text` on hover
|
|
|
168
170
|
|
|
169
171
|
## Accessibility
|
|
170
172
|
|
|
171
|
-
The row is a `role="tablist"` named by `label`, else `provideBfLabels()`'s `tabGroup` ("Tabs"); give each group on a page its own `label`. Each tab is a native `<button role="tab">` with `aria-selected` and `aria-controls` naming its panel; each `bf-tab-panel` is a `role="tabpanel"` with `aria-labelledby` naming its tab and `tabindex="0"`, and an unselected panel is `hidden`. Only the selected tab is in the tab order (a roving tab stop), so <kbd>Tab</kbd> moves from it into the panel. Selection follows focus (automatic activation): the arrow keys, <kbd>Home</kbd> and <kbd>End</kbd> move to a tab and show its panel at once, so keep a panel's first render quick. A disabled tab is native-disabled and skipped. A badge's pill is `aria-hidden`; its count is read after the label from visually hidden text (`tabBadge(n)`, "3 new").
|
|
173
|
+
The row is a `role="tablist"` named by `label`, else `provideBfLabels()`'s `tabGroup` ("Tabs"); give each group on a page its own `label`. Each tab is a native `<button role="tab">` with `aria-selected` and `aria-controls` naming its panel; each `bf-tab-panel` is a `role="tabpanel"` with `aria-labelledby` naming its tab and `tabindex="0"`, and an unselected panel is `hidden`. Only the selected tab is in the tab order (a roving tab stop), so <kbd>Tab</kbd> moves from it into the panel. Selection follows focus (automatic activation): the arrow keys, <kbd>Home</kbd> and <kbd>End</kbd> move to a tab and show its panel at once, so keep a panel's first render quick. A disabled tab is native-disabled and skipped. The sliding underline is one decorative `aria-hidden` element of the row. A badge's pill is `aria-hidden`; its count is read after the label from visually hidden text (`tabBadge(n)`, "3 new").
|
|
172
174
|
|
|
173
175
|
### Keyboard
|
|
174
176
|
|
|
@@ -213,6 +215,20 @@ The row is a `role="tablist"` named by `label`, else `provideBfLabels()`'s `tabG
|
|
|
213
215
|
|
|
214
216
|
## Migration notes
|
|
215
217
|
|
|
218
|
+
### 0.5.0
|
|
219
|
+
|
|
220
|
+
The selected tab's underline is now one indicator element of the row that slides to the newly
|
|
221
|
+
selected tab, changing width as it goes (`--bf-duration-layout`, `ease-md`). The first placement
|
|
222
|
+
and a resize land at once, and so does everything under `prefers-reduced-motion`. It is measured
|
|
223
|
+
from the selected tab's inline-start offset, so it mirrors under `dir="rtl"`, and is re-measured
|
|
224
|
+
on resize and when labels, badges, size or `fitted` change. Until it has been measured (jsdom,
|
|
225
|
+
server render, a hidden group) the tab keeps drawing its own underline, so unit tests and the
|
|
226
|
+
first paint are unchanged. The indicator is `aria-hidden`; roles, keyboard and focus are
|
|
227
|
+
unchanged.
|
|
228
|
+
|
|
229
|
+
The panel a user switches to fades in (200 ms of opacity, new `--animate-panel-in`, behind
|
|
230
|
+
`motion-safe:`); the panel shown on the first render never does.
|
|
231
|
+
|
|
216
232
|
### 0.3.1
|
|
217
233
|
|
|
218
234
|
New `bf-tab-group`: in-page tabs over the content they switch, in both shells. A `tablist` of
|
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.
|
|
@@ -405,11 +489,12 @@ export class TablePagedExampleComponent {
|
|
|
405
489
|
|
|
406
490
|
`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
491
|
|
|
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
|
|
492
|
+
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. The Sort by select is a native `<select>` unless the app adds `provideBfSelect()` from `@bitforgehq/ui/overlay` to its providers: then it is the styled [bf-select](https://bitforge-ui.web.app/components/select) (still native under touch), through the `BF_TABLE_SORT_SELECT` token, so `@bitforgehq/ui` itself never loads the CDK. In a test, `BfTableHarness.getSortSelect()` returns that select's `BfSelectHarness`, in either form.
|
|
409
493
|
|
|
410
494
|
```ts
|
|
411
495
|
import { Component, computed, signal } from '@angular/core';
|
|
412
496
|
import {
|
|
497
|
+
BF_TABLE_SORT_SELECT,
|
|
413
498
|
BfBadgeComponent,
|
|
414
499
|
BfSegmentedComponent,
|
|
415
500
|
BfTableCellDirective,
|
|
@@ -418,6 +503,7 @@ import {
|
|
|
418
503
|
type BfTableColumn,
|
|
419
504
|
type BfTableSort,
|
|
420
505
|
} from '@bitforgehq/ui';
|
|
506
|
+
import { BfSelectComponent } from '@bitforgehq/ui/overlay';
|
|
421
507
|
|
|
422
508
|
interface Task {
|
|
423
509
|
id: string;
|
|
@@ -442,10 +528,16 @@ type Width = 'phone' | 'full';
|
|
|
442
528
|
* than 36rem: the selection checkbox and the first cell head the tile, and a bar above the tiles
|
|
443
529
|
* holds Select all and a Sort by select, whose first option, Not sorted, shows while `sort` is
|
|
444
530
|
* null and clears it. Switch the box between phone width and full width.
|
|
531
|
+
*
|
|
532
|
+
* The Sort by select is the styled `bf-select`. In an app, add `provideBfSelect()` from
|
|
533
|
+
* `@bitforgehq/ui/overlay` to `app.config.ts` once; this page provides `BF_TABLE_SORT_SELECT`
|
|
534
|
+
* on the example instead, so the docs site loads the select only with this example.
|
|
445
535
|
*/
|
|
446
536
|
@Component({
|
|
447
537
|
selector: 'app-example-table-stacked',
|
|
448
538
|
imports: [BfTableComponent, BfTableCellDirective, BfBadgeComponent, BfSegmentedComponent],
|
|
539
|
+
// In an app: provideBfSelect() in app.config.ts (see the comment above).
|
|
540
|
+
providers: [{ provide: BF_TABLE_SORT_SELECT, useValue: BfSelectComponent }],
|
|
449
541
|
template: `
|
|
450
542
|
<!-- A stacking table is a size container: its width comes from its parent, never its columns. -->
|
|
451
543
|
<div class="flex w-152 max-w-full flex-col gap-3">
|
|
@@ -586,7 +678,7 @@ Put the table on a `bf-card` (or another surface) yourself; it has no background
|
|
|
586
678
|
|
|
587
679
|
## Accessibility
|
|
588
680
|
|
|
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.
|
|
681
|
+
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
682
|
|
|
591
683
|
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
684
|
|
|
@@ -600,7 +692,7 @@ A table that can stack (`stack` other than `never`) sets explicit `table`, `rowg
|
|
|
600
692
|
| <kbd>Space</kbd> | Toggles the focused checkbox, or sorts by the focused header |
|
|
601
693
|
| <kbd>Enter</kbd> | Sorts by the focused header |
|
|
602
694
|
|
|
603
|
-
A stacked table's Sort by select
|
|
695
|
+
A stacked table's Sort by select has its own keys: the native select's, or with `provideBfSelect()` those of [bf-select](https://bitforge-ui.web.app/components/select#keyboard).
|
|
604
696
|
|
|
605
697
|
Text fields, selects and composite widgets (radios, comboboxes, sliders) in a cell keep their own arrow keys.
|
|
606
698
|
|
|
@@ -655,6 +747,7 @@ Selects or clears one row.
|
|
|
655
747
|
### Do
|
|
656
748
|
|
|
657
749
|
- Give every table a `label`, and every sortable column a short header.
|
|
750
|
+
- 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
751
|
- Sort and filter the rows in a `computed()` from `sort` and your filters.
|
|
659
752
|
- 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
753
|
|
|
@@ -666,6 +759,24 @@ Selects or clears one row.
|
|
|
666
759
|
|
|
667
760
|
## Migration notes
|
|
668
761
|
|
|
762
|
+
### 0.5.0
|
|
763
|
+
|
|
764
|
+
New `BF_TABLE_SORT_SELECT` injection token: the component a stacked table renders as its "Sort
|
|
765
|
+
by" select (`null`, the default, keeps the native one). `provideBfSelect()` from
|
|
766
|
+
`@bitforgehq/ui/overlay` sets it to the styled `bf-select`, so the core entry still never imports
|
|
767
|
+
the CDK. `BfTableHarness.getSortSelect()` returns a working `BfSelectHarness` either way.
|
|
768
|
+
|
|
769
|
+
### 0.4.0
|
|
770
|
+
|
|
771
|
+
New: toggle cells for a settings matrix (rows of alert kinds, columns of channels, a `bf-toggle`
|
|
772
|
+
in each cell), documented with the `table/toggle-matrix` example. `BfTableColumn.align` takes
|
|
773
|
+
`'center'`: the header and each cell's control are centred, and while stacked the control sits at
|
|
774
|
+
the tile's end, centred on its label line. New type `BfTableColumnAlign` (`BfTableAlign` plus
|
|
775
|
+
`'center'`; `BfTableAlign` keeps its two members). A stacked cell of a start or end column keeps
|
|
776
|
+
its baseline alignment. `@bitforgehq/ui/testing`: new `BfTableHarness.getCellHarness(row, column,
|
|
777
|
+
query)` returns the harness of a control in one cell, and `selectRow()`, `getSelectedCount()`
|
|
778
|
+
read only the selection column's checkboxes, never a checkbox or toggle in a custom cell.
|
|
779
|
+
|
|
669
780
|
### 0.3.3
|
|
670
781
|
|
|
671
782
|
Fix: stacked rows (`stack="narrow"` and `stack="coarse"`) show their column labels again when the
|
|
@@ -127,6 +127,7 @@ The button consumes `bf-menu-button`'s tokens (`bf-button`'s, or `bf-icon-button
|
|
|
127
127
|
- The three items are `role="menuitemradio"` with `aria-checked`, so a screen reader announces the current choice. The dot is drawn in CSS and adds nothing to the name.
|
|
128
128
|
- Choosing an item closes the menu and returns focus to the button.
|
|
129
129
|
- Hit area: at least 32 px, 44 px under a coarse pointer, as `bf-menu-button`.
|
|
130
|
+
- Motion: the menu fades and grows out of the button as it opens, and closes at once. Under `prefers-reduced-motion` it only fades in.
|
|
130
131
|
|
|
131
132
|
### Keyboard
|
|
132
133
|
|
|
@@ -138,6 +138,8 @@ The field shows the `primary` border and focus ring while any part has focus, th
|
|
|
138
138
|
|
|
139
139
|
The parts are a `role="group"` named by `label` (else `ariaLabel` or `ariaLabelledby`); each part is a `role="spinbutton"` named from the `timeInputHour`, `timeInputMinute`, `timeInputSecond` and `timeInputDayPeriod` labels, with `aria-valuemin`, `aria-valuemax`, `aria-valuenow` and `aria-valuetext` (the AM or PM name), and the field's `aria-invalid`, `aria-errormessage`, `aria-required`, `aria-readonly` and `aria-disabled`. The clock button is named by `timeInputOpenList`, with `aria-haspopup="dialog"`, `aria-expanded` and `aria-controls`. The list is a `role="listbox"` named by the same label, its times `role="option"` with `aria-selected` and `aria-disabled`; focus stays on the list and `aria-activedescendant` names the active time.
|
|
140
140
|
|
|
141
|
+
Motion: the list grows out of the field as it opens (a fade, a slight scale and a 4 px slide, 180 ms) and fades back as it closes (130 ms). Focus returns at once and the closing list is inert. Under `prefers-reduced-motion` it only fades.
|
|
142
|
+
|
|
141
143
|
### Keyboard
|
|
142
144
|
|
|
143
145
|
| Key | Where | Action |
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# bf-timeline-event
|
|
2
|
+
|
|
3
|
+
One timeline event with a coloured marker, its text, actor and time
|
|
4
|
+
|
|
5
|
+
`BfTimelineEventComponent` 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 { BfTimelineComponent, BfTimelineEventComponent } from '@bitforgehq/ui';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The bf-timeline-event overview.
|
|
16
|
+
*
|
|
17
|
+
* Live demo of `bf-timeline-event`: one event per tone in a `bf-timeline`, with and without an
|
|
18
|
+
* actor, one with a link in its text and one with a relative `timeText`.
|
|
19
|
+
*/
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'app-example-timeline-event-overview',
|
|
22
|
+
imports: [RouterLink, BfTimelineComponent, BfTimelineEventComponent],
|
|
23
|
+
template: `
|
|
24
|
+
<bf-timeline label="Event tones" class="max-w-xl">
|
|
25
|
+
<bf-timeline-event actor="Lena Meier" time="2026-10-28T15:40:00Z" tone="primary">
|
|
26
|
+
Opened
|
|
27
|
+
<a routerLink="/components/timeline" fragment="links-in-events">CVE-2026-1234</a>
|
|
28
|
+
</bf-timeline-event>
|
|
29
|
+
<bf-timeline-event actor="CI" time="2026-10-28T15:10:00Z" tone="success">
|
|
30
|
+
All checks passed
|
|
31
|
+
</bf-timeline-event>
|
|
32
|
+
<bf-timeline-event actor="Dependency bot" time="2026-10-28T14:00:00Z" tone="warning">
|
|
33
|
+
Found an outdated base image
|
|
34
|
+
</bf-timeline-event>
|
|
35
|
+
<bf-timeline-event actor="CI" time="2026-10-28T13:05:00Z" tone="danger">
|
|
36
|
+
CI failed on the security scan
|
|
37
|
+
</bf-timeline-event>
|
|
38
|
+
<bf-timeline-event time="2026-10-28T12:00:00Z" timeText="4 hours ago" tone="info">
|
|
39
|
+
A new advisory was published
|
|
40
|
+
</bf-timeline-event>
|
|
41
|
+
<bf-timeline-event actor="Jonas Keller" time="2026-10-27T16:02:00Z">
|
|
42
|
+
Assigned the finding to Lena Meier
|
|
43
|
+
</bf-timeline-event>
|
|
44
|
+
</bf-timeline>
|
|
45
|
+
`,
|
|
46
|
+
})
|
|
47
|
+
export class TimelineEventOverviewExampleComponent {}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Overview
|
|
51
|
+
|
|
52
|
+
One event of a `bf-timeline`: a marker coloured by `tone`, the event text (the default slot,
|
|
53
|
+
which may hold links), then who did it (`actor`) and when (`time`, in a `time` element).
|
|
54
|
+
|
|
55
|
+
One event of a [`bf-timeline`](https://bitforge-ui.web.app/components/timeline): a marker coloured by `tone`, the event text from the default slot, then a meta line with `actor` and `time`. Place events in a `bf-timeline`, which gives them the list semantics and the time format; the line between the markers ends at the last event.
|
|
56
|
+
|
|
57
|
+
## When to use
|
|
58
|
+
|
|
59
|
+
Use `bf-timeline-event` for each dated entry of a `bf-timeline`: a change, a comment, a CI run. Use [bf-list-item](https://bitforge-ui.web.app/components/list-item) instead for a row the user opens or acts on.
|
|
60
|
+
|
|
61
|
+
## Basic usage
|
|
62
|
+
|
|
63
|
+
`actor`, `time` (a `Date` or an ISO 8601 string) and `tone` are inputs; the text is the content and may hold links, which take the link style. `timeText` replaces the formatted time with your own text while the `datetime` keeps the exact value. `bfSlot="end"` holds a small action, at the inline end on a wide timeline and below the text on a narrow one.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { Component } from '@angular/core';
|
|
67
|
+
import { BfTimelineComponent, BfTimelineEventComponent } from '@bitforgehq/ui';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* An event's actor, time and tone.
|
|
71
|
+
*
|
|
72
|
+
* Two events of a CVE side panel: one by a person with an exact time, one by a bot with a relative
|
|
73
|
+
* `timeText` whose `datetime` keeps the exact value.
|
|
74
|
+
*/
|
|
75
|
+
@Component({
|
|
76
|
+
selector: 'app-example-timeline-event-basic',
|
|
77
|
+
imports: [BfTimelineComponent, BfTimelineEventComponent],
|
|
78
|
+
template: `
|
|
79
|
+
<bf-timeline label="CVE history" class="max-w-sm">
|
|
80
|
+
<bf-timeline-event actor="Lena Meier" time="2026-10-28T15:40:00Z" tone="warning">
|
|
81
|
+
Accepted the risk until the next release
|
|
82
|
+
</bf-timeline-event>
|
|
83
|
+
<bf-timeline-event
|
|
84
|
+
actor="Advisory bot"
|
|
85
|
+
time="2026-10-28T10:00:00Z"
|
|
86
|
+
timeText="6 hours ago"
|
|
87
|
+
tone="info"
|
|
88
|
+
>
|
|
89
|
+
Imported the advisory
|
|
90
|
+
</bf-timeline-event>
|
|
91
|
+
</bf-timeline>
|
|
92
|
+
`,
|
|
93
|
+
})
|
|
94
|
+
export class TimelineEventBasicExampleComponent {}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Desktop
|
|
98
|
+
|
|
99
|
+
An event has no hover and no focus stop of its own; only the links in its text and the controls in its `end` slot are interactive. It renders the same inside `provideBfShell()`.
|
|
100
|
+
|
|
101
|
+
## Theming
|
|
102
|
+
|
|
103
|
+
- Marker: `text-subtle`, `primary`, `success`, `warning`, `danger` or `info`; `CanvasText` under forced colours. Line: `border`.
|
|
104
|
+
- Text `text-body` in `text`, links `primary-text`; meta line `text-caption` in `text-muted`.
|
|
105
|
+
|
|
106
|
+
## Accessibility
|
|
107
|
+
|
|
108
|
+
Inside a `bf-timeline` the event is a `listitem`; on its own it has no role. The marker is `aria-hidden`; the time is a `time` element with an ISO `datetime`, and the separator between actor and time is hidden from assistive technology.
|
|
109
|
+
|
|
110
|
+
## Inputs
|
|
111
|
+
|
|
112
|
+
| Name | Type | Default | Description |
|
|
113
|
+
| ---------- | ------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
114
|
+
| `actor` | `string` | `''` | Who did it, such as a person or a bot; empty for a system event. |
|
|
115
|
+
| `time` | `Date \| string \| null` | `null` | When it happened: a Date or an ISO 8601 string; empty or invalid shows no time. |
|
|
116
|
+
| `timeText` | `string` | `''` | Shown in place of the formatted time, such as "vor 2 Std."; the `time` element's `datetime` keeps the exact value. |
|
|
117
|
+
| `tone` | `BfTimelineTone` (`'neutral' \| 'primary' \| 'success' \| 'warning' \| 'danger' \| 'info'`) | `'neutral'` | The marker's colour. |
|
|
118
|
+
|
|
119
|
+
## Slots
|
|
120
|
+
|
|
121
|
+
- `default`: The event text; links in it take the link style.
|
|
122
|
+
- `end`: Content at the inline end on a wide timeline and below the text on a narrow one, such as a button.
|
|
123
|
+
|
|
124
|
+
## Notes
|
|
125
|
+
|
|
126
|
+
### Do
|
|
127
|
+
|
|
128
|
+
- Write the text as what happened ("Assigned to Lena Meier"), and link the object it names.
|
|
129
|
+
|
|
130
|
+
### Don't
|
|
131
|
+
|
|
132
|
+
- Don't put more than one small action in the `end` slot; open the details elsewhere.
|
|
133
|
+
|
|
134
|
+
## Migration notes
|
|
135
|
+
|
|
136
|
+
### 0.4.0
|
|
137
|
+
|
|
138
|
+
New: `bf-timeline-event`, one event of a `bf-timeline`: a 10 px marker coloured by `tone`
|
|
139
|
+
(`neutral`, `primary`, `success`, `warning`, `danger`, `info`; new type `BfTimelineTone`) on a
|
|
140
|
+
`border` line that ends at the last event, the event text in the default slot (links in it take
|
|
141
|
+
the link style), then `actor` and `time` (a `Date` or an ISO 8601 string, in a `time` element;
|
|
142
|
+
`timeText` shows a relative text while `datetime` keeps the exact value; an invalid time shows
|
|
143
|
+
nothing). A `bfSlot="end"` action sits at the inline end, or under the text once the event is
|
|
144
|
+
narrow.
|