@bitforgehq/ui 0.3.1 → 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 (45) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/README.md +9 -8
  3. package/ai/MANIFEST.index.json +18 -0
  4. package/ai/MANIFEST.json +368 -31
  5. package/ai/REFERENCE.index.json +63 -0
  6. package/ai/REFERENCE.json +247 -5
  7. package/ai/UI_GUIDE.md +59 -33
  8. package/ai/components/app-frame.md +328 -0
  9. package/ai/components/card.md +56 -2
  10. package/ai/components/empty-state.md +99 -6
  11. package/ai/components/header.md +5 -1
  12. package/ai/components/sidebar-toggle.md +287 -0
  13. package/ai/components/sidebar.md +188 -10
  14. package/ai/components/stat.md +74 -10
  15. package/ai/components/table.md +134 -5
  16. package/ai/components/tabs.md +5 -1
  17. package/ai/components/toolbar.md +69 -4
  18. package/ai/reference/bf-app-frame-breakpoint.md +27 -0
  19. package/ai/reference/bf-app-frame-content-width.md +29 -0
  20. package/ai/reference/bf-empty-state-heading-level.md +1 -1
  21. package/ai/reference/bf-empty-state-heading.md +33 -0
  22. package/ai/reference/bf-labels.md +6 -0
  23. package/ai/reference/bf-sidebar-state-service.md +21 -0
  24. package/ai/reference/bf-sidebar-toggle-harness.md +32 -0
  25. package/ai/reference/bf-size.md +1 -0
  26. package/ai/reference/bf-stat-appearance.md +33 -0
  27. package/ai/reference/bf-stat-variant.md +1 -1
  28. package/ai/reference/bf-table-harness.md +1 -1
  29. package/ai/reference/bf-table-stack.md +29 -0
  30. package/ai/reference/bf_sidebar_state_storage.md +20 -0
  31. package/ai/reference/provide-bf-labels.md +4 -3
  32. package/ai/reference/provide-bf-sidebar-state.md +20 -0
  33. package/bin/bf-ui.js +433 -3
  34. package/fesm2022/bitforgehq-ui-overlay.mjs +571 -446
  35. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
  36. package/fesm2022/bitforgehq-ui-testing.mjs +18 -2
  37. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
  38. package/fesm2022/bitforgehq-ui.mjs +759 -127
  39. package/fesm2022/bitforgehq-ui.mjs.map +1 -1
  40. package/llms-full.txt +1560 -43
  41. package/llms.txt +11 -0
  42. package/package.json +1 -1
  43. package/types/bitforgehq-ui-overlay.d.ts +51 -0
  44. package/types/bitforgehq-ui-testing.d.ts +35 -1
  45. package/types/bitforgehq-ui.d.ts +276 -20
@@ -36,6 +36,13 @@
36
36
  "entryPoint": "@bitforgehq/ui",
37
37
  "summary": "Injection token holding the user-facing strings in effect for an injector"
38
38
  },
39
+ {
40
+ "name": "BF_SIDEBAR_STATE_STORAGE",
41
+ "slug": "bf_sidebar_state_storage",
42
+ "kind": "token",
43
+ "entryPoint": "@bitforgehq/ui",
44
+ "summary": "Injection token for the store that keeps the collapsed sidebar between visits"
45
+ },
39
46
  {
40
47
  "name": "BF_TAB_BAR",
41
48
  "slug": "bf_tab_bar",
@@ -71,6 +78,20 @@
71
78
  "entryPoint": "@bitforgehq/ui",
72
79
  "summary": "The four tones of an alert, from neutral information to a danger message"
73
80
  },
81
+ {
82
+ "name": "BfAppFrameBreakpoint",
83
+ "slug": "bf-app-frame-breakpoint",
84
+ "kind": "type",
85
+ "entryPoint": "@bitforgehq/ui",
86
+ "summary": "The breakpoint where an app frame switches from its header bar to the sidebar"
87
+ },
88
+ {
89
+ "name": "BfAppFrameContentWidth",
90
+ "slug": "bf-app-frame-content-width",
91
+ "kind": "type",
92
+ "entryPoint": "@bitforgehq/ui",
93
+ "summary": "The maximum width of an app frame's content column, from medium to full"
94
+ },
74
95
  {
75
96
  "name": "BfAutocapitalize",
76
97
  "slug": "bf-autocapitalize",
@@ -218,6 +239,13 @@
218
239
  "entryPoint": "@bitforgehq/ui",
219
240
  "summary": "The two description list layouts, divided label-value rows or a grid of tiles"
220
241
  },
242
+ {
243
+ "name": "BfEmptyStateHeading",
244
+ "slug": "bf-empty-state-heading",
245
+ "kind": "type",
246
+ "entryPoint": "@bitforgehq/ui",
247
+ "summary": "The heading level of an empty state title, one for a whole page"
248
+ },
221
249
  {
222
250
  "name": "BfEmptyStateHeadingLevel",
223
251
  "slug": "bf-empty-state-heading-level",
@@ -365,6 +393,13 @@
365
393
  "entryPoint": "@bitforgehq/ui",
366
394
  "summary": "One choice of a select with its value, its label and a disabled flag"
367
395
  },
396
+ {
397
+ "name": "BfSidebarStateService",
398
+ "slug": "bf-sidebar-state-service",
399
+ "kind": "service",
400
+ "entryPoint": "@bitforgehq/ui",
401
+ "summary": "The sidebar's collapsed state, kept in storage between visits of the app"
402
+ },
368
403
  {
369
404
  "name": "BfSize",
370
405
  "slug": "bf-size",
@@ -386,6 +421,13 @@
386
421
  "entryPoint": "@bitforgehq/ui",
387
422
  "summary": "The three spinner diameters, from inline text to a page loading state"
388
423
  },
424
+ {
425
+ "name": "BfStatAppearance",
426
+ "slug": "bf-stat-appearance",
427
+ "kind": "type",
428
+ "entryPoint": "@bitforgehq/ui",
429
+ "summary": "The three stat surfaces, plain, an outlined card or a sunken tile"
430
+ },
389
431
  {
390
432
  "name": "BfStatTrend",
391
433
  "slug": "bf-stat-trend",
@@ -470,6 +512,13 @@
470
512
  "entryPoint": "@bitforgehq/ui",
471
513
  "summary": "The direction a sorted table column is in, ascending or descending"
472
514
  },
515
+ {
516
+ "name": "BfTableStack",
517
+ "slug": "bf-table-stack",
518
+ "kind": "type",
519
+ "entryPoint": "@bitforgehq/ui",
520
+ "summary": "When a table stacks its rows into cards: never, when narrow, or under touch"
521
+ },
473
522
  {
474
523
  "name": "BfTabLinks",
475
524
  "slug": "bf-tab-links",
@@ -519,6 +568,13 @@
519
568
  "entryPoint": "@bitforgehq/ui",
520
569
  "summary": "Replaces built-in strings for an app or a sub-tree, such as a translation"
521
570
  },
571
+ {
572
+ "name": "provideBfSidebarState",
573
+ "slug": "provide-bf-sidebar-state",
574
+ "kind": "function",
575
+ "entryPoint": "@bitforgehq/ui",
576
+ "summary": "Remembers the collapsed sidebar between visits, for desktop web apps"
577
+ },
522
578
  {
523
579
  "name": "BF_DESKTOP_TOAST_DEFAULTS",
524
580
  "slug": "bf_desktop_toast_defaults",
@@ -1814,6 +1870,13 @@
1814
1870
  "entryPoint": "@bitforgehq/ui/testing",
1815
1871
  "summary": "Test harness for the select: reads its label and options and picks one"
1816
1872
  },
1873
+ {
1874
+ "name": "BfSidebarToggleHarness",
1875
+ "slug": "bf-sidebar-toggle-harness",
1876
+ "kind": "class",
1877
+ "entryPoint": "@bitforgehq/ui/testing",
1878
+ "summary": "Test harness for a sidebar toggle: reads and flips the collapsed sidebar"
1879
+ },
1817
1880
  {
1818
1881
  "name": "BfSidePanelHarness",
1819
1882
  "slug": "bf-side-panel-harness",
package/ai/REFERENCE.json CHANGED
@@ -81,6 +81,22 @@
81
81
  "since": "0.3.0",
82
82
  "signature": "const BF_LABELS: InjectionToken<BfLabels>"
83
83
  },
84
+ {
85
+ "name": "BF_SIDEBAR_STATE_STORAGE",
86
+ "slug": "bf_sidebar_state_storage",
87
+ "kind": "token",
88
+ "entryPoint": "@bitforgehq/ui",
89
+ "file": "src/config/sidebar-state.ts",
90
+ "summary": "Injection token for the store that keeps the collapsed sidebar between visits",
91
+ "description": "Where `BfSidebarStateService` keeps whether the sidebar is collapsed (the key\n`bf-sidebar-collapsed`, `'true'` or `'false'`): `localStorage` when it is available, otherwise\nan in-memory map. Provide another store (`sessionStorage`, or an in-memory one in tests) to keep\nit elsewhere.",
92
+ "members": [],
93
+ "methods": [],
94
+ "slots": [],
95
+ "references": [],
96
+ "usedBy": [],
97
+ "since": "0.3.2",
98
+ "signature": "const BF_SIDEBAR_STATE_STORAGE: InjectionToken<Pick<Storage, \"getItem\" | \"setItem\">>"
99
+ },
84
100
  {
85
101
  "name": "BF_TAB_BAR",
86
102
  "slug": "bf_tab_bar",
@@ -162,6 +178,40 @@
162
178
  "signature": "type BfAlertVariant = 'neutral' | 'success' | 'warning' | 'danger'",
163
179
  "values": ["neutral", "success", "warning", "danger"]
164
180
  },
181
+ {
182
+ "name": "BfAppFrameBreakpoint",
183
+ "slug": "bf-app-frame-breakpoint",
184
+ "kind": "type",
185
+ "entryPoint": "@bitforgehq/ui",
186
+ "file": "src/components/app-frame/app-frame.component.ts",
187
+ "summary": "The breakpoint where an app frame switches from its header bar to the sidebar",
188
+ "description": "The viewport width from which `bf-app-frame` shows its sidebar column instead of the narrow\nheader bar: Tailwind's `md` (768 px) or `lg` (1024 px).",
189
+ "members": [],
190
+ "methods": [],
191
+ "slots": [],
192
+ "references": [],
193
+ "usedBy": ["bf-app-frame-component"],
194
+ "since": "0.3.2",
195
+ "signature": "type BfAppFrameBreakpoint = 'md' | 'lg'",
196
+ "values": ["md", "lg"]
197
+ },
198
+ {
199
+ "name": "BfAppFrameContentWidth",
200
+ "slug": "bf-app-frame-content-width",
201
+ "kind": "type",
202
+ "entryPoint": "@bitforgehq/ui",
203
+ "file": "src/components/app-frame/app-frame.component.ts",
204
+ "summary": "The maximum width of an app frame's content column, from medium to full",
205
+ "description": "The maximum width of `bf-app-frame`'s content column: `md` 48rem, `lg` 64rem, `xl` 80rem, or\n`full` for no limit.",
206
+ "members": [],
207
+ "methods": [],
208
+ "slots": [],
209
+ "references": [],
210
+ "usedBy": ["bf-app-frame-component"],
211
+ "since": "0.3.2",
212
+ "signature": "type BfAppFrameContentWidth = 'md' | 'lg' | 'xl' | 'full'",
213
+ "values": ["md", "lg", "xl", "full"]
214
+ },
165
215
  {
166
216
  "name": "BfAutocapitalize",
167
217
  "slug": "bf-autocapitalize",
@@ -586,6 +636,23 @@
586
636
  "signature": "type BfDescriptionListLayout = 'rows' | 'grid'",
587
637
  "values": ["rows", "grid"]
588
638
  },
639
+ {
640
+ "name": "BfEmptyStateHeading",
641
+ "slug": "bf-empty-state-heading",
642
+ "kind": "type",
643
+ "entryPoint": "@bitforgehq/ui",
644
+ "file": "src/components/empty-state/empty-state.component.ts",
645
+ "summary": "The heading level of an empty state title, one for a whole page",
646
+ "description": "The heading level a `bf-empty-state` title takes: a `BfEmptyStateHeadingLevel`, or `1` when the\nempty state is the page itself (a not-found or no-access page). A new type, so `Record`s over\nthe released `BfEmptyStateHeadingLevel` keep compiling.",
647
+ "members": [],
648
+ "methods": [],
649
+ "slots": [],
650
+ "references": ["bf-empty-state-heading-level"],
651
+ "usedBy": ["bf-empty-state-component"],
652
+ "since": "0.3.2",
653
+ "signature": "type BfEmptyStateHeading = BfEmptyStateHeadingLevel | 1",
654
+ "values": ["2", "3", "1"]
655
+ },
589
656
  {
590
657
  "name": "BfEmptyStateHeadingLevel",
591
658
  "slug": "bf-empty-state-heading-level",
@@ -598,7 +665,7 @@
598
665
  "methods": [],
599
666
  "slots": [],
600
667
  "references": [],
601
- "usedBy": ["bf-empty-state-component"],
668
+ "usedBy": ["bf-empty-state-heading"],
602
669
  "since": "0.3.0",
603
670
  "signature": "type BfEmptyStateHeadingLevel = 2 | 3",
604
671
  "values": ["2", "3"]
@@ -999,6 +1066,33 @@
999
1066
  "readonly": true,
1000
1067
  "default": "'Select all rows'"
1001
1068
  },
1069
+ {
1070
+ "name": "tableSortBy",
1071
+ "kind": "property",
1072
+ "type": "string",
1073
+ "required": true,
1074
+ "doc": "The label of a stacked bf-table's sort select.",
1075
+ "readonly": true,
1076
+ "default": "'Sort by'"
1077
+ },
1078
+ {
1079
+ "name": "tableSortAscending",
1080
+ "kind": "property",
1081
+ "type": "(column: string) => string",
1082
+ "required": true,
1083
+ "doc": "A stacked bf-table's sort option for a column in ascending order.",
1084
+ "readonly": true,
1085
+ "default": "(column) => `${column}, ascending`"
1086
+ },
1087
+ {
1088
+ "name": "tableSortDescending",
1089
+ "kind": "property",
1090
+ "type": "(column: string) => string",
1091
+ "required": true,
1092
+ "doc": "A stacked bf-table's sort option for a column in descending order.",
1093
+ "readonly": true,
1094
+ "default": "(column) => `${column}, descending`"
1095
+ },
1002
1096
  {
1003
1097
  "name": "theme",
1004
1098
  "kind": "property",
@@ -1071,6 +1165,33 @@
1071
1165
  "readonly": true,
1072
1166
  "default": "(count) => `${count} new`"
1073
1167
  },
1168
+ {
1169
+ "name": "sidebarCollapse",
1170
+ "kind": "property",
1171
+ "type": "string",
1172
+ "required": true,
1173
+ "doc": "bf-sidebar-toggle's name and tooltip while the sidebar is expanded.",
1174
+ "readonly": true,
1175
+ "default": "'Collapse sidebar'"
1176
+ },
1177
+ {
1178
+ "name": "sidebarExpand",
1179
+ "kind": "property",
1180
+ "type": "string",
1181
+ "required": true,
1182
+ "doc": "bf-sidebar-toggle's name and tooltip while the sidebar is an icon rail.",
1183
+ "readonly": true,
1184
+ "default": "'Expand sidebar'"
1185
+ },
1186
+ {
1187
+ "name": "skipToContent",
1188
+ "kind": "property",
1189
+ "type": "string",
1190
+ "required": true,
1191
+ "doc": "bf-app-frame's skip link, the first stop of the tab order, which moves focus to the content.",
1192
+ "readonly": true,
1193
+ "default": "'Skip to main content'"
1194
+ },
1074
1195
  {
1075
1196
  "name": "pagination",
1076
1197
  "kind": "property",
@@ -1655,6 +1776,30 @@
1655
1776
  "since": "0.1.0",
1656
1777
  "signature": "interface BfSelectOption<T = string>"
1657
1778
  },
1779
+ {
1780
+ "name": "BfSidebarStateService",
1781
+ "slug": "bf-sidebar-state-service",
1782
+ "kind": "service",
1783
+ "entryPoint": "@bitforgehq/ui",
1784
+ "file": "src/config/sidebar-state.ts",
1785
+ "summary": "The sidebar's collapsed state, kept in storage between visits of the app",
1786
+ "description": "Whether the app's `bf-sidebar` is collapsed to an icon rail, remembered between visits: the app\nbinds the sidebar's `[(collapsed)]` to `sidebarState.collapsed`. With `provideBfSidebarState()`\nevery change is stored in `BF_SIDEBAR_STATE_STORAGE` under `bf-sidebar-collapsed`; without it\nthe signal still holds the state but nothing is written. Storage is read in the browser only, so a\nserver render starts expanded and writes nothing. The widgets never inject it.",
1787
+ "members": [
1788
+ {
1789
+ "name": "collapsed",
1790
+ "kind": "property",
1791
+ "type": "WritableSignal<boolean>",
1792
+ "required": false,
1793
+ "doc": "Whether the sidebar is collapsed: the stored state in the browser, else false. Bind it two-way to `bf-sidebar`'s `collapsed`.",
1794
+ "readonly": true
1795
+ }
1796
+ ],
1797
+ "methods": [],
1798
+ "slots": [],
1799
+ "references": [],
1800
+ "usedBy": [],
1801
+ "since": "0.3.2"
1802
+ },
1658
1803
  {
1659
1804
  "name": "BfSize",
1660
1805
  "slug": "bf-size",
@@ -1692,6 +1837,7 @@
1692
1837
  "bf-segmented-component",
1693
1838
  "bf-segmented-size",
1694
1839
  "bf-select-component",
1840
+ "bf-sidebar-toggle-component",
1695
1841
  "bf-tab-group-component",
1696
1842
  "bf-table-component",
1697
1843
  "bf-textarea-component",
@@ -1738,6 +1884,23 @@
1738
1884
  "signature": "type BfSpinnerSize = 'sm' | 'md' | 'lg'",
1739
1885
  "values": ["sm", "md", "lg"]
1740
1886
  },
1887
+ {
1888
+ "name": "BfStatAppearance",
1889
+ "slug": "bf-stat-appearance",
1890
+ "kind": "type",
1891
+ "entryPoint": "@bitforgehq/ui",
1892
+ "file": "src/components/stat/stat.component.ts",
1893
+ "summary": "The three stat surfaces, plain, an outlined card or a sunken tile",
1894
+ "description": "The surface of a `bf-stat`, as its `variant` input takes it: a `BfStatVariant` or tile (a\nsunken, borderless tile for a stats row inside a raised card). A new type, so `Record`s over\nthe released `BfStatVariant` keep compiling.",
1895
+ "members": [],
1896
+ "methods": [],
1897
+ "slots": [],
1898
+ "references": ["bf-stat-variant"],
1899
+ "usedBy": ["bf-stat-component"],
1900
+ "since": "0.3.2",
1901
+ "signature": "type BfStatAppearance = BfStatVariant | 'tile'",
1902
+ "values": ["plain", "card", "tile"]
1903
+ },
1741
1904
  {
1742
1905
  "name": "BfStatTrend",
1743
1906
  "slug": "bf-stat-trend",
@@ -1784,7 +1947,7 @@
1784
1947
  "methods": [],
1785
1948
  "slots": [],
1786
1949
  "references": [],
1787
- "usedBy": ["bf-stat-component"],
1950
+ "usedBy": ["bf-stat-appearance"],
1788
1951
  "since": "0.3.1",
1789
1952
  "signature": "type BfStatVariant = 'plain' | 'card'",
1790
1953
  "values": ["plain", "card"]
@@ -2144,6 +2307,23 @@
2144
2307
  "signature": "type BfTableSortDirection = 'ascending' | 'descending'",
2145
2308
  "values": ["ascending", "descending"]
2146
2309
  },
2310
+ {
2311
+ "name": "BfTableStack",
2312
+ "slug": "bf-table-stack",
2313
+ "kind": "type",
2314
+ "entryPoint": "@bitforgehq/ui",
2315
+ "file": "src/components/table/table.component.ts",
2316
+ "summary": "When a table stacks its rows into cards: never, when narrow, or under touch",
2317
+ "description": "When a `bf-table` renders each row as a card of label/value pairs: `never` (the default),\n`narrow` while the table is narrower than 36rem (a container query), or `coarse` under a coarse\npointer at any width.",
2318
+ "members": [],
2319
+ "methods": [],
2320
+ "slots": [],
2321
+ "references": [],
2322
+ "usedBy": ["bf-table-component"],
2323
+ "since": "0.3.2",
2324
+ "signature": "type BfTableStack = 'never' | 'narrow' | 'coarse'",
2325
+ "values": ["never", "narrow", "coarse"]
2326
+ },
2147
2327
  {
2148
2328
  "name": "BfTabLinks",
2149
2329
  "slug": "bf-tab-links",
@@ -2419,7 +2599,7 @@
2419
2599
  "entryPoint": "@bitforgehq/ui",
2420
2600
  "file": "src/config/labels.ts",
2421
2601
  "summary": "Replaces built-in strings for an app or a sub-tree, such as a translation",
2422
- "description": "Replaces built-in strings, merged over the parent's (validation merged key by key); a key left\nout or set to `undefined` keeps the parent's string. In `ApplicationConfig.providers` it covers\nevery string. In a component's `providers` it covers that sub-tree's widgets and form controls\n(`loading`, `dismiss`, `tabBar`, `tabBadge`, `back`, `refreshing`, `tableSelectAll`, `theme`,\n`themeSystem`, `themeLight`, `themeDark`, `breadcrumb`, `breadcrumbExpand`, `navigation`,\n`navBadge`, `pagination`, `previousPage`, `nextPage`, `pageOf`, `goToPage`, `tabGroup`,\n`calendarPreviousMonth`, `calendarNextMonth`, `calendarChooseMonth`, `calendarChooseYear`,\n`statTrendUp`, `statTrendDown`, `statTrendFlat`, `comboboxEmpty`, `comboboxRemove`,\n`dateInputDay`, `dateInputMonth`, `dateInputYear`, `dateInputOpenCalendar`, `dateInputInvalid`,\n`dateInputUnavailable`, `timeInputHour`, `timeInputMinute`, `timeInputSecond`,\n`timeInputDayPeriod`, `timeInputOpenList`, `timeInputInvalid`, `timeInputMin`, `timeInputMax`,\n`chart`, `chartLegend`, `chartValues`, `chartShowData`, `chartHideData`, `chartCategory`,\n`chartValue`, `chartEmpty`, `chartError`, `validation`); the overlay services (`ok`, `cancel`,\n`actions`, `sheet`, `modal`, `close`, `expand`, `collapse`, `sheetPosition`, `dialog`,\n`notifications`) are root singletons and pick it up only when the caller passes its injector:\n`open(…, { injector: inject(Injector) })`.",
2602
+ "description": "Replaces built-in strings, merged over the parent's (validation merged key by key); a key left\nout or set to `undefined` keeps the parent's string. In `ApplicationConfig.providers` it covers\nevery string. In a component's `providers` it covers that sub-tree's widgets and form controls\n(`loading`, `dismiss`, `tabBar`, `tabBadge`, `back`, `refreshing`, `tableSelectAll`, `tableSortBy`,\n`tableSortAscending`, `tableSortDescending`, `theme`, `themeSystem`, `themeLight`, `themeDark`,\n`breadcrumb`, `breadcrumbExpand`, `navigation`, `navBadge`, `sidebarCollapse`, `sidebarExpand`,\n`skipToContent`, `pagination`, `previousPage`, `nextPage`, `pageOf`, `goToPage`, `tabGroup`,\n`calendarPreviousMonth`, `calendarNextMonth`, `calendarChooseMonth`, `calendarChooseYear`,\n`statTrendUp`, `statTrendDown`, `statTrendFlat`, `comboboxEmpty`, `comboboxRemove`,\n`dateInputDay`, `dateInputMonth`, `dateInputYear`, `dateInputOpenCalendar`, `dateInputInvalid`,\n`dateInputUnavailable`, `timeInputHour`, `timeInputMinute`, `timeInputSecond`,\n`timeInputDayPeriod`, `timeInputOpenList`, `timeInputInvalid`, `timeInputMin`, `timeInputMax`,\n`chart`, `chartLegend`, `chartValues`, `chartShowData`, `chartHideData`, `chartCategory`,\n`chartValue`, `chartEmpty`, `chartError`, `validation`); the overlay services (`ok`, `cancel`,\n`actions`, `sheet`, `modal`, `close`, `expand`, `collapse`, `sheetPosition`, `dialog`,\n`notifications`) are root singletons and pick it up only when the caller passes its injector:\n`open(…, { injector: inject(Injector) })`.",
2423
2603
  "members": [],
2424
2604
  "methods": [
2425
2605
  {
@@ -2434,7 +2614,7 @@
2434
2614
  }
2435
2615
  ],
2436
2616
  "returns": "Provider",
2437
- "doc": "Replaces built-in strings, merged over the parent's (validation merged key by key); a key left\nout or set to `undefined` keeps the parent's string. In `ApplicationConfig.providers` it covers\nevery string. In a component's `providers` it covers that sub-tree's widgets and form controls\n(`loading`, `dismiss`, `tabBar`, `tabBadge`, `back`, `refreshing`, `tableSelectAll`, `theme`,\n`themeSystem`, `themeLight`, `themeDark`, `breadcrumb`, `breadcrumbExpand`, `navigation`,\n`navBadge`, `pagination`, `previousPage`, `nextPage`, `pageOf`, `goToPage`, `tabGroup`,\n`calendarPreviousMonth`, `calendarNextMonth`, `calendarChooseMonth`, `calendarChooseYear`,\n`statTrendUp`, `statTrendDown`, `statTrendFlat`, `comboboxEmpty`, `comboboxRemove`,\n`dateInputDay`, `dateInputMonth`, `dateInputYear`, `dateInputOpenCalendar`, `dateInputInvalid`,\n`dateInputUnavailable`, `timeInputHour`, `timeInputMinute`, `timeInputSecond`,\n`timeInputDayPeriod`, `timeInputOpenList`, `timeInputInvalid`, `timeInputMin`, `timeInputMax`,\n`chart`, `chartLegend`, `chartValues`, `chartShowData`, `chartHideData`, `chartCategory`,\n`chartValue`, `chartEmpty`, `chartError`, `validation`); the overlay services (`ok`, `cancel`,\n`actions`, `sheet`, `modal`, `close`, `expand`, `collapse`, `sheetPosition`, `dialog`,\n`notifications`) are root singletons and pick it up only when the caller passes its injector:\n`open(…, { injector: inject(Injector) })`."
2617
+ "doc": "Replaces built-in strings, merged over the parent's (validation merged key by key); a key left\nout or set to `undefined` keeps the parent's string. In `ApplicationConfig.providers` it covers\nevery string. In a component's `providers` it covers that sub-tree's widgets and form controls\n(`loading`, `dismiss`, `tabBar`, `tabBadge`, `back`, `refreshing`, `tableSelectAll`, `tableSortBy`,\n`tableSortAscending`, `tableSortDescending`, `theme`, `themeSystem`, `themeLight`, `themeDark`,\n`breadcrumb`, `breadcrumbExpand`, `navigation`, `navBadge`, `sidebarCollapse`, `sidebarExpand`,\n`skipToContent`, `pagination`, `previousPage`, `nextPage`, `pageOf`, `goToPage`, `tabGroup`,\n`calendarPreviousMonth`, `calendarNextMonth`, `calendarChooseMonth`, `calendarChooseYear`,\n`statTrendUp`, `statTrendDown`, `statTrendFlat`, `comboboxEmpty`, `comboboxRemove`,\n`dateInputDay`, `dateInputMonth`, `dateInputYear`, `dateInputOpenCalendar`, `dateInputInvalid`,\n`dateInputUnavailable`, `timeInputHour`, `timeInputMinute`, `timeInputSecond`,\n`timeInputDayPeriod`, `timeInputOpenList`, `timeInputInvalid`, `timeInputMin`, `timeInputMax`,\n`chart`, `chartLegend`, `chartValues`, `chartShowData`, `chartHideData`, `chartCategory`,\n`chartValue`, `chartEmpty`, `chartError`, `validation`); the overlay services (`ok`, `cancel`,\n`actions`, `sheet`, `modal`, `close`, `expand`, `collapse`, `sheetPosition`, `dialog`,\n`notifications`) are root singletons and pick it up only when the caller passes its injector:\n`open(…, { injector: inject(Injector) })`."
2438
2618
  }
2439
2619
  ],
2440
2620
  "slots": [],
@@ -2443,6 +2623,30 @@
2443
2623
  "since": "0.3.0",
2444
2624
  "signature": "function provideBfLabels(overrides: BfLabelsOverrides): Provider"
2445
2625
  },
2626
+ {
2627
+ "name": "provideBfSidebarState",
2628
+ "slug": "provide-bf-sidebar-state",
2629
+ "kind": "function",
2630
+ "entryPoint": "@bitforgehq/ui",
2631
+ "file": "src/config/sidebar-state.ts",
2632
+ "summary": "Remembers the collapsed sidebar between visits, for desktop web apps",
2633
+ "description": "Lets `BfSidebarStateService` remember the collapsed sidebar: it stores every change of its\n`collapsed` signal (key `bf-sidebar-collapsed`, `localStorage` in the browser), so the app opens\nwith the sidebar as the user left it. Add it to `ApplicationConfig.providers` and bind the\nsidebar's `[(collapsed)]` to `sidebarState.collapsed`.",
2634
+ "members": [],
2635
+ "methods": [
2636
+ {
2637
+ "name": "provideBfSidebarState",
2638
+ "signature": "provideBfSidebarState(): EnvironmentProviders",
2639
+ "params": [],
2640
+ "returns": "EnvironmentProviders",
2641
+ "doc": "Lets `BfSidebarStateService` remember the collapsed sidebar: it stores every change of its\n`collapsed` signal (key `bf-sidebar-collapsed`, `localStorage` in the browser), so the app opens\nwith the sidebar as the user left it. Add it to `ApplicationConfig.providers` and bind the\nsidebar's `[(collapsed)]` to `sidebarState.collapsed`."
2642
+ }
2643
+ ],
2644
+ "slots": [],
2645
+ "references": [],
2646
+ "usedBy": [],
2647
+ "since": "0.3.2",
2648
+ "signature": "function provideBfSidebarState(): EnvironmentProviders"
2649
+ },
2446
2650
  {
2447
2651
  "name": "BF_DESKTOP_TOAST_DEFAULTS",
2448
2652
  "slug": "bf_desktop_toast_defaults",
@@ -9428,6 +9632,44 @@
9428
9632
  "since": "0.3.0",
9429
9633
  "signature": "class BfSelectHarness extends ComponentHarness"
9430
9634
  },
9635
+ {
9636
+ "name": "BfSidebarToggleHarness",
9637
+ "slug": "bf-sidebar-toggle-harness",
9638
+ "kind": "class",
9639
+ "entryPoint": "@bitforgehq/ui/testing",
9640
+ "file": "testing/harnesses/sidebar-toggle.harness.ts",
9641
+ "summary": "Test harness for a sidebar toggle: reads and flips the collapsed sidebar",
9642
+ "description": "Harness for `bf-sidebar-toggle`, the button that collapses a `bf-sidebar` to an icon rail. Its\n`label` filter matches the button's name (\"Collapse sidebar\" or \"Expand sidebar\").",
9643
+ "members": [],
9644
+ "methods": [
9645
+ {
9646
+ "name": "getLabel",
9647
+ "signature": "getLabel(): Promise<string>",
9648
+ "params": [],
9649
+ "returns": "Promise<string>",
9650
+ "doc": "The button's name, which says what the next press does."
9651
+ },
9652
+ {
9653
+ "name": "isExpanded",
9654
+ "signature": "isExpanded(): Promise<boolean>",
9655
+ "params": [],
9656
+ "returns": "Promise<boolean>",
9657
+ "doc": "Whether the sidebar is at full width (`aria-expanded`)."
9658
+ },
9659
+ {
9660
+ "name": "toggle",
9661
+ "signature": "toggle(): Promise<void>",
9662
+ "params": [],
9663
+ "returns": "Promise<void>",
9664
+ "doc": "Flips the sidebar with a click."
9665
+ }
9666
+ ],
9667
+ "slots": [],
9668
+ "references": [],
9669
+ "usedBy": [],
9670
+ "since": "0.3.2",
9671
+ "signature": "class BfSidebarToggleHarness extends ComponentHarness"
9672
+ },
9431
9673
  {
9432
9674
  "name": "BfSidePanelHarness",
9433
9675
  "slug": "bf-side-panel-harness",
@@ -9532,7 +9774,7 @@
9532
9774
  "entryPoint": "@bitforgehq/ui/testing",
9533
9775
  "file": "testing/harnesses/table.harness.ts",
9534
9776
  "summary": "Test harness for the data table: reads rows and cells, sorts and selects rows",
9535
- "description": "Harness for `bf-table`. It works by column header text and row position, so a test never needs\nthe column keys or the row keys (except `getSort()`, which returns the column's `key`, as the\n`sort` model holds it).",
9777
+ "description": "Harness for `bf-table`. It works by column header text and row position, so a test never needs\nthe column keys or the row keys (except `getSort()`, which returns the column's `key`, as the\n`sort` model holds it). It reads and drives a stacked table (`stack`) the same way.",
9536
9778
  "members": [],
9537
9779
  "methods": [
9538
9780
  {
package/ai/UI_GUIDE.md CHANGED
@@ -117,7 +117,12 @@ the widgets style their native controls themselves.
117
117
  Optional per-app overrides go after the import. A brand colour is a set: override
118
118
  `--color-primary` together with `--color-primary-text` (primary as text, ≥ 4.5:1 on `bg` and
119
119
  `surface`) and the rest of the `--color-primary-*` tokens, in `@theme` and in `.dark`; the docs
120
- site's theme configurator derives the whole contrast-checked set from one colour.
120
+ site's theme configurator derives the whole contrast-checked set from one colour. From a
121
+ terminal, `bunx bf-ui theme '<hex>' [--dark '<hex>']` prints the set (`primary`, its hover,
122
+ pressed, soft and strong steps, `on-primary`, `primary-text`, `selected`, `ring`) as a `@theme`
123
+ and a `.dark` block with each contrast pair's ratio, and exits 1 naming the pairs below their
124
+ minimum and a shade that passes; paste its output, never hand-pick the steps. Example (the
125
+ Meadow Sage green): `bunx bf-ui theme '#238652' --dark '#5cc48a'`.
121
126
  `--radius-control` (`9999px`, pills) reshapes buttons, chips, badges, segmented, the tab bar, the
122
127
  action bar and toggles at once:
123
128
 
@@ -138,16 +143,17 @@ Render library components only in the browser, for example in a `@defer` block o
138
143
  route. Support will be decided before 1.0.
139
144
 
140
145
  The one server-side check is a render smoke, not a support test: the library repository's
141
- `tools/ssr-smoke.ts` renders 42 fixtures with `renderApplication()` (every root widget, `bf-icon`,
146
+ `tools/ssr-smoke.ts` renders 43 fixtures with `renderApplication()` (every root widget, `bf-icon`,
142
147
  `bf-number-input`, a `/charts` line chart, a `/mobile` page with `provideBfShell()`, `bf-page`,
143
148
  `bf-header` and `bf-content`, `bf-tabs` with `bf-stack-outlet` under the router, and an `/overlay`
144
- dialog), and all of them render without an error. 27 of them render identically twice. The other 15
149
+ dialog), and all of them render without an error. 27 of them render identically twice. The other 16
145
150
  number their ids differently on every render, from a module-level counter, so they could not
146
- hydrate: `bf-calendar`, `bf-checkbox`, `bf-input`, `bf-radio-group`, `bf-segmented`, `bf-select`,
147
- `bf-sidebar`, `bf-tab-group`, `bf-table`, `bf-textarea`, `bf-theme-switch`, `bf-toggle`,
148
- `bf-line-chart`, `bf-number-input` and the `/overlay` dialog. Hydration itself is still not
149
- supported or tested, and more stands in its way: `/mobile` writes to `<html>` and adds a keyboard
150
- probe to `<body>`, and an `/overlay` dialog renders outside the app root and marks it inert.
151
+ hydrate: `bf-app-frame`, `bf-calendar`, `bf-checkbox`, `bf-input`, `bf-radio-group`, `bf-segmented`,
152
+ `bf-select`, `bf-sidebar`, `bf-tab-group`, `bf-table`, `bf-textarea`, `bf-theme-switch`,
153
+ `bf-toggle`, `bf-line-chart`, `bf-number-input` and the `/overlay` dialog. Hydration itself is still
154
+ not supported or tested, and more stands in its way: `/mobile` writes to `<html>` and adds a
155
+ keyboard probe to `<body>`, and an `/overlay` dialog renders outside the app root and marks it
156
+ inert.
151
157
 
152
158
  ## Keeping this guide in sync
153
159
 
@@ -335,8 +341,19 @@ protected readonly colorScheme = inject(BfColorSchemeService);
335
341
  `bf-tabs`, `bf-back-button` and `bf-header` are Capacitor shell parts. A desktop web app (no
336
342
  `provideBfShell()`) uses their desktop counterparts from `@bitforgehq/ui`:
337
343
 
338
- - **Navigation:** `bf-sidebar` (groups in `bf-nav-group`, `[(collapsed)]` for an icon rail; the
339
- app owns the toggle button) or `bf-top-nav` for a few sections, holding
344
+ - **App frame:** wrap the whole app in `<bf-app-frame>` (never build the frame by hand): the
345
+ `bf-sidebar` in `bfSlot="sidebar"` (sticky, full height, from `md` up), and below the
346
+ breakpoint a header bar with `bfSlot="brand"`, `bfSlot="menu"` (a `bf-menu-button` of
347
+ `a[bfMenuItem]` links from the same destinations array) and `bfSlot="actions"` (the theme
348
+ menu); the router outlet is the content. It brings the skip link and the focusable main
349
+ landmark; `breakpoint` and `contentWidth` adjust it. Desktop only: never inside
350
+ `provideBfShell()`.
351
+ - **Sidebar collapse:** `<bf-sidebar-toggle>` (`/overlay`, icons in `bfSlot="collapse-icon"` and
352
+ `bfSlot="expand-icon"`) in the sidebar footer beside the icon-only theme menu; with
353
+ `provideBfSidebarState()` bind `[(collapsed)]="sidebarState.collapsed"`
354
+ (`BfSidebarStateService`) so the choice is remembered.
355
+ - **Navigation:** `bf-sidebar` (groups in `bf-nav-group`, `[(collapsed)]` for an icon rail) or
356
+ `bf-top-nav` for a few sections, holding
340
357
  `<a bfNavItem routerLink="…" routerLinkActive ariaCurrentWhenActive="page">` links with an icon
341
358
  in `bfSlot="icon"` and an optional `[badge]` count. The router sets `aria-current="page"` and
342
359
  the item shows it. Drive the sidebar, a below-`md` `bf-menu-button` of `a[bfMenuItem]` links and,
@@ -354,8 +371,9 @@ protected readonly colorScheme = inject(BfColorSchemeService);
354
371
  controls default to `sm`, and `status` is a polite live region for the result count.
355
372
  - **Table:** `<bf-table [rows] [columns] [(sort)] selectable [(selection)]>` with
356
373
  `ng-template[bfTableCell]` for rich cells and `bfSlot="empty"` for the empty state; the app
357
- sorts the rows in a `computed()`, and a selection shows a `bf-action-bar`. Below `md` or on a
358
- coarse pointer render the same rows as `bf-list-item`s.
374
+ sorts the rows in a `computed()`, and a selection shows a `bf-action-bar`. `stack="narrow"`
375
+ (below 36rem of its own width) or `stack="coarse"` turns each row into a label/value tile with
376
+ a sort and select bar; in the Capacitor shell use `bf-list-item` rows instead.
359
377
  - **Details:** `bf-description-list` of `div[bfDescriptionItem]` label/value pairs, and `bf-stat`
360
378
  figure tiles in a grid; both work in both shells.
361
379
  - **Cards in a grid:** the grid stretches each `bf-card` and its `bfSlot="footer"` sits on the
@@ -384,6 +402,12 @@ protected readonly colorScheme = inject(BfColorSchemeService);
384
402
  `bf-sparkline` inside a `bf-chart-card` (title, period slot, loading, empty and error states),
385
403
  themed by the `chart-1`…`chart-8` tokens. Every chart takes a `label` and offers its data as a
386
404
  table.
405
+ - **Surfaces:** one surface style per level. A raised `bf-card` holds sunken tiles
406
+ (`bf-stat variant="tile"`), never an outlined surface (`variant="card"` or a `flat` card) inside
407
+ a raised one; next to raised cards on a dashboard, use raised cards.
408
+ - **Empty pages:** a 404, "no access" or "nothing here yet" page is a `bf-empty-state` with
409
+ `headingLevel` 1, centred in the content area, with the main action and an optional link.
410
+ - **Toolbar on a phone:** `bf-toolbar` keeps its status and actions together on the last row.
387
411
 
388
412
  ## Overlays (`@bitforgehq/ui/overlay`)
389
413
 
@@ -1228,27 +1252,29 @@ notes.
1228
1252
 
1229
1253
  ### Navigation
1230
1254
 
1231
- | Component | Entry point | Purpose |
1232
- | ---------------------- | ----------------------- | ------------------------------------------------------------------------------------------- |
1233
- | `bf-back-button` | `@bitforgehq/ui/mobile` | Header back button that pops the page stack or falls back to a default route |
1234
- | `bf-breadcrumb` | `@bitforgehq/ui` | Desktop breadcrumb trail of links to the current page, with optional collapsing |
1235
- | `li[bfBreadcrumbItem]` | `@bitforgehq/ui` | One level of a breadcrumb trail: a link, or the current page as text |
1236
- | `bf-content` | `@bitforgehq/ui/mobile` | Page's single scroll container that keeps focused fields clear of the soft keyboard |
1237
- | `bf-header` | `@bitforgehq/ui/mobile` | Page header toolbar with a large title that collapses into the toolbar on scroll |
1238
- | `bf-nav-group` | `@bitforgehq/ui` | A titled group of sidebar links with a small heading that names it |
1239
- | `a[bfNavItem]` | `@bitforgehq/ui` | Navigation link for a sidebar or top nav, with icon, label and count |
1240
- | `bf-page` | `@bitforgehq/ui/mobile` | Frame for one stacked mobile screen that supplies lifecycle events and shared page state |
1241
- | `bf-page-header` | `@bitforgehq/ui` | Desktop page header with breadcrumb, title, subtitle, badges and a row of actions |
1242
- | `bf-pagination` | `@bitforgehq/ui` | Previous, next and numbered page buttons for a long list or table |
1243
- | `bf-refresher` | `@bitforgehq/ui/mobile` | Pull-to-refresh indicator with haptic feedback, native iOS bounce and an Android-style pull |
1244
- | `bf-sidebar` | `@bitforgehq/ui` | Vertical app navigation with brand, grouped links, footer and an icon rail mode |
1245
- | `bf-stack-outlet` | `@bitforgehq/ui/mobile` | Router outlet keeping a live page stack with slide transitions and iOS swipe back |
1246
- | `bf-tab` | `@bitforgehq/ui` | One tab bar destination with an icon, a label and an optional count badge |
1247
- | `bf-tab-bar` | `@bitforgehq/ui` | Floating pill tab bar for mobile apps with arrow-key selection and count badges |
1248
- | `bf-tab-group` | `@bitforgehq/ui` | In-page tabs that switch between content panels, with keyboard navigation and badges |
1249
- | `bf-tab-panel` | `@bitforgehq/ui` | One tab of a tab group with its label, badge, icon and lazy content |
1250
- | `bf-tabs` | `@bitforgehq/ui/mobile` | Tabs page with one navigation stack per tab, wiring the tab bar to the router |
1251
- | `bf-top-nav` | `@bitforgehq/ui` | Horizontal app navigation, a wrapping row of links for apps with few sections |
1255
+ | Component | Entry point | Purpose |
1256
+ | ---------------------- | ------------------------ | ------------------------------------------------------------------------------------------- |
1257
+ | `bf-app-frame` | `@bitforgehq/ui` | Desktop app frame with skip link, sticky sidebar, narrow header and main content |
1258
+ | `bf-back-button` | `@bitforgehq/ui/mobile` | Header back button that pops the page stack or falls back to a default route |
1259
+ | `bf-breadcrumb` | `@bitforgehq/ui` | Desktop breadcrumb trail of links to the current page, with optional collapsing |
1260
+ | `li[bfBreadcrumbItem]` | `@bitforgehq/ui` | One level of a breadcrumb trail: a link, or the current page as text |
1261
+ | `bf-content` | `@bitforgehq/ui/mobile` | Page's single scroll container that keeps focused fields clear of the soft keyboard |
1262
+ | `bf-header` | `@bitforgehq/ui/mobile` | Page header toolbar with a large title that collapses into the toolbar on scroll |
1263
+ | `bf-nav-group` | `@bitforgehq/ui` | A titled group of sidebar links with a small heading that names it |
1264
+ | `a[bfNavItem]` | `@bitforgehq/ui` | Navigation link for a sidebar or top nav, with icon, label and count |
1265
+ | `bf-page` | `@bitforgehq/ui/mobile` | Frame for one stacked mobile screen that supplies lifecycle events and shared page state |
1266
+ | `bf-page-header` | `@bitforgehq/ui` | Desktop page header with breadcrumb, title, subtitle, badges and a row of actions |
1267
+ | `bf-pagination` | `@bitforgehq/ui` | Previous, next and numbered page buttons for a long list or table |
1268
+ | `bf-refresher` | `@bitforgehq/ui/mobile` | Pull-to-refresh indicator with haptic feedback, native iOS bounce and an Android-style pull |
1269
+ | `bf-sidebar` | `@bitforgehq/ui` | Vertical app navigation with brand, grouped links, footer and an icon rail mode |
1270
+ | `bf-sidebar-toggle` | `@bitforgehq/ui/overlay` | Icon button inside a sidebar that collapses it to an icon rail and back |
1271
+ | `bf-stack-outlet` | `@bitforgehq/ui/mobile` | Router outlet keeping a live page stack with slide transitions and iOS swipe back |
1272
+ | `bf-tab` | `@bitforgehq/ui` | One tab bar destination with an icon, a label and an optional count badge |
1273
+ | `bf-tab-bar` | `@bitforgehq/ui` | Floating pill tab bar for mobile apps with arrow-key selection and count badges |
1274
+ | `bf-tab-group` | `@bitforgehq/ui` | In-page tabs that switch between content panels, with keyboard navigation and badges |
1275
+ | `bf-tab-panel` | `@bitforgehq/ui` | One tab of a tab group with its label, badge, icon and lazy content |
1276
+ | `bf-tabs` | `@bitforgehq/ui/mobile` | Tabs page with one navigation stack per tab, wiring the tab bar to the router |
1277
+ | `bf-top-nav` | `@bitforgehq/ui` | Horizontal app navigation, a wrapping row of links for apps with few sections |
1252
1278
 
1253
1279
  ### Overlays
1254
1280