@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.
Files changed (158) hide show
  1. package/CHANGELOG.md +598 -0
  2. package/README.md +18 -11
  3. package/ai/MANIFEST.index.json +306 -0
  4. package/ai/MANIFEST.json +8915 -3788
  5. package/ai/REFERENCE.index.json +476 -0
  6. package/ai/REFERENCE.json +5810 -2905
  7. package/ai/UI_GUIDE.md +212 -58
  8. package/ai/components/app-frame.md +328 -0
  9. package/ai/components/back-button.md +42 -2
  10. package/ai/components/badge.md +17 -7
  11. package/ai/components/bar-chart.md +188 -0
  12. package/ai/components/breadcrumb-item.md +121 -0
  13. package/ai/components/breadcrumb.md +277 -0
  14. package/ai/components/button.md +82 -15
  15. package/ai/components/calendar.md +300 -0
  16. package/ai/components/card.md +119 -4
  17. package/ai/components/chart-card.md +283 -0
  18. package/ai/components/combobox.md +411 -0
  19. package/ai/components/date-input.md +396 -0
  20. package/ai/components/datetime-input.md +215 -0
  21. package/ai/components/description-item.md +111 -0
  22. package/ai/components/description-list.md +160 -0
  23. package/ai/components/donut-chart.md +169 -0
  24. package/ai/components/empty-state.md +99 -6
  25. package/ai/components/header.md +71 -2
  26. package/ai/components/icon-button.md +74 -16
  27. package/ai/components/icon.md +63 -7
  28. package/ai/components/line-chart.md +234 -0
  29. package/ai/components/list.md +70 -6
  30. package/ai/components/menu.md +5 -0
  31. package/ai/components/multi-combobox.md +257 -0
  32. package/ai/components/nav-group.md +178 -0
  33. package/ai/components/nav-item.md +210 -0
  34. package/ai/components/page-header.md +237 -0
  35. package/ai/components/pagination.md +173 -0
  36. package/ai/components/popover.md +250 -0
  37. package/ai/components/side-panel.md +391 -0
  38. package/ai/components/sidebar-toggle.md +287 -0
  39. package/ai/components/sidebar.md +541 -0
  40. package/ai/components/skeleton.md +215 -0
  41. package/ai/components/sparkline.md +188 -0
  42. package/ai/components/stat.md +297 -0
  43. package/ai/components/tab-group.md +226 -0
  44. package/ai/components/tab-panel.md +163 -0
  45. package/ai/components/table.md +697 -0
  46. package/ai/components/tabs.md +46 -2
  47. package/ai/components/theme-menu-button.md +182 -0
  48. package/ai/components/theme-switch.md +170 -0
  49. package/ai/components/time-input.md +240 -0
  50. package/ai/components/toolbar.md +354 -0
  51. package/ai/components/tooltip.md +230 -0
  52. package/ai/components/top-nav.md +159 -0
  53. package/ai/reference/bf-app-frame-breakpoint.md +27 -0
  54. package/ai/reference/bf-app-frame-content-width.md +29 -0
  55. package/ai/reference/bf-badge-appearance.md +36 -0
  56. package/ai/reference/bf-badge-variant.md +1 -1
  57. package/ai/reference/bf-bar-chart-orientation.md +27 -0
  58. package/ai/reference/bf-button-appearance.md +36 -0
  59. package/ai/reference/bf-button-variant.md +1 -1
  60. package/ai/reference/bf-calendar-disabled-date.md +23 -0
  61. package/ai/reference/bf-calendar-harness.md +51 -0
  62. package/ai/reference/bf-calendar-weekday.md +31 -0
  63. package/ai/reference/bf-chart-card-heading-level.md +26 -0
  64. package/ai/reference/bf-chart-curve.md +28 -0
  65. package/ai/reference/bf-chart-harness.md +51 -0
  66. package/ai/reference/bf-chart-legend-position.md +30 -0
  67. package/ai/reference/bf-chart-series.md +23 -0
  68. package/ai/reference/bf-chart-value-format.md +23 -0
  69. package/ai/reference/bf-color-scheme-service.md +30 -0
  70. package/ai/reference/bf-color-scheme.md +31 -0
  71. package/ai/reference/bf-combobox-filter.md +27 -0
  72. package/ai/reference/bf-combobox-harness.md +89 -0
  73. package/ai/reference/bf-combobox-option.md +33 -0
  74. package/ai/reference/bf-compare-with.md +2 -0
  75. package/ai/reference/bf-date-input-harness.md +67 -0
  76. package/ai/reference/bf-date-input-picker.md +27 -0
  77. package/ai/reference/bf-date-time-input-harness.md +76 -0
  78. package/ai/reference/bf-date-time-input-picker.md +27 -0
  79. package/ai/reference/bf-description-list-layout.md +26 -0
  80. package/ai/reference/bf-donut-slice.md +22 -0
  81. package/ai/reference/bf-empty-state-heading-level.md +1 -1
  82. package/ai/reference/bf-empty-state-heading.md +33 -0
  83. package/ai/reference/bf-labels.md +73 -20
  84. package/ai/reference/bf-menu-button-variant.md +1 -0
  85. package/ai/reference/bf-menu-placement.md +1 -0
  86. package/ai/reference/bf-overlay-options.md +28 -27
  87. package/ai/reference/bf-overlay-placement.md +34 -0
  88. package/ai/reference/bf-overlay-position.md +1 -1
  89. package/ai/reference/bf-overlay-service.md +5 -3
  90. package/ai/reference/bf-page-header-heading-level.md +26 -0
  91. package/ai/reference/bf-pagination-harness.md +61 -0
  92. package/ai/reference/bf-platform-service.md +17 -15
  93. package/ai/reference/bf-popover-harness.md +42 -0
  94. package/ai/reference/bf-popover-placement.md +28 -0
  95. package/ai/reference/bf-popover-trigger-directive.md +56 -0
  96. package/ai/reference/bf-side-panel-config.md +35 -0
  97. package/ai/reference/bf-side-panel-harness.md +34 -0
  98. package/ai/reference/bf-side-panel-ref.md +44 -0
  99. package/ai/reference/bf-side-panel-service.md +43 -0
  100. package/ai/reference/bf-side-panel-template-context.md +32 -0
  101. package/ai/reference/bf-side-panel-width.md +28 -0
  102. package/ai/reference/bf-sidebar-state-service.md +21 -0
  103. package/ai/reference/bf-sidebar-toggle-harness.md +32 -0
  104. package/ai/reference/bf-size.md +13 -0
  105. package/ai/reference/bf-skeleton-shape.md +28 -0
  106. package/ai/reference/bf-stat-appearance.md +33 -0
  107. package/ai/reference/bf-stat-trend-intent.md +27 -0
  108. package/ai/reference/bf-stat-trend.md +27 -0
  109. package/ai/reference/bf-stat-variant.md +26 -0
  110. package/ai/reference/bf-tab-group-harness.md +44 -0
  111. package/ai/reference/bf-table-align.md +26 -0
  112. package/ai/reference/bf-table-cell-context.md +24 -0
  113. package/ai/reference/bf-table-cell-directive.md +22 -0
  114. package/ai/reference/bf-table-column.md +28 -0
  115. package/ai/reference/bf-table-harness.md +77 -0
  116. package/ai/reference/bf-table-row-key.md +21 -0
  117. package/ai/reference/bf-table-sort-direction.md +26 -0
  118. package/ai/reference/bf-table-sort.md +27 -0
  119. package/ai/reference/bf-table-stack.md +29 -0
  120. package/ai/reference/bf-theme.md +5 -4
  121. package/ai/reference/bf-time-input-harness.md +67 -0
  122. package/ai/reference/bf-time-input-hour-cycle.md +27 -0
  123. package/ai/reference/bf-time-input-picker.md +27 -0
  124. package/ai/reference/bf-tooltip-directive.md +43 -0
  125. package/ai/reference/bf-tooltip-harness.md +43 -0
  126. package/ai/reference/bf-tooltip-placement.md +28 -0
  127. package/ai/reference/bf_color_scheme_storage.md +20 -0
  128. package/ai/reference/bf_side_panel_data.md +17 -0
  129. package/ai/reference/bf_sidebar_state_storage.md +20 -0
  130. package/ai/reference/bf_theme_storage.md +3 -1
  131. package/ai/reference/provide-bf-color-scheme.md +20 -0
  132. package/ai/reference/provide-bf-labels.md +13 -4
  133. package/ai/reference/provide-bf-sidebar-state.md +20 -0
  134. package/ai/reference/slide-in-from-end.md +24 -0
  135. package/ai/reference/slide-out-to-end.md +24 -0
  136. package/bin/bf-ui.js +433 -3
  137. package/fesm2022/bitforgehq-ui-charts.mjs +3045 -0
  138. package/fesm2022/bitforgehq-ui-charts.mjs.map +1 -0
  139. package/fesm2022/bitforgehq-ui-icons.mjs +107 -3
  140. package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -1
  141. package/fesm2022/bitforgehq-ui-mobile.mjs +14 -59
  142. package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -1
  143. package/fesm2022/bitforgehq-ui-overlay.mjs +6566 -38
  144. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
  145. package/fesm2022/bitforgehq-ui-testing.mjs +634 -1
  146. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
  147. package/fesm2022/bitforgehq-ui.mjs +5708 -137
  148. package/fesm2022/bitforgehq-ui.mjs.map +1 -1
  149. package/llms-full.txt +26766 -14470
  150. package/llms.txt +106 -1
  151. package/package.json +7 -3
  152. package/theme.css +101 -0
  153. package/types/bitforgehq-ui-charts.d.ts +967 -0
  154. package/types/bitforgehq-ui-icons.d.ts +13 -1
  155. package/types/bitforgehq-ui-mobile.d.ts +25 -18
  156. package/types/bitforgehq-ui-overlay.d.ts +1895 -7
  157. package/types/bitforgehq-ui-testing.d.ts +782 -1
  158. package/types/bitforgehq-ui.d.ts +2393 -45
@@ -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 on desktop.
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 sidebar or a top `nav` of links with `routerLinkActive`, and a plain `router-outlet`. Inside the shell under a mouse, the tab links take the `bf-tab` hover and focus styles.
197
+ `bf-tabs` is part of the Capacitor shell (`provideBfShell()`). On a desktop web app, put the top-level destinations in a [bf-top-nav](https://bitforge-ui.web.app/components/top-nav) (a few sections) or a [bf-sidebar](https://bitforge-ui.web.app/components/sidebar) (more, in groups, collapsible to an icon rail) of `a[bfNavItem]` links, each with `routerLink`, `routerLinkActive` and `ariaCurrentWhenActive="page"`, above a plain `router-outlet`. The router sets `aria-current="page"` on the link of the current route and the item shows it. Drive both from one array of destinations: inside the shell the same array feeds the `bf-tab`s of a [bf-tab-bar](https://bitforge-ui.web.app/components/tab-bar), and below a breakpoint a [bf-menu-button](https://bitforge-ui.web.app/components/menu-button) of `a[bfMenuItem]` links (see bf-sidebar, Responsive). Wrap the desktop app in a [bf-app-frame](https://bitforge-ui.web.app/components/app-frame): it holds the sidebar from `md` up and, below it, a header bar with that menu button. Inside the shell under a mouse, the tab links take the `bf-tab` hover and focus styles.
198
+
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`.