@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
|
@@ -98,6 +98,89 @@ export class EmptyStateActionExampleComponent {
|
|
|
98
98
|
|
|
99
99
|
Set `headingLevel` to `3` when the empty state sits inside a section that already opened with an `h2`, so the page outline stays in order.
|
|
100
100
|
|
|
101
|
+
## As the page
|
|
102
|
+
|
|
103
|
+
When the empty state is the whole page (a not-found page, a no-access page, an app's first run), its title is the page's heading: set `headingLevel` to `1` so it renders the `h1`, and give the page no other `h1`. Centre it in the content area: a column with a minimum height (`min-h-[60vh]`) and `justify-center`. Put the primary action first in the `actions` slot (a `bfButton` link home) and an optional secondary link after it (a `text` `bfButton`, such as "Contact support").
|
|
104
|
+
|
|
105
|
+
The preview below sits inside this docs page, which already has its `h1`, so it keeps `headingLevel` 2 in a shorter area; a real page uses `1`, as in the code-only example after it.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { Component } from '@angular/core';
|
|
109
|
+
import { RouterLink } from '@angular/router';
|
|
110
|
+
import { BfButtonComponent, BfEmptyStateComponent } from '@bitforgehq/ui';
|
|
111
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
112
|
+
import { FileQuestionMark } from 'lucide';
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* An empty state as a whole page.
|
|
116
|
+
*
|
|
117
|
+
* Centred in a tall content area, with a primary action and a secondary link. This preview sits
|
|
118
|
+
* inside a docs page that has its own `h1`, so it uses `headingLevel` 2; on a real not-found page
|
|
119
|
+
* the empty state is the page, so set it to 1 (see the next example).
|
|
120
|
+
*/
|
|
121
|
+
@Component({
|
|
122
|
+
selector: 'app-example-empty-state-page',
|
|
123
|
+
imports: [RouterLink, BfButtonComponent, BfEmptyStateComponent, BfIconComponent],
|
|
124
|
+
template: `
|
|
125
|
+
<div class="flex min-h-96 flex-col justify-center">
|
|
126
|
+
<bf-empty-state
|
|
127
|
+
title="Page not found"
|
|
128
|
+
description="The page may have moved, or the link has a typo."
|
|
129
|
+
[headingLevel]="2"
|
|
130
|
+
>
|
|
131
|
+
<bf-icon bfSlot="icon" [icon]="fileQuestion" size="lg" />
|
|
132
|
+
<a bfSlot="actions" bfButton routerLink="/">Go to the home page</a>
|
|
133
|
+
<a bfSlot="actions" bfButton variant="text" href="mailto:support@example.com">
|
|
134
|
+
Contact support
|
|
135
|
+
</a>
|
|
136
|
+
</bf-empty-state>
|
|
137
|
+
</div>
|
|
138
|
+
`,
|
|
139
|
+
})
|
|
140
|
+
export class EmptyStatePageExampleComponent {
|
|
141
|
+
protected readonly fileQuestion = FileQuestionMark;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
import { Component } from '@angular/core';
|
|
147
|
+
import { RouterLink } from '@angular/router';
|
|
148
|
+
import { BfButtonComponent, BfEmptyStateComponent } from '@bitforgehq/ui';
|
|
149
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
150
|
+
import { FileQuestionMark } from 'lucide';
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* A not-found page whose empty state is the page heading.
|
|
154
|
+
*
|
|
155
|
+
* The route's whole content: `headingLevel` 1 renders the title as the page's `h1`, and the
|
|
156
|
+
* `min-h-[60vh]` area centres the empty state vertically in the content column.
|
|
157
|
+
*
|
|
158
|
+
* @preview none
|
|
159
|
+
*/
|
|
160
|
+
@Component({
|
|
161
|
+
selector: 'app-example-empty-state-not-found-page',
|
|
162
|
+
imports: [RouterLink, BfButtonComponent, BfEmptyStateComponent, BfIconComponent],
|
|
163
|
+
template: `
|
|
164
|
+
<div class="flex min-h-[60vh] flex-col justify-center">
|
|
165
|
+
<bf-empty-state
|
|
166
|
+
title="Page not found"
|
|
167
|
+
description="The page may have moved, or the link has a typo."
|
|
168
|
+
[headingLevel]="1"
|
|
169
|
+
>
|
|
170
|
+
<bf-icon bfSlot="icon" [icon]="fileQuestion" size="lg" />
|
|
171
|
+
<a bfSlot="actions" bfButton routerLink="/">Go to the home page</a>
|
|
172
|
+
<a bfSlot="actions" bfButton variant="text" href="mailto:support@example.com">
|
|
173
|
+
Contact support
|
|
174
|
+
</a>
|
|
175
|
+
</bf-empty-state>
|
|
176
|
+
</div>
|
|
177
|
+
`,
|
|
178
|
+
})
|
|
179
|
+
export class EmptyStateNotFoundPageExampleComponent {
|
|
180
|
+
protected readonly fileQuestion = FileQuestionMark;
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
101
184
|
## Theming
|
|
102
185
|
|
|
103
186
|
- Icon tile: `surface-sunken` background, `text-muted` icon color, `tile` radius.
|
|
@@ -105,15 +188,15 @@ Set `headingLevel` to `3` when the empty state sits inside a section that alread
|
|
|
105
188
|
|
|
106
189
|
## Accessibility
|
|
107
190
|
|
|
108
|
-
The title renders as an `<h2>` by default
|
|
191
|
+
The title renders as an `<h2>` by default, an `<h3>` when `headingLevel` is `3` and an `<h1>` when it is `1` (only when the empty state is the page and the page has no other `h1`), so it slots into the surrounding page outline instead of always claiming the top level. The icon and action slots carry their own accessibility — an icon should stay decorative, and the action needs its own accessible name.
|
|
109
192
|
|
|
110
193
|
## Inputs
|
|
111
194
|
|
|
112
|
-
| Name | Type | Default | Description
|
|
113
|
-
| -------------- | ------------------------------------- | -------- |
|
|
114
|
-
| `title` | `string` | required | What is missing, as a short title.
|
|
115
|
-
| `description` | `string` | `''` | One sentence on how to fill it.
|
|
116
|
-
| `headingLevel` | `
|
|
195
|
+
| Name | Type | Default | Description |
|
|
196
|
+
| -------------- | ------------------------------------- | -------- | ------------------------------------------------------------------------------------------- |
|
|
197
|
+
| `title` | `string` | required | What is missing, as a short title. |
|
|
198
|
+
| `description` | `string` | `''` | One sentence on how to fill it. |
|
|
199
|
+
| `headingLevel` | `BfEmptyStateHeading` (`2 \| 3 \| 1`) | `2` | Heading level of the title so the empty state fits the page outline; 1 when it is the page. |
|
|
117
200
|
|
|
118
201
|
## Slots
|
|
119
202
|
|
|
@@ -129,10 +212,20 @@ The title renders as an `<h2>` by default and an `<h3>` when `headingLevel` is `
|
|
|
129
212
|
|
|
130
213
|
### Don't
|
|
131
214
|
|
|
215
|
+
- Don't set `headingLevel` to `1` on an empty state inside a page that already has its own `h1`.
|
|
132
216
|
- Don't use `bf-empty-state` for a loading screen; that is `bf-spinner`.
|
|
133
217
|
|
|
134
218
|
## Migration notes
|
|
135
219
|
|
|
220
|
+
### 0.3.2
|
|
221
|
+
|
|
222
|
+
`headingLevel` now takes the new named type `BfEmptyStateHeading` (`BfEmptyStateHeadingLevel |
|
|
223
|
+
1`): `1` renders the title as the page's `h1`, for an empty state that is the page (a not-found
|
|
224
|
+
or no-access page). The released `BfEmptyStateHeadingLevel` keeps its two members, so `Record`s
|
|
225
|
+
over it keep compiling. New "As the page" docs section with the `empty-state/page` example
|
|
226
|
+
(centred in a tall area, a primary action and a secondary link) and the code-only
|
|
227
|
+
`empty-state/not-found-page`.
|
|
228
|
+
|
|
136
229
|
### 0.3.0
|
|
137
230
|
|
|
138
231
|
New exported `BfEmptyStateHeadingLevel` (`2 | 3`), the type of `headingLevel` (was an inline union
|
package/ai/components/header.md
CHANGED
|
@@ -608,7 +608,7 @@ inside one.
|
|
|
608
608
|
|
|
609
609
|
## Desktop
|
|
610
610
|
|
|
611
|
-
`bf-header` is part of the Capacitor shell (`provideBfShell()`), shaped like an iOS or Material app bar. On a desktop web page use [bf-page-header](https://bitforge-ui.web.app/components/page-header): the page's one `h1` (`title`) with an optional `subtitle`, badges in `meta`, and the actions in `actions` (the main action as a primary [bf-button](https://bitforge-ui.web.app/components/button), secondary ones as `ghost` or `outline` buttons, the rest in an icon-only [bf-menu-button](https://bitforge-ui.web.app/components/menu-button) labelled "More actions"); the actions wrap under the title in a narrow window. On a nested page, put a [bf-breadcrumb](https://bitforge-ui.web.app/components/breadcrumb) in its `breadcrumb` slot, and a [bf-toolbar](https://bitforge-ui.web.app/components/toolbar) under it for search and filters. The two are siblings rather than one shell-aware widget: the large title and the scroll-under surface are phone patterns that depend on `bf-content`.
|
|
611
|
+
`bf-header` is part of the Capacitor shell (`provideBfShell()`), shaped like an iOS or Material app bar. On a desktop web page use [bf-page-header](https://bitforge-ui.web.app/components/page-header): the page's one `h1` (`title`) with an optional `subtitle`, badges in `meta`, and the actions in `actions` (the main action as a primary [bf-button](https://bitforge-ui.web.app/components/button), secondary ones as `ghost` or `outline` buttons, the rest in an icon-only [bf-menu-button](https://bitforge-ui.web.app/components/menu-button) labelled "More actions"); the actions wrap under the title in a narrow window. On a nested page, put a [bf-breadcrumb](https://bitforge-ui.web.app/components/breadcrumb) in its `breadcrumb` slot, and a [bf-toolbar](https://bitforge-ui.web.app/components/toolbar) under it for search and filters. Below the breakpoint, [bf-app-frame](https://bitforge-ui.web.app/components/app-frame) supplies the app-level header bar (brand, navigation menu, actions) above the page header. The two are siblings rather than one shell-aware widget: the large title and the scroll-under surface are phone patterns that depend on `bf-content`.
|
|
612
612
|
|
|
613
613
|
```ts
|
|
614
614
|
import { Component, signal } from '@angular/core';
|
|
@@ -748,6 +748,19 @@ The title is not in the tab order; after a push, `bf-stack-outlet` focuses it wi
|
|
|
748
748
|
|
|
749
749
|
## Migration notes
|
|
750
750
|
|
|
751
|
+
### 0.3.3
|
|
752
|
+
|
|
753
|
+
Fix: `bf-header` measures its title and slots in the next animation frame after a resize, not
|
|
754
|
+
inside the `ResizeObserver` callback. Its measurement writes the title inset and height, which
|
|
755
|
+
resize the observed elements, and WebKit then reported "ResizeObserver loop completed with
|
|
756
|
+
undelivered notifications": an error event that an app's global error listener
|
|
757
|
+
(`provideBrowserGlobalErrorListeners()`) logs as an error. Several resizes in one frame now
|
|
758
|
+
measure once.
|
|
759
|
+
|
|
760
|
+
### 0.3.2
|
|
761
|
+
|
|
762
|
+
The `## Desktop` docs say `bf-app-frame` supplies the app-level header bar below its breakpoint.
|
|
763
|
+
|
|
751
764
|
### 0.3.1
|
|
752
765
|
|
|
753
766
|
The `## Desktop` docs and When to use name the desktop replacement, `bf-page-header` (with
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# bf-sidebar-toggle
|
|
2
|
+
|
|
3
|
+
Icon button inside a sidebar that collapses it to an icon rail and back
|
|
4
|
+
|
|
5
|
+
`BfSidebarToggleComponent` from `@bitforgehq/ui/overlay` · Navigation · since 0.3.2
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, computed, signal } from '@angular/core';
|
|
11
|
+
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
12
|
+
import { BfNavItemComponent, BfSidebarComponent } from '@bitforgehq/ui';
|
|
13
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
14
|
+
import { BfSidebarToggleComponent } from '@bitforgehq/ui/overlay';
|
|
15
|
+
import { type IconNode, Layers, PanelLeftClose, PanelLeftOpen, PanelTop } from 'lucide';
|
|
16
|
+
|
|
17
|
+
interface NavLink {
|
|
18
|
+
readonly path: string;
|
|
19
|
+
readonly label: string;
|
|
20
|
+
readonly icon: IconNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The bf-sidebar-toggle overview.
|
|
25
|
+
*
|
|
26
|
+
* Live demo of `bf-sidebar-toggle` in the header of a `bf-sidebar`: a press collapses the
|
|
27
|
+
* sidebar to an icon rail and the next one expands it again. Its name and tooltip say what the
|
|
28
|
+
* next press does, and only the matching icon slot shows. The sidebar's `[(collapsed)]` keeps
|
|
29
|
+
* the state in a signal, and the result line reports it.
|
|
30
|
+
*/
|
|
31
|
+
@Component({
|
|
32
|
+
selector: 'app-example-sidebar-toggle-overview',
|
|
33
|
+
imports: [
|
|
34
|
+
RouterLink,
|
|
35
|
+
RouterLinkActive,
|
|
36
|
+
BfIconComponent,
|
|
37
|
+
BfNavItemComponent,
|
|
38
|
+
BfSidebarComponent,
|
|
39
|
+
BfSidebarToggleComponent,
|
|
40
|
+
],
|
|
41
|
+
template: `
|
|
42
|
+
<div class="border-border bg-bg rounded-card flex h-72 overflow-hidden border">
|
|
43
|
+
<bf-sidebar label="Toggle demo" [(collapsed)]="collapsed">
|
|
44
|
+
<div bfSlot="header" class="flex items-center justify-between gap-2">
|
|
45
|
+
@if (!collapsed()) {
|
|
46
|
+
<span class="text-heading text-text ps-3">Atlas</span>
|
|
47
|
+
}
|
|
48
|
+
<bf-sidebar-toggle>
|
|
49
|
+
<bf-icon bfSlot="collapse-icon" [icon]="panelClose" />
|
|
50
|
+
<bf-icon bfSlot="expand-icon" [icon]="panelOpen" />
|
|
51
|
+
</bf-sidebar-toggle>
|
|
52
|
+
</div>
|
|
53
|
+
@for (link of links; track link.path) {
|
|
54
|
+
<a bfNavItem [routerLink]="link.path" routerLinkActive ariaCurrentWhenActive="page">
|
|
55
|
+
<bf-icon bfSlot="icon" [icon]="link.icon" />
|
|
56
|
+
{{ link.label }}
|
|
57
|
+
</a>
|
|
58
|
+
}
|
|
59
|
+
</bf-sidebar>
|
|
60
|
+
<div class="min-w-0 flex-1 p-4">
|
|
61
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted wrap-anywhere">
|
|
62
|
+
{{ status() }}
|
|
63
|
+
</p>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
`,
|
|
67
|
+
})
|
|
68
|
+
export class SidebarToggleOverviewExampleComponent {
|
|
69
|
+
readonly collapsed = signal(false);
|
|
70
|
+
readonly status = computed(() =>
|
|
71
|
+
this.collapsed() ? 'Sidebar collapsed to an icon rail.' : 'Sidebar expanded.',
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
protected readonly links: readonly NavLink[] = [
|
|
75
|
+
{ path: '/components/sidebar-toggle', label: 'Sidebar toggle', icon: PanelTop },
|
|
76
|
+
{ path: '/components/sidebar', label: 'Sidebar', icon: Layers },
|
|
77
|
+
];
|
|
78
|
+
protected readonly panelOpen = PanelLeftOpen;
|
|
79
|
+
protected readonly panelClose = PanelLeftClose;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Overview
|
|
84
|
+
|
|
85
|
+
The collapse button of a `bf-sidebar`: a text icon button placed inside the sidebar (usually in
|
|
86
|
+
its header or footer) that flips the sidebar between full width and an icon rail. Its name and
|
|
87
|
+
tooltip say what the next press does ("Collapse sidebar", "Expand sidebar"), `aria-expanded`
|
|
88
|
+
follows the sidebar and `aria-controls` points at its `nav`. It shows the `collapse-icon` slot
|
|
89
|
+
while expanded and the `expand-icon` slot while collapsed. It reads and writes the sidebar's
|
|
90
|
+
`collapsed` model, so `[(collapsed)]` on the sidebar sees every press. Outside a `bf-sidebar`
|
|
91
|
+
(in a `bf-top-nav`, or on its own) it renders nothing.
|
|
92
|
+
|
|
93
|
+
The sidebar toggle collapses a [`bf-sidebar`](https://bitforge-ui.web.app/components/sidebar) to an icon rail and expands it again, from `@bitforgehq/ui/overlay` (it shows a `bfTooltip`, so it needs `@angular/cdk`; no Capacitor). Put it inside the sidebar, in its header or footer: it reads and writes the sidebar's `collapsed` model, so the sidebar's `[(collapsed)]` sees every press and the toggle needs no binding of its own. It is a text icon button whose name and tooltip say what the next press does. Import `BfSidebarToggleComponent` from `@bitforgehq/ui/overlay`.
|
|
94
|
+
|
|
95
|
+
## When to use
|
|
96
|
+
|
|
97
|
+
Use `bf-sidebar-toggle` for the collapse button of a desktop app's `bf-sidebar`. Bind your own [bf-icon-button](https://bitforge-ui.web.app/components/icon-button) to the sidebar's `[(collapsed)]` instead when the button must sit outside the sidebar, in a top bar. In the Capacitor shell there is no sidebar: use [bf-tabs](https://bitforge-ui.web.app/components/tabs) with a `bf-tab-bar` instead.
|
|
98
|
+
|
|
99
|
+
## Icons
|
|
100
|
+
|
|
101
|
+
The toggle never picks an icon. Project two, `bfSlot="collapse-icon"` (shown while the sidebar is expanded, such as Lucide's `PanelLeftClose`) and `bfSlot="expand-icon"` (shown while it is an icon rail, `PanelLeftOpen`); only the one that matches the state shows. Under a right-to-left layout pick the mirrored icons (`PanelRightClose`, `PanelRightOpen`), since the sidebar then sits at the right.
|
|
102
|
+
|
|
103
|
+
## Placement
|
|
104
|
+
|
|
105
|
+
The toggle renders only inside a `bf-sidebar`. In a `bf-top-nav`, which never collapses, or on its own it renders nothing and takes no space, so a misplaced toggle never shows a button that does nothing. In the header, put it after the brand and show the brand only while expanded; in the footer, put it beside the theme menu ([footer composition](https://bitforge-ui.web.app/components/sidebar#footer)). It keeps its place in the tab order either way, so a keyboard user finds it where they left it.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { Component, computed, signal } from '@angular/core';
|
|
109
|
+
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
110
|
+
import { type BfColorScheme, BfNavItemComponent, BfSidebarComponent } from '@bitforgehq/ui';
|
|
111
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
112
|
+
import { BfSidebarToggleComponent, BfThemeMenuButtonComponent } from '@bitforgehq/ui/overlay';
|
|
113
|
+
import {
|
|
114
|
+
Inbox,
|
|
115
|
+
type IconNode,
|
|
116
|
+
Moon,
|
|
117
|
+
PanelLeftClose,
|
|
118
|
+
PanelLeftOpen,
|
|
119
|
+
Settings,
|
|
120
|
+
Sun,
|
|
121
|
+
SunMoon,
|
|
122
|
+
Users,
|
|
123
|
+
} from 'lucide';
|
|
124
|
+
|
|
125
|
+
interface NavLink {
|
|
126
|
+
readonly path: string;
|
|
127
|
+
readonly label: string;
|
|
128
|
+
readonly icon: IconNode;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* A sidebar footer: settings, theme, toggle and version.
|
|
133
|
+
*
|
|
134
|
+
* The footer slot holds a Settings link (`a[bfNavItem]`), a row with an icon-only
|
|
135
|
+
* `bf-theme-menu-button` and the `bf-sidebar-toggle`, and a version line. Expanded, the two
|
|
136
|
+
* buttons sit at either end of the row; as an icon rail the row stacks vertically and the version
|
|
137
|
+
* line hides. The theme binds a local signal so the docs site keeps its theme; an app binds
|
|
138
|
+
* `BfColorSchemeService.scheme`.
|
|
139
|
+
*/
|
|
140
|
+
@Component({
|
|
141
|
+
selector: 'app-example-sidebar-footer',
|
|
142
|
+
imports: [
|
|
143
|
+
RouterLink,
|
|
144
|
+
RouterLinkActive,
|
|
145
|
+
BfIconComponent,
|
|
146
|
+
BfNavItemComponent,
|
|
147
|
+
BfSidebarComponent,
|
|
148
|
+
BfSidebarToggleComponent,
|
|
149
|
+
BfThemeMenuButtonComponent,
|
|
150
|
+
],
|
|
151
|
+
template: `
|
|
152
|
+
<div class="border-border bg-bg rounded-card flex h-[26rem] overflow-hidden border">
|
|
153
|
+
<bf-sidebar label="Footer demo" [(collapsed)]="collapsed">
|
|
154
|
+
@for (link of links; track link.path) {
|
|
155
|
+
<a bfNavItem [routerLink]="link.path" routerLinkActive ariaCurrentWhenActive="page">
|
|
156
|
+
<bf-icon bfSlot="icon" [icon]="link.icon" />
|
|
157
|
+
{{ link.label }}
|
|
158
|
+
</a>
|
|
159
|
+
}
|
|
160
|
+
<a
|
|
161
|
+
bfSlot="footer"
|
|
162
|
+
bfNavItem
|
|
163
|
+
routerLink="/theming"
|
|
164
|
+
routerLinkActive
|
|
165
|
+
ariaCurrentWhenActive="page"
|
|
166
|
+
>
|
|
167
|
+
<bf-icon bfSlot="icon" [icon]="settings" />
|
|
168
|
+
Settings
|
|
169
|
+
</a>
|
|
170
|
+
<div bfSlot="footer" [class]="rowClasses()">
|
|
171
|
+
<bf-theme-menu-button iconOnly variant="text" placement="above-start" [(value)]="scheme">
|
|
172
|
+
<bf-icon bfSlot="icon" [icon]="themeIcon()" />
|
|
173
|
+
</bf-theme-menu-button>
|
|
174
|
+
<bf-sidebar-toggle>
|
|
175
|
+
<bf-icon bfSlot="collapse-icon" [icon]="panelClose" />
|
|
176
|
+
<bf-icon bfSlot="expand-icon" [icon]="panelOpen" />
|
|
177
|
+
</bf-sidebar-toggle>
|
|
178
|
+
</div>
|
|
179
|
+
<p bfSlot="footer" class="text-caption text-text-muted ps-3" [class.hidden]="collapsed()">
|
|
180
|
+
Version 0.3.2
|
|
181
|
+
</p>
|
|
182
|
+
</bf-sidebar>
|
|
183
|
+
<div class="min-w-0 flex-1 p-4">
|
|
184
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted wrap-anywhere">
|
|
185
|
+
{{ collapsed() ? 'Icon rail' : 'Expanded' }}, theme {{ scheme() }}.
|
|
186
|
+
</p>
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
`,
|
|
190
|
+
})
|
|
191
|
+
export class SidebarFooterExampleComponent {
|
|
192
|
+
readonly collapsed = signal(false);
|
|
193
|
+
/** Stands in for `BfColorSchemeService.scheme`. */
|
|
194
|
+
readonly scheme = signal<BfColorScheme>('system');
|
|
195
|
+
|
|
196
|
+
/** Expanded: one row, the buttons at either end. Collapsed: a column in the rail. */
|
|
197
|
+
protected readonly rowClasses = computed(() =>
|
|
198
|
+
this.collapsed() ? 'flex flex-col items-center gap-1' : 'flex items-center justify-between',
|
|
199
|
+
);
|
|
200
|
+
protected readonly themeIcon = computed(
|
|
201
|
+
() => ({ system: SunMoon, light: Sun, dark: Moon })[this.scheme()],
|
|
202
|
+
);
|
|
203
|
+
protected readonly links: readonly NavLink[] = [
|
|
204
|
+
{ path: '/components/nav-item', label: 'Inbox', icon: Inbox },
|
|
205
|
+
{ path: '/components/nav-group', label: 'Team', icon: Users },
|
|
206
|
+
];
|
|
207
|
+
protected readonly settings = Settings;
|
|
208
|
+
protected readonly panelOpen = PanelLeftOpen;
|
|
209
|
+
protected readonly panelClose = PanelLeftClose;
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Remembering the state
|
|
214
|
+
|
|
215
|
+
The toggle holds no state. To open the app with the sidebar as the user left it, bind the sidebar to `BfSidebarStateService` from `@bitforgehq/ui` and add `provideBfSidebarState()` to the app's providers ([remember the collapsed sidebar](https://bitforge-ui.web.app/components/sidebar#remember-the-collapsed-sidebar)).
|
|
216
|
+
|
|
217
|
+
## Sizes
|
|
218
|
+
|
|
219
|
+
`size` is `sm` 32, `md` 40 or `lg` 48 px, square; unset it follows `provideBfDefaults()`'s `controlSize`, as `bf-icon-button` does.
|
|
220
|
+
|
|
221
|
+
## Labels
|
|
222
|
+
|
|
223
|
+
The name and tooltip read `sidebarCollapse` ("Collapse sidebar") while the sidebar is expanded and `sidebarExpand` ("Expand sidebar") while it is an icon rail. Translate them with `provideBfLabels()`.
|
|
224
|
+
|
|
225
|
+
## Testing
|
|
226
|
+
|
|
227
|
+
`BfSidebarToggleHarness` from `@bitforgehq/ui/testing` reads the name with `getLabel()`, the state with `isExpanded()` and presses it with `toggle()`; filter it with `label: 'Collapse sidebar'`.
|
|
228
|
+
|
|
229
|
+
## Desktop
|
|
230
|
+
|
|
231
|
+
The toggle looks and behaves like a text `bf-icon-button`: the muted text colour, a `surface-muted` fill and the pointer cursor on hover, a pressed tint while pressed, and the focus ring on keyboard focus only (`focus-visible`). Its tooltip shows after a 500 ms rest of a fine pointer and at once on keyboard focus: above the button while expanded, at the inline end of the rail while collapsed. Under a fine pointer the visible box is the hit area; under a coarse pointer (a touchscreen laptop) a `sm` or `md` toggle extends vertically to 44 px, and no tooltip shows.
|
|
232
|
+
|
|
233
|
+
## Theming
|
|
234
|
+
|
|
235
|
+
The toggle consumes `bf-icon-button`'s text variant: `text-muted` at rest, `text` and `surface-muted` on hover, `primary-soft-pressed` while pressed, and the `ring` token for focus. The tooltip consumes `bf-tooltip`'s tokens.
|
|
236
|
+
|
|
237
|
+
## Accessibility
|
|
238
|
+
|
|
239
|
+
- A native `<button type="button">` named by `aria-label`: "Collapse sidebar" while expanded, "Expand sidebar" while collapsed, so the name says what a press does. The tooltip shows the same text and describes the button while it shows.
|
|
240
|
+
- `aria-expanded` is `true` while the sidebar is at full width and `false` as an icon rail; `aria-controls` points at the sidebar's `nav`.
|
|
241
|
+
- The projected icons add nothing to the name (`bf-icon` is `aria-hidden`).
|
|
242
|
+
- Collapsing keeps focus on the toggle, and every nav item keeps its accessible name, so nothing a screen reader user relies on moves.
|
|
243
|
+
|
|
244
|
+
### Keyboard
|
|
245
|
+
|
|
246
|
+
| Key | Action |
|
|
247
|
+
| ----------------------------------- | ------------------------------------------------ |
|
|
248
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Collapses or expands the sidebar |
|
|
249
|
+
| <kbd>Escape</kbd> | Hides the tooltip while it shows |
|
|
250
|
+
| <kbd>Tab</kbd> | Moves on to the next control, hiding the tooltip |
|
|
251
|
+
|
|
252
|
+
## Inputs
|
|
253
|
+
|
|
254
|
+
| Name | Type | Default | Description |
|
|
255
|
+
| ------ | ----------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
256
|
+
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Height step: sm 32, md 40, lg 48 px (square); unset follows `provideBfDefaults()` (`controlSize`, `md` by default). |
|
|
257
|
+
|
|
258
|
+
## Slots
|
|
259
|
+
|
|
260
|
+
- `collapse-icon`: The icon shown while the sidebar is expanded (a panel closing).
|
|
261
|
+
- `expand-icon`: The icon shown while the sidebar is an icon rail (a panel opening).
|
|
262
|
+
|
|
263
|
+
## Notes
|
|
264
|
+
|
|
265
|
+
### Do
|
|
266
|
+
|
|
267
|
+
- Put the toggle inside the `bf-sidebar` it collapses, in its header or footer.
|
|
268
|
+
- Project both icon slots, a panel closing and a panel opening.
|
|
269
|
+
- Bind the sidebar to `BfSidebarStateService.collapsed` with `provideBfSidebarState()`, so the choice survives a reload.
|
|
270
|
+
|
|
271
|
+
### Don't
|
|
272
|
+
|
|
273
|
+
- Don't put it outside a `bf-sidebar`; it renders nothing there. Use a `bf-icon-button` bound to `[(collapsed)]` instead.
|
|
274
|
+
- Don't add your own `aria-label` or tooltip; the toggle names itself by what the next press does.
|
|
275
|
+
|
|
276
|
+
## Migration notes
|
|
277
|
+
|
|
278
|
+
### 0.3.2
|
|
279
|
+
|
|
280
|
+
New `bf-sidebar-toggle` in `@bitforgehq/ui/overlay`: a text icon button placed inside a
|
|
281
|
+
`bf-sidebar` (header or footer) that flips the sidebar between full width and an icon rail by
|
|
282
|
+
writing its `collapsed` model, so `[(collapsed)]` sees every press. Its name and `bfTooltip` say
|
|
283
|
+
what the next press does (new label keys `sidebarCollapse` "Collapse sidebar" and `sidebarExpand`
|
|
284
|
+
"Expand sidebar"); `aria-expanded` follows the sidebar and `aria-controls` points at its `nav`.
|
|
285
|
+
Icons are the `collapse-icon` and `expand-icon` slots, only the matching one shown; `size` as
|
|
286
|
+
`bf-icon-button`. Outside a sidebar (in a `bf-top-nav`, or alone) it renders nothing. A
|
|
287
|
+
`BfSidebarToggleHarness` in `@bitforgehq/ui/testing` (`getLabel()`, `isExpanded()`, `toggle()`).
|