@bitforgehq/ui 0.4.0 → 0.5.0-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +303 -0
- package/README.md +12 -12
- package/ai/MANIFEST.index.json +65 -2
- package/ai/MANIFEST.json +2221 -737
- package/ai/REFERENCE.index.json +161 -0
- package/ai/REFERENCE.json +1336 -27
- package/ai/UI_GUIDE.md +61 -20
- package/ai/components/app-frame.md +1 -1
- package/ai/components/chat-composer.md +357 -0
- package/ai/components/chat-message.md +251 -0
- package/ai/components/chat-thread.md +521 -0
- package/ai/components/checkbox.md +7 -0
- package/ai/components/combobox.md +12 -0
- package/ai/components/date-input.md +9 -0
- package/ai/components/datetime-input.md +2 -0
- package/ai/components/field.md +10 -0
- package/ai/components/file-input.md +498 -0
- package/ai/components/header.md +1 -1
- package/ai/components/image-input.md +368 -0
- package/ai/components/image-viewer.md +309 -0
- package/ai/components/media-card.md +478 -0
- package/ai/components/menu-button.md +2 -0
- package/ai/components/menu.md +10 -0
- package/ai/components/multi-combobox.md +7 -0
- package/ai/components/nav-group.md +10 -3
- package/ai/components/nav-item.md +11 -2
- package/ai/components/number-input.md +2 -7
- package/ai/components/popover.md +13 -0
- package/ai/components/radio-group.md +6 -0
- package/ai/components/segmented.md +14 -2
- package/ai/components/select.md +100 -42
- package/ai/components/sidebar-toggle.md +16 -9
- package/ai/components/sidebar.md +41 -13
- package/ai/components/tab-group.md +18 -2
- package/ai/components/table.md +17 -2
- package/ai/components/theme-menu-button.md +1 -0
- package/ai/components/time-input.md +2 -0
- package/ai/components/toolbar.md +7 -18
- package/ai/reference/bf-chat-composer-harness.md +64 -0
- package/ai/reference/bf-chat-message-role.md +27 -0
- package/ai/reference/bf-chat-message-status.md +28 -0
- package/ai/reference/bf-fade-in-directive.md +18 -0
- package/ai/reference/bf-file-input-harness.md +67 -0
- package/ai/reference/bf-file-input-variant.md +27 -0
- package/ai/reference/bf-image-input-harness.md +67 -0
- package/ai/reference/bf-image-input-shape.md +27 -0
- package/ai/reference/bf-image-viewer-actions-context.md +29 -0
- package/ai/reference/bf-image-viewer-harness.md +87 -0
- package/ai/reference/bf-image-viewer-image.md +25 -0
- package/ai/reference/bf-image-viewer-options.md +33 -0
- package/ai/reference/bf-image-viewer-origin.md +23 -0
- package/ai/reference/bf-image-viewer-ref.md +55 -0
- package/ai/reference/bf-image-viewer-service.md +32 -0
- package/ai/reference/bf-labels.md +105 -75
- package/ai/reference/bf-media-card-aspect.md +28 -0
- package/ai/reference/bf-media-card-harness.md +66 -0
- package/ai/reference/bf-motion-list-directive.md +22 -0
- package/ai/reference/bf-prose-directive.md +16 -0
- package/ai/reference/bf-rejected-file-reason.md +28 -0
- package/ai/reference/bf-rejected-file.md +28 -0
- package/ai/reference/bf-select-harness.md +14 -5
- package/ai/reference/bf-select-option.md +2 -0
- package/ai/reference/bf-size.md +3 -0
- package/ai/reference/bf_table_sort_select.md +23 -0
- package/ai/reference/provide-bf-labels.md +10 -3
- package/ai/reference/provide-bf-select.md +20 -0
- package/bin/bf-ui.js +1 -0
- package/fesm2022/bitforgehq-ui-overlay.mjs +2649 -606
- package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-testing.mjs +356 -10
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui.mjs +7531 -3989
- package/fesm2022/bitforgehq-ui.mjs.map +1 -1
- package/llms-full.txt +4151 -130
- package/llms.txt +31 -1
- package/package.json +1 -1
- package/theme.css +60 -0
- package/types/bitforgehq-ui-overlay.d.ts +684 -7
- package/types/bitforgehq-ui-testing.d.ts +434 -6
- package/types/bitforgehq-ui.d.ts +1473 -182
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
|
|
151
|
-
`bf-number-input`, a `/charts` line chart, a `/mobile` page with
|
|
152
|
-
`bf-header` and `bf-content`, `bf-tabs` with `bf-stack-outlet` under
|
|
153
|
-
dialog), and all of them render without an error.
|
|
154
|
-
number their ids differently on every render, from a module-level
|
|
155
|
-
hydrate: `bf-app-frame`, `bf-calendar`, `bf-checkbox`, `bf-input`, `bf-
|
|
156
|
-
`bf-
|
|
157
|
-
`bf-toggle`, `bf-line-chart`, `bf-number-input` and the `/overlay`
|
|
158
|
-
not supported or tested, and more stands in its way: `/mobile`
|
|
159
|
-
keyboard probe to `<body>`, and an `/overlay` dialog renders outside
|
|
160
|
-
inert.
|
|
150
|
+
`tools/ssr-smoke.ts` renders 49 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. 31 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-
|
|
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` and `bf-media-card`.
|
|
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
|
|
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,23 @@ 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.
|
|
391
419
|
- **Rows on a card:** `<bf-list [inset]="false" divided class="-mx-card">`, flush to the card's
|
|
392
420
|
edges with lines between the rows.
|
|
393
421
|
- **Theme switch:** `<bf-theme-switch [(value)]="colorScheme.scheme" />` (or
|
|
@@ -405,8 +433,8 @@ protected readonly colorScheme = inject(BfColorSchemeService);
|
|
|
405
433
|
for the platform picker on touch). Values are ISO strings (`'2026-10-28'`, `'14:30'`,
|
|
406
434
|
`'2026-10-28T14:30'`); month and day names come from `Intl` in the document's language.
|
|
407
435
|
- **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`
|
|
409
|
-
for a short fixed list.
|
|
436
|
+
server search (`queried`, `loading`); `bf-multi-combobox` for tags. `bf-select` (`/overlay`
|
|
437
|
+
too) stays the choice for a short fixed list.
|
|
410
438
|
- **Dashboards:** `bf-stat` with `delta`, `trend` and `trendIntent` for a figure and its change;
|
|
411
439
|
`@bitforgehq/ui/charts` for `bf-line-chart`, `bf-bar-chart`, `bf-donut-chart` and
|
|
412
440
|
`bf-sparkline` inside a `bf-chart-card` (title, period slot, loading, empty and error states),
|
|
@@ -462,6 +490,12 @@ injector? })` stacks up to three cards bottom-right (configurable with
|
|
|
462
490
|
- **Side panel:** `BfSidePanelService.open(component | template, { width, modal, data })` slides a
|
|
463
491
|
full-height `bf-side-panel` in from the inline end: a record's details or edit form beside the
|
|
464
492
|
list. `modal: false` keeps the page usable. In the Capacitor shell use `bf-sheet`/`bf-modal`.
|
|
493
|
+
- **Image viewer:** `BfImageViewerService.open(options)` with `images` (each
|
|
494
|
+
`{ src, label, caption? }`), `index`, `origin`, `actions` and `injector` shows one image full
|
|
495
|
+
screen over a dark scrim and steps through the set (buttons, arrow keys, Home/End, wrapping);
|
|
496
|
+
pass the clicked thumbnail as `origin` (or `(i) => thumbnails[i]`) so it zooms out of it and
|
|
497
|
+
back. Actions go in an `ng-template` (context `{ $implicit: image, index, ref }`). It returns a
|
|
498
|
+
`BfImageViewerRef` (`index()`, `next()`, `previous()`, `goTo()`, `close()`, `afterClosed`).
|
|
465
499
|
- **`closeOnNavigation`:** every overlay (`BfDialogConfig`, `confirm()`, and the `/mobile` sheet,
|
|
466
500
|
modal, action sheet and alert options) closes when the URL path changes by default (`true`);
|
|
467
501
|
pass `closeOnNavigation: false` to keep one open across a navigation (e.g. a draft that must
|
|
@@ -993,7 +1027,7 @@ describe('bf-button in a consumer app (Vitest)', () => {
|
|
|
993
1027
|
|
|
994
1028
|
### 6. Drive a form widget through its harness
|
|
995
1029
|
|
|
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)`.
|
|
1030
|
+
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`, `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
1031
|
|
|
998
1032
|
`select-harness.spec.ts`:
|
|
999
1033
|
|
|
@@ -1006,7 +1040,7 @@ import { Component, signal } from '@angular/core';
|
|
|
1006
1040
|
import { TestBed } from '@angular/core/testing';
|
|
1007
1041
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
|
1008
1042
|
import { FormField, form, required } from '@angular/forms/signals';
|
|
1009
|
-
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui';
|
|
1043
|
+
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui/overlay';
|
|
1010
1044
|
import { BfSelectHarness, provideBfTesting } from '@bitforgehq/ui/testing';
|
|
1011
1045
|
|
|
1012
1046
|
@Component({
|
|
@@ -1063,7 +1097,7 @@ describe('BfSelectHarness in a consumer app (Vitest)', () => {
|
|
|
1063
1097
|
|
|
1064
1098
|
### 7. Drive a dialog from the document root
|
|
1065
1099
|
|
|
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)`.
|
|
1100
|
+
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` and `BfImageViewerHarness` fire the `load` and `error` events with `loadImage()` and `failImage()`.
|
|
1067
1101
|
|
|
1068
1102
|
`dialog-harness.spec.ts`:
|
|
1069
1103
|
|
|
@@ -1220,6 +1254,7 @@ notes.
|
|
|
1220
1254
|
| Component | Entry point | Purpose |
|
|
1221
1255
|
| ------------------- | ----------------------------- | ------------------------------------------------------------------------------------------- |
|
|
1222
1256
|
| `bf-calendar` | `@bitforgehq/ui` | Inline month calendar for picking one date, with month and year jumps |
|
|
1257
|
+
| `bf-chat-composer` | `@bitforgehq/ui/overlay` | Chat input that grows with the text and sends on Enter, or stops a reply |
|
|
1223
1258
|
| `bf-checkbox` | `@bitforgehq/ui` | Rounded checkbox with an optional label that binds to Signal Forms |
|
|
1224
1259
|
| `bf-chip` | `@bitforgehq/ui` | Toggleable filter chip with a check mark when selected, in tonal or ink style |
|
|
1225
1260
|
| `bf-chip-group` | `@bitforgehq/ui` | Named row of chips that picks one like radios, or groups independent toggles |
|
|
@@ -1227,12 +1262,14 @@ notes.
|
|
|
1227
1262
|
| `bf-date-input` | `@bitforgehq/ui/overlay` | Typed date field with a calendar popover, valued as an ISO date string |
|
|
1228
1263
|
| `bf-datetime-input` | `@bitforgehq/ui/overlay` | Typed date and time field with a calendar and time list in one popover |
|
|
1229
1264
|
| `bf-field` | `@bitforgehq/ui` | Label, help text and error text wrapper that wires ids for assistive tech |
|
|
1265
|
+
| `bf-file-input` | `@bitforgehq/ui` | File picker field that lists picked or dropped files and checks type, size and count |
|
|
1266
|
+
| `bf-image-input` | `@bitforgehq/ui` | Single-image picker with a round or square preview tile and replace or remove |
|
|
1230
1267
|
| `bf-input` | `@bitforgehq/ui` | Single-line text field with label, help and error text, icon slots and Signal Forms binding |
|
|
1231
1268
|
| `bf-multi-combobox` | `@bitforgehq/ui/overlay` | Text field that filters options as you type and picks several as tags |
|
|
1232
1269
|
| `bf-number-input` | `@bitforgehq/ui/number-input` | Number field that binds number or null, with native min, max and step |
|
|
1233
1270
|
| `bf-radio-group` | `@bitforgehq/ui` | Group of radio buttons for choosing one option, with optional per-option descriptions |
|
|
1234
1271
|
| `bf-segmented` | `@bitforgehq/ui` | Segmented control for switching between two to four views, keyboard-navigable like radios |
|
|
1235
|
-
| `bf-select` | `@bitforgehq/ui`
|
|
1272
|
+
| `bf-select` | `@bitforgehq/ui/overlay` | Single-choice dropdown with a styled list, native under touch, with Signal Forms binding |
|
|
1236
1273
|
| `bf-textarea` | `@bitforgehq/ui` | Multi-line text field with label, help and error text, and Signal Forms binding |
|
|
1237
1274
|
| `bf-theme-switch` | `@bitforgehq/ui` | Segmented System, Light and Dark switch bound two-way to the app colour scheme |
|
|
1238
1275
|
| `bf-time-input` | `@bitforgehq/ui/overlay` | Typed time-of-day field with a popover list of times, valued as an ISO string |
|
|
@@ -1247,12 +1284,15 @@ notes.
|
|
|
1247
1284
|
| `bf-bar-chart` | `@bitforgehq/ui/charts` | Bar chart of series over categories, grouped or stacked, vertical or horizontal |
|
|
1248
1285
|
| `bf-card` | `@bitforgehq/ui` | Content surface with an optional header slot, in four visual styles from raised to flat |
|
|
1249
1286
|
| `bf-chart-card` | `@bitforgehq/ui/charts` | Card around one chart with a title, period control and loading, empty and error states |
|
|
1287
|
+
| `bf-chat-message` | `@bitforgehq/ui` | One chat message, a user bubble or a full-width assistant reply that can stream |
|
|
1288
|
+
| `bf-chat-thread` | `@bitforgehq/ui` | Scrolling chat message log that follows new text and offers a jump to latest |
|
|
1250
1289
|
| `div[bfDescriptionItem]` | `@bitforgehq/ui` | One label and value pair of a description list, label as term, content as value |
|
|
1251
1290
|
| `bf-description-list` | `@bitforgehq/ui` | Native description list of label-value pairs, as divided rows or a tile grid |
|
|
1252
1291
|
| `bf-donut-chart` | `@bitforgehq/ui/charts` | Donut chart of the parts of a whole, with a legend, tooltip and centre slot |
|
|
1253
1292
|
| `bf-line-chart` | `@bitforgehq/ui/charts` | Line chart of series over categories, with tooltip, legend and data table |
|
|
1254
1293
|
| `bf-list` | `@bitforgehq/ui` | Container for list rows, inset by default with rounded corners on a warm background |
|
|
1255
1294
|
| `bf-list-item` | `@bitforgehq/ui` | List row with leading slot, title, subtitle, trailing slot and an optional chevron |
|
|
1295
|
+
| `bf-media-card` | `@bitforgehq/ui` | Card with an image that loads, fades in or fails, plus a body and actions |
|
|
1256
1296
|
| `bf-progress` | `@bitforgehq/ui` | Progress bar in two sizes and four tones for completion, quotas and unknown waits |
|
|
1257
1297
|
| `bf-sparkline` | `@bitforgehq/ui/charts` | Tiny trend line with no axes or legend, for stats and table cells |
|
|
1258
1298
|
| `bf-stat` | `@bitforgehq/ui` | One labelled figure with an optional icon tile and a supporting line |
|
|
@@ -1305,6 +1345,7 @@ notes.
|
|
|
1305
1345
|
| `bf-alert-dialog` | `@bitforgehq/ui/mobile` | Centred confirmation dialog with a title, message and a row of role-styled buttons |
|
|
1306
1346
|
| `bf-desktop-toast` | `@bitforgehq/ui/overlay` | Desktop notification card in a bottom corner stack of up to three |
|
|
1307
1347
|
| `bf-dialog` | `@bitforgehq/ui/overlay` | Desktop modal dialog with a title, scrolling body, footer actions and focus trap |
|
|
1348
|
+
| `bf-image-viewer` | `@bitforgehq/ui/overlay` | Full-screen image viewer that zooms from a thumbnail and steps through a set |
|
|
1308
1349
|
| `bf-menu` | `@bitforgehq/ui/overlay` | Keyboard-operable menu panel with items, checkbox and radio items, groups and submenus |
|
|
1309
1350
|
| `bf-menu-button` | `@bitforgehq/ui/overlay` | Styled button that opens a menu, with outline, text, tonal and icon-only looks |
|
|
1310
1351
|
| `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
|
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# bf-chat-composer
|
|
2
|
+
|
|
3
|
+
Chat input that grows with the text and sends on Enter, or stops a reply
|
|
4
|
+
|
|
5
|
+
`BfChatComposerComponent` from `@bitforgehq/ui/overlay` · Forms · since 0.5.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, DestroyRef, inject, signal } from '@angular/core';
|
|
11
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
12
|
+
import { BfChatComposerComponent } from '@bitforgehq/ui/overlay';
|
|
13
|
+
import { ArrowUp, Square } from 'lucide';
|
|
14
|
+
|
|
15
|
+
/* How long the pretend reply runs before it finishes by itself. */
|
|
16
|
+
const REPLY_TIME = 3000;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The bf-chat-composer overview.
|
|
20
|
+
*
|
|
21
|
+
* Type and press Enter (Shift+Enter for a new line) or the send button: the text is sent, the
|
|
22
|
+
* field clears, and the send button turns into Stop while the pretend reply runs. Stop ends it
|
|
23
|
+
* early. Icons go in the `send-icon` and `stop-icon` slots; their names stay as text.
|
|
24
|
+
*/
|
|
25
|
+
@Component({
|
|
26
|
+
selector: 'app-example-chat-composer-overview',
|
|
27
|
+
imports: [BfChatComposerComponent, BfIconComponent],
|
|
28
|
+
template: `
|
|
29
|
+
<div class="flex flex-col gap-3">
|
|
30
|
+
<bf-chat-composer
|
|
31
|
+
placeholder="Ask for ideas"
|
|
32
|
+
[(value)]="draft"
|
|
33
|
+
[running]="running()"
|
|
34
|
+
(sent)="send($event)"
|
|
35
|
+
(stopped)="finish('Stopped.')"
|
|
36
|
+
>
|
|
37
|
+
<bf-icon bfSlot="send-icon" [icon]="arrowUp" />
|
|
38
|
+
<bf-icon bfSlot="stop-icon" [icon]="square" />
|
|
39
|
+
</bf-chat-composer>
|
|
40
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
41
|
+
</div>
|
|
42
|
+
`,
|
|
43
|
+
})
|
|
44
|
+
export class ChatComposerOverviewExampleComponent {
|
|
45
|
+
protected readonly arrowUp = ArrowUp;
|
|
46
|
+
protected readonly square = Square;
|
|
47
|
+
|
|
48
|
+
readonly draft = signal('');
|
|
49
|
+
readonly running = signal(false);
|
|
50
|
+
readonly status = signal('Nothing sent yet.');
|
|
51
|
+
|
|
52
|
+
private timer: ReturnType<typeof setTimeout> | null = null;
|
|
53
|
+
|
|
54
|
+
constructor() {
|
|
55
|
+
inject(DestroyRef).onDestroy(() => this.clearTimer());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
send(text: string): void {
|
|
59
|
+
this.running.set(true);
|
|
60
|
+
this.status.set(`Sent: "${text}". Replying...`);
|
|
61
|
+
this.timer = setTimeout(() => this.finish('Reply done.'), REPLY_TIME);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
finish(status: string): void {
|
|
65
|
+
this.clearTimer();
|
|
66
|
+
this.running.set(false);
|
|
67
|
+
this.status.set(status);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private clearTimer(): void {
|
|
71
|
+
if (this.timer !== null) {
|
|
72
|
+
clearTimeout(this.timer);
|
|
73
|
+
this.timer = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Overview
|
|
80
|
+
|
|
81
|
+
The input row of a chat: a `bf-textarea` that grows with the text (`autoResize`, up to
|
|
82
|
+
`maxRows`, then it scrolls), a start and an end slot for extra controls, and one button that
|
|
83
|
+
sends the text or, while a reply is `running`, stops it. `Enter` sends and `Shift+Enter`
|
|
84
|
+
starts a new line; nothing sends during IME composition or
|
|
85
|
+
when the text is only whitespace. After a send the text clears and focus stays in the field.
|
|
86
|
+
`disabled` with a `disabledReason` shows the reason as the button's tooltip and as the empty
|
|
87
|
+
field's placeholder.
|
|
88
|
+
|
|
89
|
+
The input row of a chat: a text field that grows with the text, slots for extra controls, and one button that sends the text or stops the running reply. Import it from `@bitforgehq/ui/overlay` (it needs `@angular/cdk` for the disabled reason's tooltip).
|
|
90
|
+
|
|
91
|
+
## When to use
|
|
92
|
+
|
|
93
|
+
Use `bf-chat-composer` below a [bf-chat-thread](https://bitforge-ui.web.app/components/chat-thread) to write to an assistant. Use [bf-textarea](https://bitforge-ui.web.app/components/textarea) instead for free text in a form that is saved with the form, such as a comment field.
|
|
94
|
+
|
|
95
|
+
## Basic usage
|
|
96
|
+
|
|
97
|
+
Bind the text with `[(value)]` and handle `(sent)`, which emits the trimmed text; the field then clears and keeps focus. <kbd>Enter</kbd> sends and <kbd>Shift</kbd>+<kbd>Enter</kbd> starts a new line. Nothing sends while an input method is composing (Japanese, Chinese or Korean input confirm their text with <kbd>Enter</kbd>), and empty or whitespace-only text never sends: the send button is disabled then. `(changed)` emits the text after each edit by the user.
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { Component, DestroyRef, inject, signal } from '@angular/core';
|
|
101
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
102
|
+
import { BfChatComposerComponent } from '@bitforgehq/ui/overlay';
|
|
103
|
+
import { ArrowUp, Square } from 'lucide';
|
|
104
|
+
|
|
105
|
+
/* How long the pretend reply runs before it finishes by itself. */
|
|
106
|
+
const REPLY_TIME = 3000;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The bf-chat-composer overview.
|
|
110
|
+
*
|
|
111
|
+
* Type and press Enter (Shift+Enter for a new line) or the send button: the text is sent, the
|
|
112
|
+
* field clears, and the send button turns into Stop while the pretend reply runs. Stop ends it
|
|
113
|
+
* early. Icons go in the `send-icon` and `stop-icon` slots; their names stay as text.
|
|
114
|
+
*/
|
|
115
|
+
@Component({
|
|
116
|
+
selector: 'app-example-chat-composer-overview',
|
|
117
|
+
imports: [BfChatComposerComponent, BfIconComponent],
|
|
118
|
+
template: `
|
|
119
|
+
<div class="flex flex-col gap-3">
|
|
120
|
+
<bf-chat-composer
|
|
121
|
+
placeholder="Ask for ideas"
|
|
122
|
+
[(value)]="draft"
|
|
123
|
+
[running]="running()"
|
|
124
|
+
(sent)="send($event)"
|
|
125
|
+
(stopped)="finish('Stopped.')"
|
|
126
|
+
>
|
|
127
|
+
<bf-icon bfSlot="send-icon" [icon]="arrowUp" />
|
|
128
|
+
<bf-icon bfSlot="stop-icon" [icon]="square" />
|
|
129
|
+
</bf-chat-composer>
|
|
130
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
131
|
+
</div>
|
|
132
|
+
`,
|
|
133
|
+
})
|
|
134
|
+
export class ChatComposerOverviewExampleComponent {
|
|
135
|
+
protected readonly arrowUp = ArrowUp;
|
|
136
|
+
protected readonly square = Square;
|
|
137
|
+
|
|
138
|
+
readonly draft = signal('');
|
|
139
|
+
readonly running = signal(false);
|
|
140
|
+
readonly status = signal('Nothing sent yet.');
|
|
141
|
+
|
|
142
|
+
private timer: ReturnType<typeof setTimeout> | null = null;
|
|
143
|
+
|
|
144
|
+
constructor() {
|
|
145
|
+
inject(DestroyRef).onDestroy(() => this.clearTimer());
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
send(text: string): void {
|
|
149
|
+
this.running.set(true);
|
|
150
|
+
this.status.set(`Sent: "${text}". Replying...`);
|
|
151
|
+
this.timer = setTimeout(() => this.finish('Reply done.'), REPLY_TIME);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
finish(status: string): void {
|
|
155
|
+
this.clearTimer();
|
|
156
|
+
this.running.set(false);
|
|
157
|
+
this.status.set(status);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private clearTimer(): void {
|
|
161
|
+
if (this.timer !== null) {
|
|
162
|
+
clearTimeout(this.timer);
|
|
163
|
+
this.timer = null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Send and stop
|
|
170
|
+
|
|
171
|
+
While `running` is true (the app's reply is streaming) the button is Stop: it emits `(stopped)` and the app ends the reply and sets `running` back to false. <kbd>Enter</kbd> does not send while a reply runs, and the text stays in the field. The button keeps its place and focus while it switches.
|
|
172
|
+
|
|
173
|
+
## Icons
|
|
174
|
+
|
|
175
|
+
The `send-icon` and `stop-icon` slots take the button's icons (the library never imports an icon into a widget). With an icon the button is a round icon button and its text ("Send", "Stop", from `chatSend` and `chatStop`) stays as its accessible name; without one the button shows the text.
|
|
176
|
+
|
|
177
|
+
## Extra controls
|
|
178
|
+
|
|
179
|
+
The `start` slot (before the field) and the `end` slot (between the field and the button) take controls of the app, such as a preset picker or an attach button. They line up with the bottom of the field as it grows.
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { Component, signal } from '@angular/core';
|
|
183
|
+
import { BfIconButtonComponent, type BfSelectOption } from '@bitforgehq/ui';
|
|
184
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
185
|
+
import { BfChatComposerComponent, BfSelectComponent } from '@bitforgehq/ui/overlay';
|
|
186
|
+
import { Paperclip } from 'lucide';
|
|
187
|
+
|
|
188
|
+
type Preset = 'names' | 'slogans' | 'menu';
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Extra controls in the start and end slots.
|
|
192
|
+
*
|
|
193
|
+
* A preset picker in the `start` slot and an attach button in the `end` slot, beside the field;
|
|
194
|
+
* without icons in its slots the send button shows its text. The sent line names the preset the
|
|
195
|
+
* message went out with.
|
|
196
|
+
*/
|
|
197
|
+
@Component({
|
|
198
|
+
selector: 'app-example-chat-composer-slots',
|
|
199
|
+
imports: [BfChatComposerComponent, BfSelectComponent, BfIconButtonComponent, BfIconComponent],
|
|
200
|
+
template: `
|
|
201
|
+
<div class="flex flex-col gap-3">
|
|
202
|
+
<bf-chat-composer placeholder="Describe the cafe" (sent)="sent.set(preset() + ': ' + $event)">
|
|
203
|
+
<bf-select
|
|
204
|
+
bfSlot="start"
|
|
205
|
+
ariaLabel="Preset"
|
|
206
|
+
class="w-36"
|
|
207
|
+
[options]="presets"
|
|
208
|
+
[(value)]="preset"
|
|
209
|
+
/>
|
|
210
|
+
<bf-icon-button bfSlot="end" variant="text" label="Attach a file">
|
|
211
|
+
<bf-icon [icon]="paperclip" />
|
|
212
|
+
</bf-icon-button>
|
|
213
|
+
</bf-chat-composer>
|
|
214
|
+
<p data-sent class="text-caption text-text-muted">{{ sent() || 'Nothing sent yet.' }}</p>
|
|
215
|
+
</div>
|
|
216
|
+
`,
|
|
217
|
+
})
|
|
218
|
+
export class ChatComposerSlotsExampleComponent {
|
|
219
|
+
protected readonly paperclip = Paperclip;
|
|
220
|
+
|
|
221
|
+
readonly presets: readonly BfSelectOption<Preset>[] = [
|
|
222
|
+
{ value: 'names', label: 'Names' },
|
|
223
|
+
{ value: 'slogans', label: 'Slogans' },
|
|
224
|
+
{ value: 'menu', label: 'Menu' },
|
|
225
|
+
];
|
|
226
|
+
readonly preset = signal<Preset | null>('names');
|
|
227
|
+
readonly sent = signal('');
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Growing field
|
|
232
|
+
|
|
233
|
+
The field is a [bf-textarea](https://bitforge-ui.web.app/components/textarea) with `autoResize`: it starts at one line and grows with the text up to `maxRows` lines (8 by default), then it scrolls. `maxLength` limits the text. A browser without CSS field sizing keeps the one-line field and scrolls inside it.
|
|
234
|
+
|
|
235
|
+
## Disabled
|
|
236
|
+
|
|
237
|
+
`disabled` stops typing and sending. With a `disabledReason` (a usage limit, a missing choice) the reason is the empty field's placeholder and the send button's tooltip on hover and keyboard focus; the button then stays focusable, marked `aria-disabled`, so the tooltip can show.
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
import { Component } from '@angular/core';
|
|
241
|
+
import { BfChatComposerComponent } from '@bitforgehq/ui/overlay';
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Disabled with a reason.
|
|
245
|
+
*
|
|
246
|
+
* While nothing can be sent, `disabled` with a `disabledReason` says why: it is the empty
|
|
247
|
+
* field's placeholder, and the send button's tooltip on hover and keyboard focus (the button
|
|
248
|
+
* stays focusable for that, marked `aria-disabled`).
|
|
249
|
+
*/
|
|
250
|
+
@Component({
|
|
251
|
+
selector: 'app-example-chat-composer-disabled',
|
|
252
|
+
imports: [BfChatComposerComponent],
|
|
253
|
+
template: `
|
|
254
|
+
<bf-chat-composer disabled disabledReason="You have used today's 20 messages. More tomorrow." />
|
|
255
|
+
`,
|
|
256
|
+
})
|
|
257
|
+
export class ChatComposerDisabledExampleComponent {}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Sizes
|
|
261
|
+
|
|
262
|
+
`size` sets the field's padding and type and the button's height together: `sm` 32 px, `md` 40 px (default), `lg` 48 px buttons. Unset, it follows `provideBfDefaults()`'s `fieldSize`; a touch app that sets `fieldSize: 'lg'` gets the 48 px composer.
|
|
263
|
+
|
|
264
|
+
## Desktop
|
|
265
|
+
|
|
266
|
+
The field shows the text cursor and its focus ring on every focus. The button has bf-button's hover and pressed steps (primary for send, tonal for stop); disabled it shows no hover. With a `disabledReason` its tooltip shows after a 500 ms hover or at once on keyboard focus. Under a coarse pointer the button's hit area grows to 44 px; the field gets no touch extension (set `fieldSize: 'lg'` for touch).
|
|
267
|
+
|
|
268
|
+
## Theming
|
|
269
|
+
|
|
270
|
+
- Field: see [bf-textarea](https://bitforge-ui.web.app/components/textarea) (`border-strong`, `primary` focus border, `ring` ring, `text-muted` placeholder).
|
|
271
|
+
- Send button: `primary` with `on-primary` text (`primary-hover`, `primary-pressed`); stop button: `primary-soft` with `primary-strong`; disabled: `surface-sunken` with `text-disabled`.
|
|
272
|
+
- Tooltip: see the `bfTooltip` directive.
|
|
273
|
+
|
|
274
|
+
## Accessibility
|
|
275
|
+
|
|
276
|
+
The field is a native `textarea`, named by `label` (visible), else `ariaLabel` or `ariaLabelledby`, else the `chatComposer` label ("Message"). The button is a native `button` whose text is its name, also when an icon shows ("Send", "Stop"); it is natively `disabled` with nothing to send, and `aria-disabled` while disabled with a reason, which the tooltip then describes (`aria-describedby`) when it shows. After a send focus returns to the field. The tooltip shows nothing under a coarse pointer, so the reason is also the placeholder of the empty field.
|
|
277
|
+
|
|
278
|
+
### Keyboard
|
|
279
|
+
|
|
280
|
+
| Key | Where | Action |
|
|
281
|
+
| ------------------------------------------------ | -------- | ----------------------------------------------------------------------------- |
|
|
282
|
+
| <kbd>Enter</kbd> | Field | Sends the text (not while composing, while a reply runs, or when it is empty) |
|
|
283
|
+
| <kbd>Shift</kbd>+<kbd>Enter</kbd> | Field | Starts a new line |
|
|
284
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Composer | Moves between the start controls, the field, the end controls and the button |
|
|
285
|
+
| <kbd>Enter</kbd> / <kbd>Space</kbd> | Button | Sends the text, or stops the reply while it runs |
|
|
286
|
+
| <kbd>Escape</kbd> | Button | Hides the disabled reason's tooltip |
|
|
287
|
+
|
|
288
|
+
## Inputs
|
|
289
|
+
|
|
290
|
+
| Name | Type | Default | Description |
|
|
291
|
+
| ---------------- | ----------------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------- |
|
|
292
|
+
| `running` | `boolean` | `false` | A reply is running: the button stops it (`stopped`) instead of sending. |
|
|
293
|
+
| `disabled` | `boolean` | `false` | Nothing can be written or sent. |
|
|
294
|
+
| `disabledReason` | `string` | `''` | Why it is disabled, shown as the button's tooltip and the empty field's placeholder. |
|
|
295
|
+
| `label` | `string` | `''` | Visible label above the field; usually left empty in a chat. |
|
|
296
|
+
| `ariaLabel` | `string` | `''` | The field's accessible name without a visible label; empty uses the `chatComposer` label ("Message"). |
|
|
297
|
+
| `ariaLabelledby` | `string` | `''` | Id(s) of the element(s) that name the field without a visible label. |
|
|
298
|
+
| `placeholder` | `string` | `''` | Example text shown while the field is empty. |
|
|
299
|
+
| `maxRows` | `number` | `8` | The lines the field grows to at most; past them it scrolls. |
|
|
300
|
+
| `maxLength` | `number \| undefined` | `undefined` | Maximum number of characters (native `maxlength`). |
|
|
301
|
+
| `name` | `string` | `''` | Native name attribute of the textarea. |
|
|
302
|
+
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Padding, type and button step: sm, md, lg; unset follows `provideBfDefaults()` (`fieldSize`, `md` by default). |
|
|
303
|
+
| `value` | `string` | `''` | The text being written; two-way bindable. Cleared after a send. Two-way: `[(value)]`. |
|
|
304
|
+
|
|
305
|
+
## Outputs
|
|
306
|
+
|
|
307
|
+
| Name | Payload | Description |
|
|
308
|
+
| --------- | -------- | ------------------------------------------------------------------------------------ |
|
|
309
|
+
| `changed` | `string` | Emitted with the new text right after the user changes it (each keystroke or paste). |
|
|
310
|
+
| `sent` | `string` | Emitted with the trimmed text when the user sends it (Enter or the send button). |
|
|
311
|
+
| `stopped` | `void` | Emitted when the user presses the stop button while `running`. |
|
|
312
|
+
|
|
313
|
+
## Slots
|
|
314
|
+
|
|
315
|
+
- `start`: Controls before the field, such as a preset picker or an attach button.
|
|
316
|
+
- `end`: Controls between the field and the send button.
|
|
317
|
+
- `send-icon`: The send button's icon; without one the button shows its text.
|
|
318
|
+
- `stop-icon`: The stop button's icon; without one the button shows its text.
|
|
319
|
+
|
|
320
|
+
## Methods
|
|
321
|
+
|
|
322
|
+
### `send(): void`
|
|
323
|
+
|
|
324
|
+
Sends the text: emits `sent` with it trimmed, clears the field and keeps focus in it. Does
|
|
325
|
+
nothing while disabled or running, or when the text is only whitespace.
|
|
326
|
+
|
|
327
|
+
### `focus(): void`
|
|
328
|
+
|
|
329
|
+
Focuses the text field.
|
|
330
|
+
|
|
331
|
+
## Notes
|
|
332
|
+
|
|
333
|
+
### Do
|
|
334
|
+
|
|
335
|
+
- Set `running` from the app's own reply state and turn it off when the reply ends, stops or fails.
|
|
336
|
+
- Give a `disabledReason` whenever the composer is disabled for a reason the user can act on.
|
|
337
|
+
|
|
338
|
+
### Don't
|
|
339
|
+
|
|
340
|
+
- Don't clear the value yourself after `(sent)`: the composer does it.
|
|
341
|
+
- Don't put the reply's progress in the composer; show it in the thread's streaming message.
|
|
342
|
+
|
|
343
|
+
## Migration notes
|
|
344
|
+
|
|
345
|
+
### 0.5.0
|
|
346
|
+
|
|
347
|
+
New: `bf-chat-composer` in `@bitforgehq/ui/overlay`, the input row of a chat. A `bf-textarea`
|
|
348
|
+
with `autoResize` grows from one line to `maxRows` (8), then scrolls. `Enter` sends and
|
|
349
|
+
`Shift+Enter` starts a new line; nothing sends during IME composition or with whitespace-only
|
|
350
|
+
text. `value` is a `model()` that clears after a send; `sent` emits the trimmed text, `changed`
|
|
351
|
+
each edit. While `running` the send button is a stop button that emits `stopped`. Icons go in the
|
|
352
|
+
`send-icon` and `stop-icon` slots (the text stays as the button's name, and shows without an
|
|
353
|
+
icon); `start` and `end` slots take extra controls. `disabled` with `disabledReason` shows the
|
|
354
|
+
reason as the button's tooltip (`aria-disabled`, still focusable) and the empty field's
|
|
355
|
+
placeholder. `size` follows `fieldSize`. New labels `chatComposer`, `chatSend` and `chatStop`.
|
|
356
|
+
`@bitforgehq/ui/testing`: new `BfChatComposerHarness` (`setValue()`, `getValue()`, `send()`,
|
|
357
|
+
`pressEnter()`, `stop()`, `isRunning()`, `isDisabled()`, `canAct()`, `getButtonLabel()`).
|