@bitforgehq/ui 0.4.0 → 0.5.0-next.4

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 (87) hide show
  1. package/CHANGELOG.md +350 -0
  2. package/README.md +12 -12
  3. package/ai/MANIFEST.index.json +74 -2
  4. package/ai/MANIFEST.json +3507 -1763
  5. package/ai/REFERENCE.index.json +189 -0
  6. package/ai/REFERENCE.json +1617 -139
  7. package/ai/UI_GUIDE.md +70 -20
  8. package/ai/components/app-frame.md +1 -1
  9. package/ai/components/badge.md +41 -1
  10. package/ai/components/chat-composer.md +357 -0
  11. package/ai/components/chat-message.md +251 -0
  12. package/ai/components/chat-thread.md +521 -0
  13. package/ai/components/checkbox.md +7 -0
  14. package/ai/components/combobox.md +12 -0
  15. package/ai/components/date-input.md +9 -0
  16. package/ai/components/datetime-input.md +2 -0
  17. package/ai/components/field.md +10 -0
  18. package/ai/components/file-input.md +498 -0
  19. package/ai/components/header.md +1 -1
  20. package/ai/components/image-input.md +368 -0
  21. package/ai/components/image-tile.md +452 -0
  22. package/ai/components/image-viewer.md +309 -0
  23. package/ai/components/media-card.md +478 -0
  24. package/ai/components/menu-button.md +67 -9
  25. package/ai/components/menu.md +10 -0
  26. package/ai/components/multi-combobox.md +7 -0
  27. package/ai/components/nav-group.md +10 -3
  28. package/ai/components/nav-item.md +11 -2
  29. package/ai/components/number-input.md +2 -7
  30. package/ai/components/pagination.md +59 -11
  31. package/ai/components/popover.md +13 -0
  32. package/ai/components/radio-group.md +6 -0
  33. package/ai/components/segmented.md +14 -2
  34. package/ai/components/select.md +100 -42
  35. package/ai/components/sidebar-toggle.md +16 -9
  36. package/ai/components/sidebar.md +41 -13
  37. package/ai/components/tab-group.md +18 -2
  38. package/ai/components/table.md +17 -2
  39. package/ai/components/theme-menu-button.md +1 -0
  40. package/ai/components/time-input.md +2 -0
  41. package/ai/components/toolbar.md +7 -18
  42. package/ai/reference/bf-chat-composer-harness.md +64 -0
  43. package/ai/reference/bf-chat-message-role.md +27 -0
  44. package/ai/reference/bf-chat-message-status.md +28 -0
  45. package/ai/reference/bf-fade-in-directive.md +18 -0
  46. package/ai/reference/bf-file-input-harness.md +67 -0
  47. package/ai/reference/bf-file-input-variant.md +27 -0
  48. package/ai/reference/bf-image-input-harness.md +67 -0
  49. package/ai/reference/bf-image-input-shape.md +27 -0
  50. package/ai/reference/bf-image-tile-aspect.md +28 -0
  51. package/ai/reference/bf-image-tile-caption-placement.md +27 -0
  52. package/ai/reference/bf-image-tile-harness.md +70 -0
  53. package/ai/reference/bf-image-viewer-actions-context.md +29 -0
  54. package/ai/reference/bf-image-viewer-harness.md +87 -0
  55. package/ai/reference/bf-image-viewer-image.md +25 -0
  56. package/ai/reference/bf-image-viewer-options.md +33 -0
  57. package/ai/reference/bf-image-viewer-origin.md +23 -0
  58. package/ai/reference/bf-image-viewer-ref.md +55 -0
  59. package/ai/reference/bf-image-viewer-service.md +32 -0
  60. package/ai/reference/bf-labels.md +107 -75
  61. package/ai/reference/bf-media-card-aspect.md +28 -0
  62. package/ai/reference/bf-media-card-harness.md +66 -0
  63. package/ai/reference/bf-motion-list-directive.md +22 -0
  64. package/ai/reference/bf-pagination-position-label.md +22 -0
  65. package/ai/reference/bf-prose-directive.md +16 -0
  66. package/ai/reference/bf-rejected-file-reason.md +28 -0
  67. package/ai/reference/bf-rejected-file.md +28 -0
  68. package/ai/reference/bf-select-harness.md +14 -5
  69. package/ai/reference/bf-select-option.md +2 -0
  70. package/ai/reference/bf-size.md +3 -0
  71. package/ai/reference/bf_table_sort_select.md +23 -0
  72. package/ai/reference/provide-bf-labels.md +11 -3
  73. package/ai/reference/provide-bf-select.md +20 -0
  74. package/bin/bf-ui.js +1 -0
  75. package/fesm2022/bitforgehq-ui-overlay.mjs +2660 -594
  76. package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
  77. package/fesm2022/bitforgehq-ui-testing.mjs +414 -10
  78. package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
  79. package/fesm2022/bitforgehq-ui.mjs +8056 -3993
  80. package/fesm2022/bitforgehq-ui.mjs.map +1 -1
  81. package/llms-full.txt +4923 -132
  82. package/llms.txt +36 -1
  83. package/package.json +1 -1
  84. package/theme.css +60 -0
  85. package/types/bitforgehq-ui-overlay.d.ts +695 -7
  86. package/types/bitforgehq-ui-testing.d.ts +517 -6
  87. package/types/bitforgehq-ui.d.ts +1699 -181
package/ai/UI_GUIDE.md CHANGED
@@ -147,17 +147,17 @@ Render library components only in the browser, for example in a `@defer` block o
147
147
  route. Support will be decided before 1.0.
148
148
 
149
149
  The one server-side check is a render smoke, not a support test: the library repository's
150
- `tools/ssr-smoke.ts` renders 45 fixtures with `renderApplication()` (every root widget, `bf-icon`,
151
- `bf-number-input`, a `/charts` line chart, a `/mobile` page with `provideBfShell()`, `bf-page`,
152
- `bf-header` and `bf-content`, `bf-tabs` with `bf-stack-outlet` under the router, and an `/overlay`
153
- dialog), and all of them render without an error. 29 of them render identically twice. The other 16
154
- number their ids differently on every render, from a module-level counter, so they could not
155
- hydrate: `bf-app-frame`, `bf-calendar`, `bf-checkbox`, `bf-input`, `bf-radio-group`, `bf-segmented`,
156
- `bf-select`, `bf-sidebar`, `bf-tab-group`, `bf-table`, `bf-textarea`, `bf-theme-switch`,
157
- `bf-toggle`, `bf-line-chart`, `bf-number-input` and the `/overlay` dialog. Hydration itself is still
158
- not supported or tested, and more stands in its way: `/mobile` writes to `<html>` and adds a
159
- keyboard probe to `<body>`, and an `/overlay` dialog renders outside the app root and marks it
160
- inert.
150
+ `tools/ssr-smoke.ts` renders 50 fixtures with `renderApplication()` (every root widget, `bf-icon`,
151
+ `bf-number-input`, `bf-select` from `/overlay`, a `/charts` line chart, a `/mobile` page with
152
+ `provideBfShell()`, `bf-page`, `bf-header` and `bf-content`, `bf-tabs` with `bf-stack-outlet` under
153
+ the router, and an `/overlay` dialog), and all of them render without an error. 32 of them render
154
+ identically twice. The other 18 number their ids differently on every render, from a module-level
155
+ counter, so they could not hydrate: `bf-app-frame`, `bf-calendar`, `bf-checkbox`, `bf-file-input`, `bf-image-input`, `bf-input`,
156
+ `bf-radio-group`, `bf-segmented`, `bf-select`, `bf-sidebar`, `bf-tab-group`, `bf-table`,
157
+ `bf-textarea`, `bf-theme-switch`, `bf-toggle`, `bf-line-chart`, `bf-number-input` and the `/overlay`
158
+ dialog. Hydration itself is still not supported or tested, and more stands in its way: `/mobile`
159
+ writes to `<html>` and adds a keyboard probe to `<body>`, and an `/overlay` dialog renders outside
160
+ the app root and marks it inert.
161
161
 
162
162
  ## Keeping this guide in sync
163
163
 
@@ -176,6 +176,10 @@ or out of date — wire it into CI so agent guidance never drifts from the insta
176
176
  - Number field: `import { BfNumberInputComponent } from '@bitforgehq/ui/number-input';`. It is
177
177
  the one widget in its own entry point, because it loads Angular Forms (`@angular/forms`, a
178
178
  required peer); `@bitforgehq/ui` itself loads no forms code, so an app without forms ships none.
179
+ - Select: `import { BfSelectComponent } from '@bitforgehq/ui/overlay';` (needs `@angular/cdk`;
180
+ `BfSelectOption` is also exported from `@bitforgehq/ui`). Under a mouse it opens a styled list,
181
+ under touch it renders the native `<select>`. Add `provideBfSelect()` to the app's providers so a
182
+ stacked `bf-table`'s "Sort by" control is the styled select too (without it, the native one).
179
183
  - Charts: `import { BfLineChartComponent } from '@bitforgehq/ui/charts';` (also `BfBarChartComponent`,
180
184
  `BfDonutChartComponent`, `BfSparklineComponent`, `BfChartCardComponent`). Our own SVG charts,
181
185
  themed with the `--color-chart-1` … `--color-chart-8` tokens; pass `series` (`{ name, values }`) and
@@ -186,7 +190,7 @@ or out of date — wire it into CI so agent guidance never drifts from the insta
186
190
  `border-border`, `rounded-field`, `shadow-raised`, `text-body`). Never raw palette classes and
187
191
  never `dark:` variants; put `dark` on `<html>` and the theme re-colors everything.
188
192
  - Forms: `bf-input`, `bf-number-input`, `bf-textarea`, `bf-select`, `bf-checkbox`, `bf-toggle`,
189
- `bf-radio-group` and `bf-segmented` expose `value` / `checked` as `model()` and implement
193
+ `bf-radio-group`, `bf-segmented`, `bf-file-input` and `bf-image-input` expose `value` / `checked` as `model()` and implement
190
194
  Signal Forms' `FormValueControl` / `FormCheckboxControl`. Bind either `[formField]="form.field"` or
191
195
  `[(value)]` / `[(checked)]`, never both. Import `FormField`, `FormRoot`, `form` and the
192
196
  validators from `@angular/forms/signals`. The first error shows once the field is touched: its
@@ -204,6 +208,14 @@ or out of date — wire it into CI so agent guidance never drifts from the insta
204
208
  (`number | null`, `null` while empty; `min()`/`max()` reach the native input; unreadable text is
205
209
  a `parse` error on the bound field, blocking `submit()`), never to a `string` model on
206
210
  `bf-input type="number"`.
211
+ - Files: `bf-file-input` binds a `File[]` (`multiple` for several; `variant="dropzone"` for a drop
212
+ area) and `bf-image-input` a `File | null` (`[(src)]` shows the saved picture; Remove sets both
213
+ to null). Both only pick: upload from the form's `submission`. `accept`, `maxSize` and
214
+ `maxFiles` refuse a file without making the field invalid; read refusals from `(rejected)`
215
+ (`BfRejectedFile[]`). `required()` does not count an empty `File[]` as empty: add
216
+ `validate(p.files, ({ value }) => value().length === 0 ? requiredError() : undefined)`. For an
217
+ image with a `src`, give the marker with `metadata(p.avatar, REQUIRED, () => true)` and a
218
+ `validate()` that counts `src` too.
207
219
  - Reactive Forms work through Angular 22's built-in interop: `[formControl]` and
208
220
  `formControlName` bind every form widget above with no `ControlValueAccessor` (never add one).
209
221
  Value, touched, dirty, validity, pending, disabled, required and errors flow into the widget;
@@ -242,7 +254,7 @@ you can guess a binding without looking it up:
242
254
  spinner, tab bar, progress or icon); every component that renders a native input, select or
243
255
  textarea (checkbox and toggle included) also takes `ariaLabel` and `ariaLabelledby` for when no
244
256
  visible `label` is shown; a built-in sub-control's name is `<part>Label` (`dismissLabel`,
245
- `closeLabel`, `actionLabel`); `alt` only on `bf-avatar`.
257
+ `closeLabel`, `actionLabel`); `alt` only on `bf-avatar`, `bf-media-card` and `bf-image-tile`.
246
258
  - Types: every union or object type of an input, model, output, `Bf…Options`/`Bf…Config` property
247
259
  or an interface they name is a named type exported from the same entry
248
260
  (`BfToggleLabelPosition`, `BfRefresherRefreshEvent`, `BfDialogRole`, …), so type your own state
@@ -264,7 +276,8 @@ toolbar or a German settings page); a key you leave out keeps the parent's value
264
276
 
265
277
  - `provideBfDefaults({ size, fieldSize, controlSize })` sets the height step (`BfSize`: `sm` 32,
266
278
  `md` 40, `lg` 48 px) a widget uses when its own `size` input is unset. `fieldSize` covers the
267
- text fields (`bf-input`, `bf-number-input`, `bf-select`, `bf-textarea`), `controlSize` the
279
+ text fields (`bf-input`, `bf-number-input`, `bf-select`, `bf-textarea`, and the file pickers
280
+ `bf-file-input`, `bf-image-input`), `controlSize` the
268
281
  actions and choice controls (`bf-button`, `bf-icon-button`, `bf-chip`, `bf-segmented`,
269
282
  `bf-checkbox`, `bf-toggle`, `bf-radio-group`); `size` sets both, and an explicit
270
283
  `fieldSize`/`controlSize` wins over it. The library default is compact `md` for both (desktop
@@ -386,8 +399,28 @@ protected readonly colorScheme = inject(BfColorSchemeService);
386
399
  (text with inline links in the content, a small action in `bfSlot="end"`) for a record's
387
400
  history or an audit trail, in the order the app gives; times follow `LOCALE_ID` (`timeFormat`,
388
401
  `timeText` for a relative time). Not for navigation or steps.
402
+ - **Chat:** a `bf-chat-thread` (give it `class="flex-1"` or a height) of `bf-chat-message`
403
+ elements (`role` `user` or `assistant`, `author`, `streaming`, `status`) above a
404
+ `bf-chat-composer` from `/overlay` (`[(value)]="draft"`, `[running]="replying()"`,
405
+ `(sent)="ask($event)"`, `(stopped)="cancel()"`). A message's content is the app's rendered,
406
+ sanitised markdown, styled by the body's `bfProse`; cards and chips go in `bfSlot="content"`,
407
+ Retry in `bfSlot="action"`; the composer's icons go in `bfSlot="send-icon"` and
408
+ `bfSlot="stop-icon"`, extra controls in `start` and `end`. The thread follows new text, shows
409
+ "Jump to latest" when scrolled up and is `aria-busy` while a message streams; never scroll or
410
+ animate it yourself. Set `streaming` false when the reply ends, stops or fails.
389
411
  - **Cards in a grid:** the grid stretches each `bf-card` and its `bfSlot="footer"` sits on the
390
412
  bottom edge, so footers line up; put a footer's parts in one element.
413
+ - **Pictures that are slow or can fail:** `bf-media-card` owns the picture (`[src]`, `alt`,
414
+ `[loading]`, `loadingLabel="Drawing…"`, `[failed]`): a skeleton while it loads (or while `src`
415
+ is empty and `loading`), a fade when it arrives, a message and `bfSlot="failed-action"` (Retry)
416
+ when it fails; retry with a changed `src`. Never project your own `<img>` for the main picture.
417
+ Clickable like `bf-card` (a `bfAction` in the title); `selectable` with `[(selected)]` and
418
+ `label` for a pick-several grid.
419
+ - **Thumbnail grids:** `bf-image-tile` (`[src]`, `alt`, `aspect`, `captionPlacement="overlay"`
420
+ for the title on a gradient over the picture), with a `bfAction` link or button in its title
421
+ heading, `bfSlot="meta"` for a date or count, `bfSlot="badge"` for a `bf-badge` and
422
+ `[selected]` for the current one. Put tiles straight into a CSS grid: they stretch to the row
423
+ and keep two title lines, so a row lines up; give every tile a meta line or none.
391
424
  - **Rows on a card:** `<bf-list [inset]="false" divided class="-mx-card">`, flush to the card's
392
425
  edges with lines between the rows.
393
426
  - **Theme switch:** `<bf-theme-switch [(value)]="colorScheme.scheme" />` (or
@@ -399,14 +432,17 @@ protected readonly colorScheme = inject(BfColorSchemeService);
399
432
  - **Paging and loading:** `<bf-pagination [(page)]="page" [total]="count" [pageSize]="20">` under a
400
433
  `bf-table` (the app slices its rows); `bf-skeleton` (`shape` text/rect/circle, `lines`) as the
401
434
  placeholder while data loads, inside a region with `aria-busy="true"`.
435
+ - **Stepping through versions:** a compact `bf-pagination` with `[pageCount]`,
436
+ `[positionLabel]="(page, count) => …"` ("v3 of 7") and `previousLabel`/`nextLabel`; past four
437
+ options it replaces a `bf-segmented`.
402
438
  - **Dates and times:** `<bf-calendar [(value)]="date">` for an inline month view;
403
439
  `bf-date-input`, `bf-time-input` and `bf-datetime-input` (`/overlay`) for a field: typed parts
404
440
  in locale order plus a calendar or time-list popover (`picker="none"` for typed only, `native`
405
441
  for the platform picker on touch). Values are ISO strings (`'2026-10-28'`, `'14:30'`,
406
442
  `'2026-10-28T14:30'`); month and day names come from `Intl` in the document's language.
407
443
  - **Picking from many options:** `bf-combobox` (`/overlay`) for one value with search, groups and
408
- server search (`queried`, `loading`); `bf-multi-combobox` for tags. `bf-select` stays the choice
409
- for a short fixed list.
444
+ server search (`queried`, `loading`); `bf-multi-combobox` for tags. `bf-select` (`/overlay`
445
+ too) stays the choice for a short fixed list.
410
446
  - **Dashboards:** `bf-stat` with `delta`, `trend` and `trendIntent` for a figure and its change;
411
447
  `@bitforgehq/ui/charts` for `bf-line-chart`, `bf-bar-chart`, `bf-donut-chart` and
412
448
  `bf-sparkline` inside a `bf-chart-card` (title, period slot, loading, empty and error states),
@@ -462,6 +498,12 @@ injector? })` stacks up to three cards bottom-right (configurable with
462
498
  - **Side panel:** `BfSidePanelService.open(component | template, { width, modal, data })` slides a
463
499
  full-height `bf-side-panel` in from the inline end: a record's details or edit form beside the
464
500
  list. `modal: false` keeps the page usable. In the Capacitor shell use `bf-sheet`/`bf-modal`.
501
+ - **Image viewer:** `BfImageViewerService.open(options)` with `images` (each
502
+ `{ src, label, caption? }`), `index`, `origin`, `actions` and `injector` shows one image full
503
+ screen over a dark scrim and steps through the set (buttons, arrow keys, Home/End, wrapping);
504
+ pass the clicked thumbnail as `origin` (or `(i) => thumbnails[i]`) so it zooms out of it and
505
+ back. Actions go in an `ng-template` (context `{ $implicit: image, index, ref }`). It returns a
506
+ `BfImageViewerRef` (`index()`, `next()`, `previous()`, `goTo()`, `close()`, `afterClosed`).
465
507
  - **`closeOnNavigation`:** every overlay (`BfDialogConfig`, `confirm()`, and the `/mobile` sheet,
466
508
  modal, action sheet and alert options) closes when the URL path changes by default (`true`);
467
509
  pass `closeOnNavigation: false` to keep one open across a navigation (e.g. a draft that must
@@ -993,7 +1035,7 @@ describe('bf-button in a consumer app (Vitest)', () => {
993
1035
 
994
1036
  ### 6. Drive a form widget through its harness
995
1037
 
996
- The entry ships CDK harnesses for the widgets whose markup is not obvious to drive: `BfSelectHarness`, `BfRadioGroupHarness`, `BfSegmentedHarness`, `BfDialogHarness`, `BfMenuHarness`, `BfMenuItemHarness`, `BfMenuButtonHarness`, `BfDesktopToastHarness`, `BfTableHarness`, `BfToggleHarness`, `BfPaginationHarness` `BfTabGroupHarness`, `BfCalendarHarness` and `BfChartHarness`. `with({ label })` finds a widget by its label. Load an in-page widget with `TestbedHarnessEnvironment.loader(fixture)`.
1038
+ The entry ships CDK harnesses for the widgets whose markup is not obvious to drive: `BfSelectHarness`, `BfRadioGroupHarness`, `BfSegmentedHarness`, `BfDialogHarness`, `BfMenuHarness`, `BfMenuItemHarness`, `BfMenuButtonHarness`, `BfDesktopToastHarness`, `BfTableHarness`, `BfToggleHarness`, `BfPaginationHarness` `BfTabGroupHarness`, `BfCalendarHarness`, `BfFileInputHarness`, `BfImageInputHarness`, `BfMediaCardHarness`, `BfImageTileHarness`, `BfChatComposerHarness` and `BfChartHarness`. `with({ label })` finds a widget by its label. Load an in-page widget with `TestbedHarnessEnvironment.loader(fixture)`. jsdom has no `URL.createObjectURL`, so after `addFile()` `BfImageInputHarness.hasPreview()` stays false: assert on the bound value, or stub `URL.createObjectURL` to read the preview.
997
1039
 
998
1040
  `select-harness.spec.ts`:
999
1041
 
@@ -1006,7 +1048,7 @@ import { Component, signal } from '@angular/core';
1006
1048
  import { TestBed } from '@angular/core/testing';
1007
1049
  import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
1008
1050
  import { FormField, form, required } from '@angular/forms/signals';
1009
- import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui';
1051
+ import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui/overlay';
1010
1052
  import { BfSelectHarness, provideBfTesting } from '@bitforgehq/ui/testing';
1011
1053
 
1012
1054
  @Component({
@@ -1063,7 +1105,7 @@ describe('BfSelectHarness in a consumer app (Vitest)', () => {
1063
1105
 
1064
1106
  ### 7. Drive a dialog from the document root
1065
1107
 
1066
- Dialogs, menus and toasts render in the CDK overlay container at the end of `<body>`, outside the fixture. Load their harnesses with `TestbedHarnessEnvironment.documentRootLoader(fixture)`.
1108
+ Dialogs, menus and toasts render in the CDK overlay container at the end of `<body>`, outside the fixture. Load their harnesses with `TestbedHarnessEnvironment.documentRootLoader(fixture)`; `BfImageViewerHarness` drives the image viewer the same way. jsdom never loads images: `BfMediaCardHarness`, `BfImageTileHarness` and `BfImageViewerHarness` fire the `load` and `error` events with `loadImage()` and `failImage()`.
1067
1109
 
1068
1110
  `dialog-harness.spec.ts`:
1069
1111
 
@@ -1220,6 +1262,7 @@ notes.
1220
1262
  | Component | Entry point | Purpose |
1221
1263
  | ------------------- | ----------------------------- | ------------------------------------------------------------------------------------------- |
1222
1264
  | `bf-calendar` | `@bitforgehq/ui` | Inline month calendar for picking one date, with month and year jumps |
1265
+ | `bf-chat-composer` | `@bitforgehq/ui/overlay` | Chat input that grows with the text and sends on Enter, or stops a reply |
1223
1266
  | `bf-checkbox` | `@bitforgehq/ui` | Rounded checkbox with an optional label that binds to Signal Forms |
1224
1267
  | `bf-chip` | `@bitforgehq/ui` | Toggleable filter chip with a check mark when selected, in tonal or ink style |
1225
1268
  | `bf-chip-group` | `@bitforgehq/ui` | Named row of chips that picks one like radios, or groups independent toggles |
@@ -1227,12 +1270,14 @@ notes.
1227
1270
  | `bf-date-input` | `@bitforgehq/ui/overlay` | Typed date field with a calendar popover, valued as an ISO date string |
1228
1271
  | `bf-datetime-input` | `@bitforgehq/ui/overlay` | Typed date and time field with a calendar and time list in one popover |
1229
1272
  | `bf-field` | `@bitforgehq/ui` | Label, help text and error text wrapper that wires ids for assistive tech |
1273
+ | `bf-file-input` | `@bitforgehq/ui` | File picker field that lists picked or dropped files and checks type, size and count |
1274
+ | `bf-image-input` | `@bitforgehq/ui` | Single-image picker with a round or square preview tile and replace or remove |
1230
1275
  | `bf-input` | `@bitforgehq/ui` | Single-line text field with label, help and error text, icon slots and Signal Forms binding |
1231
1276
  | `bf-multi-combobox` | `@bitforgehq/ui/overlay` | Text field that filters options as you type and picks several as tags |
1232
1277
  | `bf-number-input` | `@bitforgehq/ui/number-input` | Number field that binds number or null, with native min, max and step |
1233
1278
  | `bf-radio-group` | `@bitforgehq/ui` | Group of radio buttons for choosing one option, with optional per-option descriptions |
1234
1279
  | `bf-segmented` | `@bitforgehq/ui` | Segmented control for switching between two to four views, keyboard-navigable like radios |
1235
- | `bf-select` | `@bitforgehq/ui` | Native single-choice dropdown styled like the text field, with Signal Forms binding |
1280
+ | `bf-select` | `@bitforgehq/ui/overlay` | Single-choice dropdown with a styled list, native under touch, with Signal Forms binding |
1236
1281
  | `bf-textarea` | `@bitforgehq/ui` | Multi-line text field with label, help and error text, and Signal Forms binding |
1237
1282
  | `bf-theme-switch` | `@bitforgehq/ui` | Segmented System, Light and Dark switch bound two-way to the app colour scheme |
1238
1283
  | `bf-time-input` | `@bitforgehq/ui/overlay` | Typed time-of-day field with a popover list of times, valued as an ISO string |
@@ -1247,12 +1292,16 @@ notes.
1247
1292
  | `bf-bar-chart` | `@bitforgehq/ui/charts` | Bar chart of series over categories, grouped or stacked, vertical or horizontal |
1248
1293
  | `bf-card` | `@bitforgehq/ui` | Content surface with an optional header slot, in four visual styles from raised to flat |
1249
1294
  | `bf-chart-card` | `@bitforgehq/ui/charts` | Card around one chart with a title, period control and loading, empty and error states |
1295
+ | `bf-chat-message` | `@bitforgehq/ui` | One chat message, a user bubble or a full-width assistant reply that can stream |
1296
+ | `bf-chat-thread` | `@bitforgehq/ui` | Scrolling chat message log that follows new text and offers a jump to latest |
1250
1297
  | `div[bfDescriptionItem]` | `@bitforgehq/ui` | One label and value pair of a description list, label as term, content as value |
1251
1298
  | `bf-description-list` | `@bitforgehq/ui` | Native description list of label-value pairs, as divided rows or a tile grid |
1252
1299
  | `bf-donut-chart` | `@bitforgehq/ui/charts` | Donut chart of the parts of a whole, with a legend, tooltip and centre slot |
1300
+ | `bf-image-tile` | `@bitforgehq/ui` | Small card for thumbnail grids: a picture and title that act as one target |
1253
1301
  | `bf-line-chart` | `@bitforgehq/ui/charts` | Line chart of series over categories, with tooltip, legend and data table |
1254
1302
  | `bf-list` | `@bitforgehq/ui` | Container for list rows, inset by default with rounded corners on a warm background |
1255
1303
  | `bf-list-item` | `@bitforgehq/ui` | List row with leading slot, title, subtitle, trailing slot and an optional chevron |
1304
+ | `bf-media-card` | `@bitforgehq/ui` | Card with an image that loads, fades in or fails, plus a body and actions |
1256
1305
  | `bf-progress` | `@bitforgehq/ui` | Progress bar in two sizes and four tones for completion, quotas and unknown waits |
1257
1306
  | `bf-sparkline` | `@bitforgehq/ui/charts` | Tiny trend line with no axes or legend, for stats and table cells |
1258
1307
  | `bf-stat` | `@bitforgehq/ui` | One labelled figure with an optional icon tile and a supporting line |
@@ -1305,6 +1354,7 @@ notes.
1305
1354
  | `bf-alert-dialog` | `@bitforgehq/ui/mobile` | Centred confirmation dialog with a title, message and a row of role-styled buttons |
1306
1355
  | `bf-desktop-toast` | `@bitforgehq/ui/overlay` | Desktop notification card in a bottom corner stack of up to three |
1307
1356
  | `bf-dialog` | `@bitforgehq/ui/overlay` | Desktop modal dialog with a title, scrolling body, footer actions and focus trap |
1357
+ | `bf-image-viewer` | `@bitforgehq/ui/overlay` | Full-screen image viewer that zooms from a thumbnail and steps through a set |
1308
1358
  | `bf-menu` | `@bitforgehq/ui/overlay` | Keyboard-operable menu panel with items, checkbox and radio items, groups and submenus |
1309
1359
  | `bf-menu-button` | `@bitforgehq/ui/overlay` | Styled button that opens a menu, with outline, text, tonal and icon-only looks |
1310
1360
  | `bf-modal` | `@bitforgehq/ui/mobile` | Modal overlay shown as an inset card over the scaled-down page, or full screen |
@@ -157,7 +157,7 @@ Use `bf-app-frame` once, at the root of a desktop web app whose top-level destin
157
157
 
158
158
  ## Slots and layout
159
159
 
160
- Put the `bf-sidebar` in `bfSlot="sidebar"`, the app's name or logo in `bfSlot="brand"`, the narrow-screen `bf-menu-button` of `a[bfMenuItem]` router links in `bfSlot="menu"` and end actions (the theme menu, the account) in `bfSlot="actions"`; everything else is the page. From the breakpoint up the sidebar column sits at the inline start, `sticky top-0` and `h-dvh` tall, and the `bf-sidebar` inside fills it and scrolls its own destinations; the header bar is hidden. Below it the sidebar column is hidden and the header bar (`surface`, a bottom border, sticky at the top) shows the menu at the inline start, then the brand, and the actions at the end: the top app bar convention, and the menu comes first in the tab order too. The order is fixed, whatever order the slots have in your template. Keep the brand in the sidebar's own `header` slot too, for the wide layout.
160
+ Put the `bf-sidebar` in `bfSlot="sidebar"`, the app's name or logo in `bfSlot="brand"`, the narrow-screen `bf-menu-button` of `a[bfMenuItem]` router links in `bfSlot="menu"` and end actions (the theme menu, the account) in `bfSlot="actions"`; everything else is the page. From the breakpoint up the sidebar column sits at the inline start, `sticky top-0` and `h-dvh` tall, and the `bf-sidebar` inside fills it and scrolls its own destinations; the header bar is hidden. Below it the sidebar column is hidden and the header bar (`surface`, a bottom border, sticky at the top) shows the menu at the inline start, then the brand, and the actions at the end: the top app bar convention, and the menu comes first in the tab order too. The order is fixed, whatever order the slots have in your template. Keep the brand in the sidebar's own `header` slot too, for the wide layout. The sidebar column takes the sidebar's width, so when the sidebar collapses or expands the content column moves with its animation instead of jumping.
161
161
 
162
162
  ## Breakpoint and content width
163
163
 
@@ -91,6 +91,37 @@ import { BfBadgeComponent } from '@bitforgehq/ui';
91
91
  export class BadgeStrongExampleComponent {}
92
92
  ```
93
93
 
94
+ ## Icon
95
+
96
+ An icon in the `icon-start` slot sits before the text, at 12 px whatever the `bf-icon`'s own `size`, 4 px from the text, and the start padding tightens to match. Use it to repeat what the text says at a glance, a check on "Reference" or a cube on "3 versions", never instead of the text. A count pill does not show it.
97
+
98
+ ```ts
99
+ import { Component } from '@angular/core';
100
+ import { BfBadgeComponent } from '@bitforgehq/ui';
101
+ import { BfIconComponent } from '@bitforgehq/ui/icons';
102
+ import { Box, Check, Clock } from 'lucide';
103
+
104
+ /** Badges with a leading icon: the icon repeats the meaning, the text still says it. */
105
+ @Component({
106
+ selector: 'app-example-badge-icon',
107
+ imports: [BfBadgeComponent, BfIconComponent],
108
+ template: `
109
+ <div class="flex flex-wrap items-center gap-2">
110
+ <bf-badge variant="success"><bf-icon bfSlot="icon-start" [icon]="check" />Reference</bf-badge>
111
+ <bf-badge variant="info"><bf-icon bfSlot="icon-start" [icon]="box" />3 versions</bf-badge>
112
+ <bf-badge variant="warning" emphasis="strong"
113
+ ><bf-icon bfSlot="icon-start" [icon]="clock" />Queued</bf-badge
114
+ >
115
+ </div>
116
+ `,
117
+ })
118
+ export class BadgeIconExampleComponent {
119
+ protected readonly check = Check;
120
+ protected readonly box = Box;
121
+ protected readonly clock = Clock;
122
+ }
123
+ ```
124
+
94
125
  ## Count pill
95
126
 
96
127
  Setting `count` switches to the compact primary pill. Values above 99 render as "99+"; zero renders as "0".
@@ -154,11 +185,12 @@ Consumes tokens by variant, background then text:
154
185
  - `info`: `info-soft` / `info` (≥ 4.5:1 in both modes; it is not derived from the primary, so a custom brand colour leaves it unchanged)
155
186
  - Count pill (`count` set): `primary` / `on-primary`, ignoring `variant`
156
187
  - Strong (`emphasis="strong"`, label and count pill): `<variant>-strong` / `on-<variant>-strong` (`neutral-strong`, `success-strong`, `warning-strong`, `danger-strong`, `info-strong`, each with its `on-` text token; ≥ 4.5:1 in both modes and the fill ≥ 3:1 on `bg`, `surface` and `surface-sunken`); `primary` uses `primary` / `on-primary`. An app can retune `--color-danger-strong` and `--color-on-danger-strong` (and the others) in `@theme` and `.dark` like any status token
188
+ - Icon (`icon-start`): drawn in the text colour (`currentColor`), so it follows the variant and stays visible in forced colours
157
189
  - Shape (label and count pill): `rounded-control` (`--radius-control`, `9999px` by default).
158
190
 
159
191
  ## Accessibility
160
192
 
161
- A badge is a plain `<span>` with no role or ARIA attributes; its text is read as part of the surrounding content, so pair it with a label or context that says what it means rather than relying on color alone. A count pill with `countLabel` carries that context itself: a visually hidden (`sr-only`) span after the number, read as one phrase with it ("99+ unread messages"). A strong badge is louder, not more meaningful: its text ("Critical", "Kritisch") still says the state. In forced colours (Windows High Contrast) the fills give way to the system colours, so a strong badge draws a `CanvasText` outline and still reads as a filled pill next to a soft one.
193
+ A badge is a plain `<span>` with no role or ARIA attributes; a `bf-icon` in `icon-start` is decorative (`aria-hidden`) unless it has a `label`, so the text alone carries the meaning; its text is read as part of the surrounding content, so pair it with a label or context that says what it means rather than relying on color alone. A count pill with `countLabel` carries that context itself: a visually hidden (`sr-only`) span after the number, read as one phrase with it ("99+ unread messages"). A strong badge is louder, not more meaningful: its text ("Critical", "Kritisch") still says the state. In forced colours (Windows High Contrast) the fills give way to the system colours, so a strong badge draws a `CanvasText` outline and still reads as a filled pill next to a soft one.
162
194
 
163
195
  ## Inputs
164
196
 
@@ -171,6 +203,7 @@ A badge is a plain `<span>` with no role or ARIA attributes; its text is read as
171
203
 
172
204
  ## Slots
173
205
 
206
+ - `icon-start`: An icon before the text, drawn at 12 px; not shown on a count pill.
174
207
  - `default`: The badge text, shown when no `count` is set.
175
208
 
176
209
  ## Notes
@@ -190,6 +223,13 @@ A badge is a plain `<span>` with no role or ARIA attributes; its text is read as
190
223
 
191
224
  ## Migration notes
192
225
 
226
+ ### 0.5.0
227
+
228
+ New `icon-start` slot: `<bf-icon bfSlot="icon-start" … />` before the label text, drawn at 12 px
229
+ whatever the icon's `size`, 4 px from the text, with the start padding tightened to 8 px. It
230
+ takes the text colour, so it follows the variant and forced colours. A count pill does not show
231
+ it.
232
+
193
233
  ### 0.4.0
194
234
 
195
235
  New `emphasis` input (`BfBadgeEmphasis`: `soft`, the default, or `strong`). `strong` is a solid