@bitforgehq/ui 0.3.0 → 0.3.2
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 +598 -0
- package/README.md +18 -11
- package/ai/MANIFEST.index.json +306 -0
- package/ai/MANIFEST.json +8915 -3788
- package/ai/REFERENCE.index.json +476 -0
- package/ai/REFERENCE.json +5810 -2905
- package/ai/UI_GUIDE.md +212 -58
- package/ai/components/app-frame.md +328 -0
- package/ai/components/back-button.md +42 -2
- package/ai/components/badge.md +17 -7
- package/ai/components/bar-chart.md +188 -0
- package/ai/components/breadcrumb-item.md +121 -0
- package/ai/components/breadcrumb.md +277 -0
- package/ai/components/button.md +82 -15
- package/ai/components/calendar.md +300 -0
- package/ai/components/card.md +119 -4
- package/ai/components/chart-card.md +283 -0
- package/ai/components/combobox.md +411 -0
- package/ai/components/date-input.md +396 -0
- package/ai/components/datetime-input.md +215 -0
- package/ai/components/description-item.md +111 -0
- package/ai/components/description-list.md +160 -0
- package/ai/components/donut-chart.md +169 -0
- package/ai/components/empty-state.md +99 -6
- package/ai/components/header.md +71 -2
- package/ai/components/icon-button.md +74 -16
- package/ai/components/icon.md +63 -7
- package/ai/components/line-chart.md +234 -0
- package/ai/components/list.md +70 -6
- package/ai/components/menu.md +5 -0
- package/ai/components/multi-combobox.md +257 -0
- package/ai/components/nav-group.md +178 -0
- package/ai/components/nav-item.md +210 -0
- package/ai/components/page-header.md +237 -0
- package/ai/components/pagination.md +173 -0
- package/ai/components/popover.md +250 -0
- package/ai/components/side-panel.md +391 -0
- package/ai/components/sidebar-toggle.md +287 -0
- package/ai/components/sidebar.md +541 -0
- package/ai/components/skeleton.md +215 -0
- package/ai/components/sparkline.md +188 -0
- package/ai/components/stat.md +297 -0
- package/ai/components/tab-group.md +226 -0
- package/ai/components/tab-panel.md +163 -0
- package/ai/components/table.md +697 -0
- package/ai/components/tabs.md +46 -2
- package/ai/components/theme-menu-button.md +182 -0
- package/ai/components/theme-switch.md +170 -0
- package/ai/components/time-input.md +240 -0
- package/ai/components/toolbar.md +354 -0
- package/ai/components/tooltip.md +230 -0
- package/ai/components/top-nav.md +159 -0
- 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-badge-appearance.md +36 -0
- package/ai/reference/bf-badge-variant.md +1 -1
- package/ai/reference/bf-bar-chart-orientation.md +27 -0
- package/ai/reference/bf-button-appearance.md +36 -0
- package/ai/reference/bf-button-variant.md +1 -1
- package/ai/reference/bf-calendar-disabled-date.md +23 -0
- package/ai/reference/bf-calendar-harness.md +51 -0
- package/ai/reference/bf-calendar-weekday.md +31 -0
- package/ai/reference/bf-chart-card-heading-level.md +26 -0
- package/ai/reference/bf-chart-curve.md +28 -0
- package/ai/reference/bf-chart-harness.md +51 -0
- package/ai/reference/bf-chart-legend-position.md +30 -0
- package/ai/reference/bf-chart-series.md +23 -0
- package/ai/reference/bf-chart-value-format.md +23 -0
- package/ai/reference/bf-color-scheme-service.md +30 -0
- package/ai/reference/bf-color-scheme.md +31 -0
- package/ai/reference/bf-combobox-filter.md +27 -0
- package/ai/reference/bf-combobox-harness.md +89 -0
- package/ai/reference/bf-combobox-option.md +33 -0
- package/ai/reference/bf-compare-with.md +2 -0
- package/ai/reference/bf-date-input-harness.md +67 -0
- package/ai/reference/bf-date-input-picker.md +27 -0
- package/ai/reference/bf-date-time-input-harness.md +76 -0
- package/ai/reference/bf-date-time-input-picker.md +27 -0
- package/ai/reference/bf-description-list-layout.md +26 -0
- package/ai/reference/bf-donut-slice.md +22 -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 +73 -20
- package/ai/reference/bf-menu-button-variant.md +1 -0
- package/ai/reference/bf-menu-placement.md +1 -0
- package/ai/reference/bf-overlay-options.md +28 -27
- package/ai/reference/bf-overlay-placement.md +34 -0
- package/ai/reference/bf-overlay-position.md +1 -1
- package/ai/reference/bf-overlay-service.md +5 -3
- package/ai/reference/bf-page-header-heading-level.md +26 -0
- package/ai/reference/bf-pagination-harness.md +61 -0
- package/ai/reference/bf-platform-service.md +17 -15
- package/ai/reference/bf-popover-harness.md +42 -0
- package/ai/reference/bf-popover-placement.md +28 -0
- package/ai/reference/bf-popover-trigger-directive.md +56 -0
- package/ai/reference/bf-side-panel-config.md +35 -0
- package/ai/reference/bf-side-panel-harness.md +34 -0
- package/ai/reference/bf-side-panel-ref.md +44 -0
- package/ai/reference/bf-side-panel-service.md +43 -0
- package/ai/reference/bf-side-panel-template-context.md +32 -0
- package/ai/reference/bf-side-panel-width.md +28 -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 +13 -0
- package/ai/reference/bf-skeleton-shape.md +28 -0
- package/ai/reference/bf-stat-appearance.md +33 -0
- package/ai/reference/bf-stat-trend-intent.md +27 -0
- package/ai/reference/bf-stat-trend.md +27 -0
- package/ai/reference/bf-stat-variant.md +26 -0
- package/ai/reference/bf-tab-group-harness.md +44 -0
- package/ai/reference/bf-table-align.md +26 -0
- package/ai/reference/bf-table-cell-context.md +24 -0
- package/ai/reference/bf-table-cell-directive.md +22 -0
- package/ai/reference/bf-table-column.md +28 -0
- package/ai/reference/bf-table-harness.md +77 -0
- package/ai/reference/bf-table-row-key.md +21 -0
- package/ai/reference/bf-table-sort-direction.md +26 -0
- package/ai/reference/bf-table-sort.md +27 -0
- package/ai/reference/bf-table-stack.md +29 -0
- package/ai/reference/bf-theme.md +5 -4
- package/ai/reference/bf-time-input-harness.md +67 -0
- package/ai/reference/bf-time-input-hour-cycle.md +27 -0
- package/ai/reference/bf-time-input-picker.md +27 -0
- package/ai/reference/bf-tooltip-directive.md +43 -0
- package/ai/reference/bf-tooltip-harness.md +43 -0
- package/ai/reference/bf-tooltip-placement.md +28 -0
- package/ai/reference/bf_color_scheme_storage.md +20 -0
- package/ai/reference/bf_side_panel_data.md +17 -0
- package/ai/reference/bf_sidebar_state_storage.md +20 -0
- package/ai/reference/bf_theme_storage.md +3 -1
- package/ai/reference/provide-bf-color-scheme.md +20 -0
- package/ai/reference/provide-bf-labels.md +13 -4
- package/ai/reference/provide-bf-sidebar-state.md +20 -0
- package/ai/reference/slide-in-from-end.md +24 -0
- package/ai/reference/slide-out-to-end.md +24 -0
- package/bin/bf-ui.js +433 -3
- package/fesm2022/bitforgehq-ui-charts.mjs +3045 -0
- package/fesm2022/bitforgehq-ui-charts.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-icons.mjs +107 -3
- package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-mobile.mjs +14 -59
- package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-overlay.mjs +6566 -38
- package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-testing.mjs +634 -1
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui.mjs +5708 -137
- package/fesm2022/bitforgehq-ui.mjs.map +1 -1
- package/llms-full.txt +26766 -14470
- package/llms.txt +106 -1
- package/package.json +7 -3
- package/theme.css +101 -0
- package/types/bitforgehq-ui-charts.d.ts +967 -0
- package/types/bitforgehq-ui-icons.d.ts +13 -1
- package/types/bitforgehq-ui-mobile.d.ts +25 -18
- package/types/bitforgehq-ui-overlay.d.ts +1895 -7
- package/types/bitforgehq-ui-testing.d.ts +782 -1
- package/types/bitforgehq-ui.d.ts +2393 -45
package/ai/components/tabs.md
CHANGED
|
@@ -79,7 +79,7 @@ else the current tab route), the bar hides while the soft keyboard is open, and
|
|
|
79
79
|
|
|
80
80
|
## When to use
|
|
81
81
|
|
|
82
|
-
Use `bf-tabs` for the tab root of a Capacitor app: one navigation stack per tab behind a floating [bf-tab-bar](https://bitforge-ui.web.app/components/tab-bar). Use [bf-stack-outlet](https://bitforge-ui.web.app/components/stack-outlet) instead for an app with one stack and no tabs, and a sidebar of links
|
|
82
|
+
Use `bf-tabs` for the tab root of a Capacitor app: one navigation stack per tab behind a floating [bf-tab-bar](https://bitforge-ui.web.app/components/tab-bar). Use [bf-stack-outlet](https://bitforge-ui.web.app/components/stack-outlet) instead for an app with one stack and no tabs, and on desktop a [bf-sidebar](https://bitforge-ui.web.app/components/sidebar) or a [bf-top-nav](https://bitforge-ui.web.app/components/top-nav) of `a[bfNavItem]` links over a plain `router-outlet`.
|
|
83
83
|
|
|
84
84
|
## Basic usage
|
|
85
85
|
|
|
@@ -194,7 +194,41 @@ fires no keyboard event).
|
|
|
194
194
|
|
|
195
195
|
## Desktop
|
|
196
196
|
|
|
197
|
-
`bf-tabs` is part of the Capacitor shell (`provideBfShell()`). On a desktop web app put the top-level destinations in a
|
|
197
|
+
`bf-tabs` is part of the Capacitor shell (`provideBfShell()`). On a desktop web app, put the top-level destinations in a [bf-top-nav](https://bitforge-ui.web.app/components/top-nav) (a few sections) or a [bf-sidebar](https://bitforge-ui.web.app/components/sidebar) (more, in groups, collapsible to an icon rail) of `a[bfNavItem]` links, each with `routerLink`, `routerLinkActive` and `ariaCurrentWhenActive="page"`, above a plain `router-outlet`. The router sets `aria-current="page"` on the link of the current route and the item shows it. Drive both from one array of destinations: inside the shell the same array feeds the `bf-tab`s of a [bf-tab-bar](https://bitforge-ui.web.app/components/tab-bar), and below a breakpoint a [bf-menu-button](https://bitforge-ui.web.app/components/menu-button) of `a[bfMenuItem]` links (see bf-sidebar, Responsive). Wrap the desktop app in a [bf-app-frame](https://bitforge-ui.web.app/components/app-frame): it holds the sidebar from `md` up and, below it, a header bar with that menu button. Inside the shell under a mouse, the tab links take the `bf-tab` hover and focus styles.
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { Component } from '@angular/core';
|
|
201
|
+
import { RouterLink, RouterLinkActive } from '@angular/router';
|
|
202
|
+
import { BfNavItemComponent, BfTopNavComponent } from '@bitforgehq/ui';
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The desktop replacement for tabs: a bf-top-nav.
|
|
206
|
+
*
|
|
207
|
+
* `a[bfNavItem]` links with `routerLinkActive` and `ariaCurrentWhenActive="page"`: the router marks
|
|
208
|
+
* the current page and the item shows it; a plain `router-outlet` below renders the page. With
|
|
209
|
+
* more sections, the same links go in a `bf-sidebar`.
|
|
210
|
+
*/
|
|
211
|
+
@Component({
|
|
212
|
+
selector: 'app-example-tabs-desktop-nav',
|
|
213
|
+
imports: [RouterLink, RouterLinkActive, BfNavItemComponent, BfTopNavComponent],
|
|
214
|
+
template: `
|
|
215
|
+
<bf-top-nav label="Navigation components">
|
|
216
|
+
@for (link of links; track link.path) {
|
|
217
|
+
<a bfNavItem [routerLink]="link.path" routerLinkActive ariaCurrentWhenActive="page">{{
|
|
218
|
+
link.label
|
|
219
|
+
}}</a>
|
|
220
|
+
}
|
|
221
|
+
</bf-top-nav>
|
|
222
|
+
`,
|
|
223
|
+
})
|
|
224
|
+
export class TabsDesktopNavExampleComponent {
|
|
225
|
+
protected readonly links: readonly { path: string; label: string }[] = [
|
|
226
|
+
{ path: '/components/tabs', label: 'Tabs' },
|
|
227
|
+
{ path: '/components/tab-bar', label: 'Tab bar' },
|
|
228
|
+
{ path: '/components/stack-outlet', label: 'Stack outlet' },
|
|
229
|
+
];
|
|
230
|
+
}
|
|
231
|
+
```
|
|
198
232
|
|
|
199
233
|
## Theming
|
|
200
234
|
|
|
@@ -331,6 +365,16 @@ content to the top.
|
|
|
331
365
|
|
|
332
366
|
## Migration notes
|
|
333
367
|
|
|
368
|
+
### 0.3.2
|
|
369
|
+
|
|
370
|
+
The `## Desktop` docs point at `bf-app-frame` for the frame around the desktop navigation.
|
|
371
|
+
|
|
372
|
+
### 0.3.1
|
|
373
|
+
|
|
374
|
+
The `## Desktop` docs name the desktop replacement: a `bf-top-nav` or `bf-sidebar` of
|
|
375
|
+
`a[bfNavItem]` links with `routerLinkActive` and `ariaCurrentWhenActive="page"`, driven by the
|
|
376
|
+
same destinations array as the shell's `bf-tab-bar`, with an example. No code change.
|
|
377
|
+
|
|
334
378
|
### 0.3.0
|
|
335
379
|
|
|
336
380
|
**Breaking:** (ARIA contract) `bf-tabs` provides `BF_TAB_LINKS`, so the `bf-tab-bar` projected
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# bf-theme-menu-button
|
|
2
|
+
|
|
3
|
+
Menu button with System, Light and Dark radio items bound to the colour scheme
|
|
4
|
+
|
|
5
|
+
`BfThemeMenuButtonComponent` from `@bitforgehq/ui/overlay` · Overlays · since 0.3.1
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, computed, signal } from '@angular/core';
|
|
11
|
+
import { type BfColorScheme } from '@bitforgehq/ui';
|
|
12
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
13
|
+
import { BfThemeMenuButtonComponent } from '@bitforgehq/ui/overlay';
|
|
14
|
+
import { Moon, Sun, SunMoon } from 'lucide';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The bf-theme-menu-button overview.
|
|
18
|
+
*
|
|
19
|
+
* Live demo of `bf-theme-menu-button`: an outline "Theme" button and an icon-only text button
|
|
20
|
+
* share one choice; the icon follows it (sun, moon, or both for System) and the status line
|
|
21
|
+
* reports it. The demo binds a local signal, so the docs site keeps its own theme; an app binds
|
|
22
|
+
* `BfColorSchemeService.scheme` instead.
|
|
23
|
+
*/
|
|
24
|
+
@Component({
|
|
25
|
+
selector: 'app-example-theme-menu-button-overview',
|
|
26
|
+
imports: [BfIconComponent, BfThemeMenuButtonComponent],
|
|
27
|
+
template: `
|
|
28
|
+
<div class="flex flex-col gap-4">
|
|
29
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
30
|
+
<bf-theme-menu-button placement="below-start" [(value)]="scheme">
|
|
31
|
+
<bf-icon bfSlot="icon" [icon]="icon()" />
|
|
32
|
+
</bf-theme-menu-button>
|
|
33
|
+
<bf-theme-menu-button iconOnly variant="text" [(value)]="scheme">
|
|
34
|
+
<bf-icon bfSlot="icon" [icon]="icon()" />
|
|
35
|
+
</bf-theme-menu-button>
|
|
36
|
+
</div>
|
|
37
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">
|
|
38
|
+
Theme: {{ scheme() }}
|
|
39
|
+
</p>
|
|
40
|
+
</div>
|
|
41
|
+
`,
|
|
42
|
+
})
|
|
43
|
+
export class ThemeMenuButtonOverviewExampleComponent {
|
|
44
|
+
/** Stands in for `BfColorSchemeService.scheme`. */
|
|
45
|
+
readonly scheme = signal<BfColorScheme>('system');
|
|
46
|
+
/** Sun for light, moon for dark, both while the choice follows the system. */
|
|
47
|
+
readonly icon = computed(() => ({ system: SunMoon, light: Sun, dark: Moon })[this.scheme()]);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Overview
|
|
52
|
+
|
|
53
|
+
The colour scheme switch as a menu button, for a crowded top bar: a `bf-menu-button` whose menu
|
|
54
|
+
holds System, Light and Dark as radio items, the current one marked with a dot. It holds a
|
|
55
|
+
`BfColorScheme` and injects no service, so the app binds its own state, usually
|
|
56
|
+
`BfColorSchemeService.scheme` (`BfPlatformService.theme` in a Capacitor app). The button shows
|
|
57
|
+
`label` ("Theme") next to an optional icon, or only the icon with `iconOnly`.
|
|
58
|
+
|
|
59
|
+
A theme menu button lets the user pick the app's colour scheme from a menu, from `@bitforgehq/ui/overlay` (needs `@angular/cdk`, no Capacitor). It is a `bf-menu-button` whose `bf-menu` holds System, Light and Dark as radio items, the current one marked with a dot. It holds a `BfColorScheme` and injects no service: the app binds its own state, normally `BfColorSchemeService.scheme`, with `[(value)]`. It works in both shells. Import `BfThemeMenuButtonComponent` from `@bitforgehq/ui/overlay`.
|
|
60
|
+
|
|
61
|
+
## When to use
|
|
62
|
+
|
|
63
|
+
Use `bf-theme-menu-button` in a crowded top bar or toolbar, where one button fits and three segments do not. Use [bf-theme-switch](https://bitforge-ui.web.app/components/theme-switch) instead on a settings page or in a header row with room, where the choice should stay visible. For a menu of other actions use [bf-menu-button](https://bitforge-ui.web.app/components/menu-button).
|
|
64
|
+
|
|
65
|
+
## Icon only in a top bar
|
|
66
|
+
|
|
67
|
+
`iconOnly` renders a round button holding only the icon, like `bf-icon-button`; `label` (else the `theme` label, "Theme") is then its accessible name. Project the icon in the `icon` slot: the component never picks one. Show the current look, a sun or a moon from `BfColorSchemeService.dark()`, so under System the icon says what the OS chose. Bind `[(value)]` to `BfColorSchemeService.scheme`: on desktop with `provideBfColorScheme()` in the app's providers, in a Capacitor app through `provideBfShell()` (where `BfPlatformService.theme` is the same signal). `changed` emits the new choice after a user pick; picking the current item again changes nothing.
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import { Component, computed, inject } from '@angular/core';
|
|
71
|
+
import { BfColorSchemeService } from '@bitforgehq/ui';
|
|
72
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
73
|
+
import { BfThemeMenuButtonComponent } from '@bitforgehq/ui/overlay';
|
|
74
|
+
import { Moon, Sun } from 'lucide';
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* An icon-only theme menu in a top bar, bound to the app's colour scheme.
|
|
78
|
+
*
|
|
79
|
+
* `[(value)]` binds `BfColorSchemeService.scheme`: with `provideBfColorScheme()` in the app's
|
|
80
|
+
* providers (desktop) or `provideBfShell()` (Capacitor), a pick sets the `dark` class on `<html>`
|
|
81
|
+
* and is stored. The icon follows `dark()`, so under System it shows what the OS chose.
|
|
82
|
+
*
|
|
83
|
+
* @preview none
|
|
84
|
+
*/
|
|
85
|
+
@Component({
|
|
86
|
+
selector: 'app-example-theme-menu-button-top-bar',
|
|
87
|
+
imports: [BfIconComponent, BfThemeMenuButtonComponent],
|
|
88
|
+
template: `
|
|
89
|
+
<header class="border-border flex items-center gap-2 border-b px-4 py-2">
|
|
90
|
+
<span class="text-heading me-auto">Grove</span>
|
|
91
|
+
<bf-theme-menu-button iconOnly variant="text" [(value)]="colorScheme.scheme">
|
|
92
|
+
<bf-icon bfSlot="icon" [icon]="icon()" />
|
|
93
|
+
</bf-theme-menu-button>
|
|
94
|
+
</header>
|
|
95
|
+
`,
|
|
96
|
+
})
|
|
97
|
+
export class ThemeMenuButtonTopBarExampleComponent {
|
|
98
|
+
// app.config.ts: providers: [provideBfColorScheme()]
|
|
99
|
+
protected readonly colorScheme = inject(BfColorSchemeService);
|
|
100
|
+
protected readonly icon = computed(() => (this.colorScheme.dark() ? Moon : Sun));
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Label and variants
|
|
105
|
+
|
|
106
|
+
Without `iconOnly` the button shows `label` (else "Theme") after the optional icon, with the menu-button caret. It shows the name, not the current choice: the choice is the checked item in the menu (and the icon, when you project one), and a fixed text keeps the button's width and accessible name steady. `variant` is `outline` (default), `text` or `tonal`; `size` is `sm` 32, `md` 40 or `lg` 48 px, unset following `provideBfDefaults()`'s `controlSize`. `placement` defaults to `'below-end'`, since the button usually sits at the end of a bar.
|
|
107
|
+
|
|
108
|
+
## Labels
|
|
109
|
+
|
|
110
|
+
The items read `themeSystem`, `themeLight` and `themeDark`; the button reads `theme`. Translate them with `provideBfLabels()`.
|
|
111
|
+
|
|
112
|
+
## Testing
|
|
113
|
+
|
|
114
|
+
The component renders a `bf-menu-button`, so `BfMenuButtonHarness` from `@bitforgehq/ui/testing` drives it: filter it by `label: 'Theme'`, open it with `getMenu()`, then pick with `clickItem('Dark')`; `isChecked()` on a `BfMenuItemHarness` reads the dot.
|
|
115
|
+
|
|
116
|
+
## Desktop
|
|
117
|
+
|
|
118
|
+
The button hovers, presses and shows the focus ring like `bf-menu-button` (and `bf-icon-button` with `iconOnly`): a darker fill or text on hover with the pointer cursor, the ring on keyboard focus only. A click opens the menu; menu rows take the muted fill on hover. Under a fine pointer the visible box is the hit area. In a dense top bar set `size="sm"`, or `provideBfDefaults({ size: 'sm' })` in a component's `providers` ([density guide](https://bitforge-ui.web.app/density)). Add `provideBfColorScheme()` to the desktop app's providers; the Capacitor shell needs nothing more than `provideBfShell()`.
|
|
119
|
+
|
|
120
|
+
## Theming
|
|
121
|
+
|
|
122
|
+
The button consumes `bf-menu-button`'s tokens (`bf-button`'s, or `bf-icon-button`'s with `iconOnly`); the menu consumes `bf-menu`'s: `surface` with a `border` and `shadow-floating`, rows `text` with a `surface-muted` hover and focus, and the radio dot in the row's text colour.
|
|
123
|
+
|
|
124
|
+
## Accessibility
|
|
125
|
+
|
|
126
|
+
- The button is a native `<button type="button">` with `aria-haspopup="menu"` and `aria-expanded`; its name is the visible label, or `label` (`aria-label`) with `iconOnly`. The menu it opens is named by it.
|
|
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
|
+
- Choosing an item closes the menu and returns focus to the button.
|
|
129
|
+
- Hit area: at least 32 px, 44 px under a coarse pointer, as `bf-menu-button`.
|
|
130
|
+
|
|
131
|
+
### Keyboard
|
|
132
|
+
|
|
133
|
+
The keys inside the open menu are on the [bf-menu](https://bitforge-ui.web.app/components/menu#keyboard) page.
|
|
134
|
+
|
|
135
|
+
| Key | Action |
|
|
136
|
+
| ---------------------------------------------------------- | -------------------------------------------------------------- |
|
|
137
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> / <kbd>ArrowDown</kbd> | Opens the menu and focuses System |
|
|
138
|
+
| <kbd>ArrowUp</kbd> | Opens the menu and focuses Dark |
|
|
139
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | In the open menu: picks the focused choice, closes the menu |
|
|
140
|
+
| <kbd>Escape</kbd> | In the open menu: closes it without a change and returns focus |
|
|
141
|
+
|
|
142
|
+
## Inputs
|
|
143
|
+
|
|
144
|
+
| Name | Type | Default | Description |
|
|
145
|
+
| ----------- | ---------------------------------------------------------------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
146
|
+
| `label` | `string` | `''` | The button's text, and its accessible name with `iconOnly`; empty falls back to the `theme` label. |
|
|
147
|
+
| `iconOnly` | `boolean` | `false` | Square button holding only the projected icon, like `bf-icon-button`. |
|
|
148
|
+
| `variant` | `BfMenuButtonVariant` (`'outline' \| 'text' \| 'tonal'`) | `'outline'` | Visual style: outline (default), text, tonal. |
|
|
149
|
+
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Height step: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()` (`controlSize`, `md` by default). |
|
|
150
|
+
| `placement` | `BfMenuPlacement` (`'below-start' \| 'below-end' \| 'above-start' \| 'above-end'`) | `'below-end'` | Where the menu opens against the button; it flips when it would overflow. |
|
|
151
|
+
| `value` | `BfColorScheme` (`'system' \| 'light' \| 'dark'`) | `'system'` | The colour scheme choice; bind it two-way, usually to `BfColorSchemeService.scheme`. Two-way: `[(value)]`. |
|
|
152
|
+
|
|
153
|
+
## Outputs
|
|
154
|
+
|
|
155
|
+
| Name | Payload | Description |
|
|
156
|
+
| --------- | ------------------------------------------------- | -------------------------------------------------------------------- |
|
|
157
|
+
| `changed` | `BfColorScheme` (`'system' \| 'light' \| 'dark'`) | Emitted with the new choice right after the user picks another item. |
|
|
158
|
+
|
|
159
|
+
## Slots
|
|
160
|
+
|
|
161
|
+
- `icon`: The icon before the label, or the whole button with `iconOnly` (a sun or a moon).
|
|
162
|
+
|
|
163
|
+
## Notes
|
|
164
|
+
|
|
165
|
+
### Do
|
|
166
|
+
|
|
167
|
+
- Project a sun or moon icon that follows `BfColorSchemeService.dark()`.
|
|
168
|
+
- Bind `[(value)]` to `BfColorSchemeService.scheme` (or `BfPlatformService.theme` in the shell).
|
|
169
|
+
|
|
170
|
+
### Don't
|
|
171
|
+
|
|
172
|
+
- Don't use it where there is room for three segments; use `bf-theme-switch`, which shows the choice without a click.
|
|
173
|
+
- Don't import it into a root-entry widget; it lives in `@bitforgehq/ui/overlay` because it opens a `bf-menu`.
|
|
174
|
+
|
|
175
|
+
## Migration notes
|
|
176
|
+
|
|
177
|
+
### 0.3.1
|
|
178
|
+
|
|
179
|
+
New `bf-theme-menu-button` in `@bitforgehq/ui/overlay`: a `bf-menu-button` whose menu holds
|
|
180
|
+
System, Light and Dark as radio items, for a crowded top bar. Same `value` / `changed` / `label`
|
|
181
|
+
as `bf-theme-switch`, plus `iconOnly`, `variant`, `size`, `placement` (default `'below-end'`) and
|
|
182
|
+
an `icon` slot for the consumer's sun or moon.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# bf-theme-switch
|
|
2
|
+
|
|
3
|
+
Segmented System, Light and Dark switch bound two-way to the app colour scheme
|
|
4
|
+
|
|
5
|
+
`BfThemeSwitchComponent` from `@bitforgehq/ui` · Forms · since 0.3.1
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, signal } from '@angular/core';
|
|
11
|
+
import { BfThemeSwitchComponent, type BfColorScheme } from '@bitforgehq/ui';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The bf-theme-switch overview.
|
|
15
|
+
*
|
|
16
|
+
* Live demo of `bf-theme-switch`: the default switch and a `sm` one share one choice, and the
|
|
17
|
+
* status line reports it. The demo binds a local signal, so the docs site keeps its own theme; an
|
|
18
|
+
* app binds `BfColorSchemeService.scheme` instead.
|
|
19
|
+
*/
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'app-example-theme-switch-overview',
|
|
22
|
+
imports: [BfThemeSwitchComponent],
|
|
23
|
+
template: `
|
|
24
|
+
<div class="flex flex-col items-start gap-3">
|
|
25
|
+
<bf-theme-switch [(value)]="scheme" />
|
|
26
|
+
<bf-theme-switch size="sm" label="Theme (small)" [(value)]="scheme" />
|
|
27
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">
|
|
28
|
+
Theme: {{ scheme() }}
|
|
29
|
+
</p>
|
|
30
|
+
</div>
|
|
31
|
+
`,
|
|
32
|
+
})
|
|
33
|
+
export class ThemeSwitchOverviewExampleComponent {
|
|
34
|
+
/** Stands in for `BfColorSchemeService.scheme`. */
|
|
35
|
+
readonly scheme = signal<BfColorScheme>('system');
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Overview
|
|
40
|
+
|
|
41
|
+
The colour scheme switch: System, Light and Dark as a three-option segmented control. It holds
|
|
42
|
+
a `BfColorScheme` and injects no service, so the app binds its own state, usually
|
|
43
|
+
`BfColorSchemeService.scheme` (`provideBfColorScheme()` on desktop, `provideBfShell()` in a
|
|
44
|
+
Capacitor app, where `BfPlatformService.theme` is the same signal).
|
|
45
|
+
|
|
46
|
+
A theme switch lets the user pick the app's colour scheme: System, Light or Dark, as a three-option `bf-segmented`. It holds a `BfColorScheme` and injects no service: the app binds its own state, normally `BfColorSchemeService.scheme`, with `[(value)]`. It works in both shells.
|
|
47
|
+
|
|
48
|
+
## When to use
|
|
49
|
+
|
|
50
|
+
Use `bf-theme-switch` on a settings page, or in a header row with room for three segments, so the choice stays visible. Use [bf-theme-menu-button](https://bitforge-ui.web.app/components/theme-menu-button) instead in a crowded top bar, where only one button fits. For a general view switch use [bf-segmented](https://bitforge-ui.web.app/components/segmented) itself.
|
|
51
|
+
|
|
52
|
+
## Bind the colour scheme
|
|
53
|
+
|
|
54
|
+
Bind `[(value)]` to `BfColorSchemeService.scheme` (from `@bitforgehq/ui`). On desktop, `provideBfColorScheme()` in the app's providers makes the service set the `dark` class on `<html>`, follow `prefers-color-scheme` under System and store the choice under `bf-theme`. In a Capacitor app `provideBfShell()` already does this, and `BfPlatformService.theme` (`@bitforgehq/ui/mobile`) is the same signal, so `[(value)]="platform.theme"` works too. `changed` emits the new choice after a user pick, never for a programmatic write.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { Component, inject } from '@angular/core';
|
|
58
|
+
import { BfColorSchemeService, BfThemeSwitchComponent } from '@bitforgehq/ui';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* A theme switch bound to the app's colour scheme.
|
|
62
|
+
*
|
|
63
|
+
* `[(value)]` binds `BfColorSchemeService.scheme` itself: with `provideBfColorScheme()` in the
|
|
64
|
+
* app's providers (desktop) or `provideBfShell()` (Capacitor), a pick sets the `dark` class on
|
|
65
|
+
* `<html>` and is stored for the next visit.
|
|
66
|
+
*
|
|
67
|
+
* @preview none
|
|
68
|
+
*/
|
|
69
|
+
@Component({
|
|
70
|
+
selector: 'app-example-theme-switch-color-scheme',
|
|
71
|
+
imports: [BfThemeSwitchComponent],
|
|
72
|
+
template: `<bf-theme-switch [(value)]="colorScheme.scheme" />`,
|
|
73
|
+
})
|
|
74
|
+
export class ThemeSwitchColorSchemeExampleComponent {
|
|
75
|
+
// app.config.ts: providers: [provideBfColorScheme()]
|
|
76
|
+
protected readonly colorScheme = inject(BfColorSchemeService);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Full width
|
|
81
|
+
|
|
82
|
+
`fullWidth` stretches the switch and shares the width between the segments, as on a phone settings screen. `size` sets the height step (`sm` 32, `md` 40, `lg` 48 px; unset follows `provideBfDefaults()`'s `controlSize`).
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import { Component, signal } from '@angular/core';
|
|
86
|
+
import { BfThemeSwitchComponent, type BfColorScheme } from '@bitforgehq/ui';
|
|
87
|
+
|
|
88
|
+
/** A full-width theme switch under a visible caption, as on a phone settings screen. */
|
|
89
|
+
@Component({
|
|
90
|
+
selector: 'app-example-theme-switch-full-width',
|
|
91
|
+
imports: [BfThemeSwitchComponent],
|
|
92
|
+
template: `
|
|
93
|
+
<div class="flex max-w-sm flex-col gap-2">
|
|
94
|
+
<!-- The group's own name repeats the caption, so the caption is hidden from readers. -->
|
|
95
|
+
<p aria-hidden="true" class="text-caption text-text-muted">Appearance</p>
|
|
96
|
+
<bf-theme-switch fullWidth label="Appearance" [(value)]="scheme" />
|
|
97
|
+
</div>
|
|
98
|
+
`,
|
|
99
|
+
})
|
|
100
|
+
export class ThemeSwitchFullWidthExampleComponent {
|
|
101
|
+
protected readonly scheme = signal<BfColorScheme>('light');
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Labels
|
|
106
|
+
|
|
107
|
+
The segments read `themeSystem`, `themeLight` and `themeDark`, and the group's accessible name is `label`, else `theme` ("Theme"); translate them with `provideBfLabels()`. The name is not shown: put a visible caption beside it when the page needs one.
|
|
108
|
+
|
|
109
|
+
## Testing
|
|
110
|
+
|
|
111
|
+
The switch renders a `bf-segmented`, so `BfSegmentedHarness` from `@bitforgehq/ui/testing` drives it: filter it by `label: 'Theme'`, pick with `select('Dark')` and read the choice with `getSelectedLabel()`.
|
|
112
|
+
|
|
113
|
+
## Desktop
|
|
114
|
+
|
|
115
|
+
The segments hover, press and show the focus ring like `bf-segmented`'s: a darker label on hover with the pointer cursor, the ring on keyboard focus only. Under a fine pointer each segment is its own hit area. In a dense header row set `size="sm"`, or `provideBfDefaults({ size: 'sm' })` in a component's `providers` ([density guide](https://bitforge-ui.web.app/density)). Add `provideBfColorScheme()` to the desktop app's providers; the Capacitor shell needs nothing more than `provideBfShell()`.
|
|
116
|
+
|
|
117
|
+
## Theming
|
|
118
|
+
|
|
119
|
+
The switch is a `bf-segmented` and consumes its tokens: track `surface-sunken`, active segment `surface` / `text` with `shadow-segment` and a 1.5 px inset `primary-strong` outline, inactive `text-muted`, focus `ring`, shape `rounded-control`.
|
|
120
|
+
|
|
121
|
+
## Accessibility
|
|
122
|
+
|
|
123
|
+
A `bf-segmented`: `role="radiogroup"` named by `label` (else the `theme` label), each choice a `<button role="radio">` with `aria-checked`, one tab stop on the selected segment. A visible caption next to it should repeat the name or carry `aria-hidden="true"`, so readers do not hear it twice. Under a coarse pointer each segment's hit area reaches 44 px.
|
|
124
|
+
|
|
125
|
+
### Keyboard
|
|
126
|
+
|
|
127
|
+
| Key | Action |
|
|
128
|
+
| ------------------------------------------------ | -------------------------------------------------------- |
|
|
129
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves focus into and out of the switch (one tab stop) |
|
|
130
|
+
| <kbd>ArrowRight</kbd> / <kbd>ArrowDown</kbd> | Selects and focuses the next choice, wrapping at the end |
|
|
131
|
+
| <kbd>ArrowLeft</kbd> / <kbd>ArrowUp</kbd> | Selects and focuses the previous choice, wrapping |
|
|
132
|
+
| <kbd>Home</kbd> / <kbd>End</kbd> | Selects System / Dark |
|
|
133
|
+
| <kbd>Space</kbd> / <kbd>Enter</kbd> | Selects the focused choice |
|
|
134
|
+
|
|
135
|
+
## Inputs
|
|
136
|
+
|
|
137
|
+
| Name | Type | Default | Description |
|
|
138
|
+
| ----------- | ----------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
139
|
+
| `label` | `string` | `''` | Accessible name of the switch (not shown); empty falls back to the `theme` label. |
|
|
140
|
+
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Height step: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()` (`controlSize`, `md` by default). |
|
|
141
|
+
| `fullWidth` | `boolean` | `false` | Stretch the switch and share the width between the three segments. |
|
|
142
|
+
| `value` | `BfColorScheme` (`'system' \| 'light' \| 'dark'`) | `'system'` | The colour scheme choice; bind it two-way, usually to `BfColorSchemeService.scheme`, or with `[formField]`. Two-way: `[(value)]`. |
|
|
143
|
+
|
|
144
|
+
## Outputs
|
|
145
|
+
|
|
146
|
+
| Name | Payload | Description |
|
|
147
|
+
| --------- | ------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
148
|
+
| `changed` | `BfColorScheme` (`'system' \| 'light' \| 'dark'`) | Emitted with the new choice right after the user picks another segment. |
|
|
149
|
+
|
|
150
|
+
## Notes
|
|
151
|
+
|
|
152
|
+
### Do
|
|
153
|
+
|
|
154
|
+
- Bind `[(value)]` to `BfColorSchemeService.scheme` (or `BfPlatformService.theme` in the shell), so the switch and the page agree.
|
|
155
|
+
- Keep System as an option: it follows the OS until the user decides.
|
|
156
|
+
|
|
157
|
+
### Don't
|
|
158
|
+
|
|
159
|
+
- Don't call `scheme.set()` from `(changed)` as well as binding `[(value)]`; the two-way binding already writes it.
|
|
160
|
+
- Don't squeeze the switch into a crowded top bar; use `bf-theme-menu-button` there.
|
|
161
|
+
|
|
162
|
+
## Migration notes
|
|
163
|
+
|
|
164
|
+
### 0.3.1
|
|
165
|
+
|
|
166
|
+
New `bf-theme-switch` in `@bitforgehq/ui`: System, Light and Dark as a three-option
|
|
167
|
+
`bf-segmented`, holding a `BfColorScheme` in `value` (`[(value)]`, `changed`), with `label`,
|
|
168
|
+
`size` and `fullWidth`. It injects no service; bind it to `BfColorSchemeService.scheme`
|
|
169
|
+
(`BfPlatformService.theme` in the shell). New `BfLabels` keys `theme`, `themeSystem`, `themeLight`
|
|
170
|
+
and `themeDark`.
|