@bitforgehq/ui 0.5.0-next.3 → 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.
- package/CHANGELOG.md +47 -0
- package/README.md +3 -3
- package/ai/MANIFEST.index.json +9 -0
- package/ai/MANIFEST.json +265 -5
- package/ai/REFERENCE.index.json +28 -0
- package/ai/REFERENCE.json +172 -3
- package/ai/UI_GUIDE.md +14 -5
- package/ai/components/badge.md +41 -1
- package/ai/components/image-tile.md +452 -0
- package/ai/components/menu-button.md +65 -9
- package/ai/components/pagination.md +59 -11
- package/ai/reference/bf-image-tile-aspect.md +28 -0
- package/ai/reference/bf-image-tile-caption-placement.md +27 -0
- package/ai/reference/bf-image-tile-harness.md +70 -0
- package/ai/reference/bf-labels.md +3 -1
- package/ai/reference/bf-pagination-position-label.md +22 -0
- package/ai/reference/provide-bf-labels.md +2 -1
- package/fesm2022/bitforgehq-ui-overlay.mjs +26 -3
- package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-testing.mjs +59 -1
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui.mjs +576 -55
- package/fesm2022/bitforgehq-ui.mjs.map +1 -1
- package/llms-full.txt +779 -9
- package/llms.txt +5 -0
- package/package.json +1 -1
- package/types/bitforgehq-ui-overlay.d.ts +11 -0
- package/types/bitforgehq-ui-testing.d.ts +83 -0
- package/types/bitforgehq-ui.d.ts +239 -12
package/llms-full.txt
CHANGED
|
@@ -147,10 +147,10 @@ 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
|
|
150
|
+
`tools/ssr-smoke.ts` renders 50 fixtures with `renderApplication()` (every root widget, `bf-icon`,
|
|
151
151
|
`bf-number-input`, `bf-select` from `/overlay`, a `/charts` line chart, a `/mobile` page with
|
|
152
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.
|
|
153
|
+
the router, and an `/overlay` dialog), and all of them render without an error. 32 of them render
|
|
154
154
|
identically twice. The other 18 number their ids differently on every render, from a module-level
|
|
155
155
|
counter, so they could not hydrate: `bf-app-frame`, `bf-calendar`, `bf-checkbox`, `bf-file-input`, `bf-image-input`, `bf-input`,
|
|
156
156
|
`bf-radio-group`, `bf-segmented`, `bf-select`, `bf-sidebar`, `bf-tab-group`, `bf-table`,
|
|
@@ -254,7 +254,7 @@ you can guess a binding without looking it up:
|
|
|
254
254
|
spinner, tab bar, progress or icon); every component that renders a native input, select or
|
|
255
255
|
textarea (checkbox and toggle included) also takes `ariaLabel` and `ariaLabelledby` for when no
|
|
256
256
|
visible `label` is shown; a built-in sub-control's name is `<part>Label` (`dismissLabel`,
|
|
257
|
-
`closeLabel`, `actionLabel`); `alt` only on `bf-avatar` and `bf-
|
|
257
|
+
`closeLabel`, `actionLabel`); `alt` only on `bf-avatar`, `bf-media-card` and `bf-image-tile`.
|
|
258
258
|
- Types: every union or object type of an input, model, output, `Bf…Options`/`Bf…Config` property
|
|
259
259
|
or an interface they name is a named type exported from the same entry
|
|
260
260
|
(`BfToggleLabelPosition`, `BfRefresherRefreshEvent`, `BfDialogRole`, …), so type your own state
|
|
@@ -416,6 +416,11 @@ protected readonly colorScheme = inject(BfColorSchemeService);
|
|
|
416
416
|
when it fails; retry with a changed `src`. Never project your own `<img>` for the main picture.
|
|
417
417
|
Clickable like `bf-card` (a `bfAction` in the title); `selectable` with `[(selected)]` and
|
|
418
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.
|
|
419
424
|
- **Rows on a card:** `<bf-list [inset]="false" divided class="-mx-card">`, flush to the card's
|
|
420
425
|
edges with lines between the rows.
|
|
421
426
|
- **Theme switch:** `<bf-theme-switch [(value)]="colorScheme.scheme" />` (or
|
|
@@ -427,6 +432,9 @@ protected readonly colorScheme = inject(BfColorSchemeService);
|
|
|
427
432
|
- **Paging and loading:** `<bf-pagination [(page)]="page" [total]="count" [pageSize]="20">` under a
|
|
428
433
|
`bf-table` (the app slices its rows); `bf-skeleton` (`shape` text/rect/circle, `lines`) as the
|
|
429
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`.
|
|
430
438
|
- **Dates and times:** `<bf-calendar [(value)]="date">` for an inline month view;
|
|
431
439
|
`bf-date-input`, `bf-time-input` and `bf-datetime-input` (`/overlay`) for a field: typed parts
|
|
432
440
|
in locale order plus a calendar or time-list popover (`picker="none"` for typed only, `native`
|
|
@@ -1027,7 +1035,7 @@ describe('bf-button in a consumer app (Vitest)', () => {
|
|
|
1027
1035
|
|
|
1028
1036
|
### 6. Drive a form widget through its harness
|
|
1029
1037
|
|
|
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.
|
|
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.
|
|
1031
1039
|
|
|
1032
1040
|
`select-harness.spec.ts`:
|
|
1033
1041
|
|
|
@@ -1097,7 +1105,7 @@ describe('BfSelectHarness in a consumer app (Vitest)', () => {
|
|
|
1097
1105
|
|
|
1098
1106
|
### 7. Drive a dialog from the document root
|
|
1099
1107
|
|
|
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()`.
|
|
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()`.
|
|
1101
1109
|
|
|
1102
1110
|
`dialog-harness.spec.ts`:
|
|
1103
1111
|
|
|
@@ -1289,6 +1297,7 @@ notes.
|
|
|
1289
1297
|
| `div[bfDescriptionItem]` | `@bitforgehq/ui` | One label and value pair of a description list, label as term, content as value |
|
|
1290
1298
|
| `bf-description-list` | `@bitforgehq/ui` | Native description list of label-value pairs, as divided rows or a tile grid |
|
|
1291
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 |
|
|
1292
1301
|
| `bf-line-chart` | `@bitforgehq/ui/charts` | Line chart of series over categories, with tooltip, legend and data table |
|
|
1293
1302
|
| `bf-list` | `@bitforgehq/ui` | Container for list rows, inset by default with rounded corners on a warm background |
|
|
1294
1303
|
| `bf-list-item` | `@bitforgehq/ui` | List row with leading slot, title, subtitle, trailing slot and an optional chevron |
|
|
@@ -3917,6 +3926,37 @@ import { BfBadgeComponent } from '@bitforgehq/ui';
|
|
|
3917
3926
|
export class BadgeStrongExampleComponent {}
|
|
3918
3927
|
```
|
|
3919
3928
|
|
|
3929
|
+
## Icon
|
|
3930
|
+
|
|
3931
|
+
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.
|
|
3932
|
+
|
|
3933
|
+
```ts
|
|
3934
|
+
import { Component } from '@angular/core';
|
|
3935
|
+
import { BfBadgeComponent } from '@bitforgehq/ui';
|
|
3936
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
3937
|
+
import { Box, Check, Clock } from 'lucide';
|
|
3938
|
+
|
|
3939
|
+
/** Badges with a leading icon: the icon repeats the meaning, the text still says it. */
|
|
3940
|
+
@Component({
|
|
3941
|
+
selector: 'app-example-badge-icon',
|
|
3942
|
+
imports: [BfBadgeComponent, BfIconComponent],
|
|
3943
|
+
template: `
|
|
3944
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
3945
|
+
<bf-badge variant="success"><bf-icon bfSlot="icon-start" [icon]="check" />Reference</bf-badge>
|
|
3946
|
+
<bf-badge variant="info"><bf-icon bfSlot="icon-start" [icon]="box" />3 versions</bf-badge>
|
|
3947
|
+
<bf-badge variant="warning" emphasis="strong"
|
|
3948
|
+
><bf-icon bfSlot="icon-start" [icon]="clock" />Queued</bf-badge
|
|
3949
|
+
>
|
|
3950
|
+
</div>
|
|
3951
|
+
`,
|
|
3952
|
+
})
|
|
3953
|
+
export class BadgeIconExampleComponent {
|
|
3954
|
+
protected readonly check = Check;
|
|
3955
|
+
protected readonly box = Box;
|
|
3956
|
+
protected readonly clock = Clock;
|
|
3957
|
+
}
|
|
3958
|
+
```
|
|
3959
|
+
|
|
3920
3960
|
## Count pill
|
|
3921
3961
|
|
|
3922
3962
|
Setting `count` switches to the compact primary pill. Values above 99 render as "99+"; zero renders as "0".
|
|
@@ -3980,11 +4020,12 @@ Consumes tokens by variant, background then text:
|
|
|
3980
4020
|
- `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)
|
|
3981
4021
|
- Count pill (`count` set): `primary` / `on-primary`, ignoring `variant`
|
|
3982
4022
|
- 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
|
|
4023
|
+
- Icon (`icon-start`): drawn in the text colour (`currentColor`), so it follows the variant and stays visible in forced colours
|
|
3983
4024
|
- Shape (label and count pill): `rounded-control` (`--radius-control`, `9999px` by default).
|
|
3984
4025
|
|
|
3985
4026
|
## Accessibility
|
|
3986
4027
|
|
|
3987
|
-
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.
|
|
4028
|
+
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.
|
|
3988
4029
|
|
|
3989
4030
|
## Inputs
|
|
3990
4031
|
|
|
@@ -3997,6 +4038,7 @@ A badge is a plain `<span>` with no role or ARIA attributes; its text is read as
|
|
|
3997
4038
|
|
|
3998
4039
|
## Slots
|
|
3999
4040
|
|
|
4041
|
+
- `icon-start`: An icon before the text, drawn at 12 px; not shown on a count pill.
|
|
4000
4042
|
- `default`: The badge text, shown when no `count` is set.
|
|
4001
4043
|
|
|
4002
4044
|
## Notes
|
|
@@ -4016,6 +4058,13 @@ A badge is a plain `<span>` with no role or ARIA attributes; its text is read as
|
|
|
4016
4058
|
|
|
4017
4059
|
## Migration notes
|
|
4018
4060
|
|
|
4061
|
+
### 0.5.0
|
|
4062
|
+
|
|
4063
|
+
New `icon-start` slot: `<bf-icon bfSlot="icon-start" … />` before the label text, drawn at 12 px
|
|
4064
|
+
whatever the icon's `size`, 4 px from the text, with the start padding tightened to 8 px. It
|
|
4065
|
+
takes the text colour, so it follows the variant and forced colours. A count pill does not show
|
|
4066
|
+
it.
|
|
4067
|
+
|
|
4019
4068
|
### 0.4.0
|
|
4020
4069
|
|
|
4021
4070
|
New `emphasis` input (`BfBadgeEmphasis`: `soft`, the default, or `strong`). `strong` is a solid
|
|
@@ -14449,6 +14498,461 @@ only picks; uploading stays with the app. New label keys `imageInputChoose`, `im
|
|
|
14449
14498
|
|
|
14450
14499
|
---
|
|
14451
14500
|
|
|
14501
|
+
# bf-image-tile
|
|
14502
|
+
|
|
14503
|
+
Small card for thumbnail grids: a picture and title that act as one target
|
|
14504
|
+
|
|
14505
|
+
`BfImageTileComponent` from `@bitforgehq/ui` · Data display · since 0.5.0
|
|
14506
|
+
|
|
14507
|
+
## Example
|
|
14508
|
+
|
|
14509
|
+
```ts
|
|
14510
|
+
import { Component, DestroyRef, inject, signal } from '@angular/core';
|
|
14511
|
+
import { BfActionDirective, BfBadgeComponent, BfImageTileComponent } from '@bitforgehq/ui';
|
|
14512
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
14513
|
+
import { Box, ImagePlus } from 'lucide';
|
|
14514
|
+
|
|
14515
|
+
/** One model in the grid, as the app tracks it. */
|
|
14516
|
+
interface Model {
|
|
14517
|
+
readonly id: string;
|
|
14518
|
+
readonly name: string;
|
|
14519
|
+
readonly src: string;
|
|
14520
|
+
readonly alt: string;
|
|
14521
|
+
readonly versions: number;
|
|
14522
|
+
readonly updated: string;
|
|
14523
|
+
}
|
|
14524
|
+
|
|
14525
|
+
/** How long the simulated render of the last model takes. */
|
|
14526
|
+
const RENDER_MS = 3000;
|
|
14527
|
+
|
|
14528
|
+
/**
|
|
14529
|
+
* The bf-image-tile overview.
|
|
14530
|
+
*
|
|
14531
|
+
* A grid of 3D models, one picture each: a click selects a tile (the ring, and "Selected" read
|
|
14532
|
+
* before its title), a badge counts the versions, the last picture is still rendering and fades
|
|
14533
|
+
* in when it arrives, and a model without a render yet shows the placeholder. The titles differ
|
|
14534
|
+
* in length, yet every tile in a row is as tall.
|
|
14535
|
+
*/
|
|
14536
|
+
@Component({
|
|
14537
|
+
selector: 'app-example-image-tile-overview',
|
|
14538
|
+
imports: [BfActionDirective, BfBadgeComponent, BfIconComponent, BfImageTileComponent],
|
|
14539
|
+
template: `
|
|
14540
|
+
<div class="grid grid-cols-2 gap-4 @md:grid-cols-4">
|
|
14541
|
+
@for (model of models(); track model.id) {
|
|
14542
|
+
<bf-image-tile
|
|
14543
|
+
[src]="model.src"
|
|
14544
|
+
[alt]="model.alt"
|
|
14545
|
+
[loading]="model.id === 'lantern' && rendering()"
|
|
14546
|
+
[selected]="model.id === current()"
|
|
14547
|
+
>
|
|
14548
|
+
<h2>
|
|
14549
|
+
<button bfAction type="button" (click)="current.set(model.id)">{{ model.name }}</button>
|
|
14550
|
+
</h2>
|
|
14551
|
+
<span bfSlot="meta">{{ model.updated }}</span>
|
|
14552
|
+
@if (model.versions > 1) {
|
|
14553
|
+
<bf-badge bfSlot="badge" variant="info"
|
|
14554
|
+
><bf-icon bfSlot="icon-start" [icon]="box" />{{ model.versions }} versions</bf-badge
|
|
14555
|
+
>
|
|
14556
|
+
}
|
|
14557
|
+
<bf-icon bfSlot="placeholder-icon" [icon]="imagePlus" />
|
|
14558
|
+
</bf-image-tile>
|
|
14559
|
+
}
|
|
14560
|
+
</div>
|
|
14561
|
+
<p aria-live="polite" class="text-caption text-text-muted mt-3">
|
|
14562
|
+
Selected: {{ currentName() }}
|
|
14563
|
+
</p>
|
|
14564
|
+
`,
|
|
14565
|
+
})
|
|
14566
|
+
export class ImageTileOverviewExampleComponent {
|
|
14567
|
+
protected readonly box = Box;
|
|
14568
|
+
protected readonly imagePlus = ImagePlus;
|
|
14569
|
+
protected readonly current = signal('ship');
|
|
14570
|
+
protected readonly rendering = signal(true);
|
|
14571
|
+
protected readonly models = signal<Model[]>([
|
|
14572
|
+
{
|
|
14573
|
+
id: 'ship',
|
|
14574
|
+
name: 'Harbour ship',
|
|
14575
|
+
src: 'media/harbour.svg',
|
|
14576
|
+
alt: 'A ship in a harbour',
|
|
14577
|
+
versions: 3,
|
|
14578
|
+
updated: 'Today',
|
|
14579
|
+
},
|
|
14580
|
+
{
|
|
14581
|
+
id: 'tower',
|
|
14582
|
+
name: 'Glacier watchtower with a long rope bridge',
|
|
14583
|
+
src: 'media/glacier.svg',
|
|
14584
|
+
alt: 'A tower on a glacier',
|
|
14585
|
+
versions: 1,
|
|
14586
|
+
updated: 'Yesterday',
|
|
14587
|
+
},
|
|
14588
|
+
{ id: 'well', name: 'Desert well', src: '', alt: '', versions: 1, updated: 'Last week' },
|
|
14589
|
+
{
|
|
14590
|
+
id: 'lantern',
|
|
14591
|
+
name: 'Forest lantern',
|
|
14592
|
+
src: '',
|
|
14593
|
+
alt: 'A lantern in a pine forest',
|
|
14594
|
+
versions: 2,
|
|
14595
|
+
updated: 'Rendering…',
|
|
14596
|
+
},
|
|
14597
|
+
]);
|
|
14598
|
+
|
|
14599
|
+
constructor() {
|
|
14600
|
+
const timer = setTimeout(() => {
|
|
14601
|
+
this.models.update((models) =>
|
|
14602
|
+
models.map((m) =>
|
|
14603
|
+
m.id === 'lantern' ? { ...m, src: 'media/forest.svg', updated: 'Just now' } : m,
|
|
14604
|
+
),
|
|
14605
|
+
);
|
|
14606
|
+
this.rendering.set(false);
|
|
14607
|
+
}, RENDER_MS);
|
|
14608
|
+
inject(DestroyRef).onDestroy(() => clearTimeout(timer));
|
|
14609
|
+
}
|
|
14610
|
+
|
|
14611
|
+
protected currentName(): string {
|
|
14612
|
+
return this.models().find((m) => m.id === this.current())?.name ?? '';
|
|
14613
|
+
}
|
|
14614
|
+
}
|
|
14615
|
+
```
|
|
14616
|
+
|
|
14617
|
+
## Overview
|
|
14618
|
+
|
|
14619
|
+
Small card for a grid of thumbnails: mostly picture, at a fixed aspect ratio (square by
|
|
14620
|
+
default), with a title and an optional meta line below it or, with `captionPlacement="overlay"`,
|
|
14621
|
+
on a soft gradient over its bottom. Without a picture yet it shows a placeholder (an icon and a
|
|
14622
|
+
text); while one loads a shimmering skeleton; the picture fades in when it arrives; a failed load
|
|
14623
|
+
shows a short message. Make the whole tile one link or button with a `bfAction` in its title, as
|
|
14624
|
+
on `bf-card`: it then lifts on hover, zooms the picture slightly and shows the focus ring.
|
|
14625
|
+
`selected` draws a ring. The title clamps to two lines and always takes two, so tiles in a grid
|
|
14626
|
+
row are as tall whatever their titles.
|
|
14627
|
+
|
|
14628
|
+
A small card for a grid of thumbnails: 3D models with one render each, a board of reference pictures, a gallery. It is mostly picture, at a fixed aspect ratio (square by default), with a title and an optional meta line below it or over it on a soft gradient. Like `bf-media-card` it owns the whole life of the picture: a placeholder before there is one, a shimmering skeleton while it loads, a fade when it arrives and a short message when it fails.
|
|
14629
|
+
|
|
14630
|
+
## When to use
|
|
14631
|
+
|
|
14632
|
+
Use `bf-image-tile` for many pictures in a grid, where each one opens or selects something and the picture says more than the text. Use [bf-media-card](https://bitforge-ui.web.app/components/media-card) instead when a card also needs a body text, a footer with buttons or a select-several check button, and [bf-card](https://bitforge-ui.web.app/components/card) for a card without a picture. Open the full-size picture with [bf-image-viewer](https://bitforge-ui.web.app/components/image-viewer).
|
|
14633
|
+
|
|
14634
|
+
## Basic usage
|
|
14635
|
+
|
|
14636
|
+
Give each tile the picture's `src` and `alt`, a heading with a `bfAction` button or link as its title, and an optional `bfSlot="meta"` line such as a date or a count. A `bf-badge` in `bfSlot="badge"` sits in the picture's top start corner. `selected` marks the current tile with a ring. The titles differ in length, yet every tile in a row is as tall.
|
|
14637
|
+
|
|
14638
|
+
```ts
|
|
14639
|
+
import { Component, DestroyRef, inject, signal } from '@angular/core';
|
|
14640
|
+
import { BfActionDirective, BfBadgeComponent, BfImageTileComponent } from '@bitforgehq/ui';
|
|
14641
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
14642
|
+
import { Box, ImagePlus } from 'lucide';
|
|
14643
|
+
|
|
14644
|
+
/** One model in the grid, as the app tracks it. */
|
|
14645
|
+
interface Model {
|
|
14646
|
+
readonly id: string;
|
|
14647
|
+
readonly name: string;
|
|
14648
|
+
readonly src: string;
|
|
14649
|
+
readonly alt: string;
|
|
14650
|
+
readonly versions: number;
|
|
14651
|
+
readonly updated: string;
|
|
14652
|
+
}
|
|
14653
|
+
|
|
14654
|
+
/** How long the simulated render of the last model takes. */
|
|
14655
|
+
const RENDER_MS = 3000;
|
|
14656
|
+
|
|
14657
|
+
/**
|
|
14658
|
+
* The bf-image-tile overview.
|
|
14659
|
+
*
|
|
14660
|
+
* A grid of 3D models, one picture each: a click selects a tile (the ring, and "Selected" read
|
|
14661
|
+
* before its title), a badge counts the versions, the last picture is still rendering and fades
|
|
14662
|
+
* in when it arrives, and a model without a render yet shows the placeholder. The titles differ
|
|
14663
|
+
* in length, yet every tile in a row is as tall.
|
|
14664
|
+
*/
|
|
14665
|
+
@Component({
|
|
14666
|
+
selector: 'app-example-image-tile-overview',
|
|
14667
|
+
imports: [BfActionDirective, BfBadgeComponent, BfIconComponent, BfImageTileComponent],
|
|
14668
|
+
template: `
|
|
14669
|
+
<div class="grid grid-cols-2 gap-4 @md:grid-cols-4">
|
|
14670
|
+
@for (model of models(); track model.id) {
|
|
14671
|
+
<bf-image-tile
|
|
14672
|
+
[src]="model.src"
|
|
14673
|
+
[alt]="model.alt"
|
|
14674
|
+
[loading]="model.id === 'lantern' && rendering()"
|
|
14675
|
+
[selected]="model.id === current()"
|
|
14676
|
+
>
|
|
14677
|
+
<h2>
|
|
14678
|
+
<button bfAction type="button" (click)="current.set(model.id)">{{ model.name }}</button>
|
|
14679
|
+
</h2>
|
|
14680
|
+
<span bfSlot="meta">{{ model.updated }}</span>
|
|
14681
|
+
@if (model.versions > 1) {
|
|
14682
|
+
<bf-badge bfSlot="badge" variant="info"
|
|
14683
|
+
><bf-icon bfSlot="icon-start" [icon]="box" />{{ model.versions }} versions</bf-badge
|
|
14684
|
+
>
|
|
14685
|
+
}
|
|
14686
|
+
<bf-icon bfSlot="placeholder-icon" [icon]="imagePlus" />
|
|
14687
|
+
</bf-image-tile>
|
|
14688
|
+
}
|
|
14689
|
+
</div>
|
|
14690
|
+
<p aria-live="polite" class="text-caption text-text-muted mt-3">
|
|
14691
|
+
Selected: {{ currentName() }}
|
|
14692
|
+
</p>
|
|
14693
|
+
`,
|
|
14694
|
+
})
|
|
14695
|
+
export class ImageTileOverviewExampleComponent {
|
|
14696
|
+
protected readonly box = Box;
|
|
14697
|
+
protected readonly imagePlus = ImagePlus;
|
|
14698
|
+
protected readonly current = signal('ship');
|
|
14699
|
+
protected readonly rendering = signal(true);
|
|
14700
|
+
protected readonly models = signal<Model[]>([
|
|
14701
|
+
{
|
|
14702
|
+
id: 'ship',
|
|
14703
|
+
name: 'Harbour ship',
|
|
14704
|
+
src: 'media/harbour.svg',
|
|
14705
|
+
alt: 'A ship in a harbour',
|
|
14706
|
+
versions: 3,
|
|
14707
|
+
updated: 'Today',
|
|
14708
|
+
},
|
|
14709
|
+
{
|
|
14710
|
+
id: 'tower',
|
|
14711
|
+
name: 'Glacier watchtower with a long rope bridge',
|
|
14712
|
+
src: 'media/glacier.svg',
|
|
14713
|
+
alt: 'A tower on a glacier',
|
|
14714
|
+
versions: 1,
|
|
14715
|
+
updated: 'Yesterday',
|
|
14716
|
+
},
|
|
14717
|
+
{ id: 'well', name: 'Desert well', src: '', alt: '', versions: 1, updated: 'Last week' },
|
|
14718
|
+
{
|
|
14719
|
+
id: 'lantern',
|
|
14720
|
+
name: 'Forest lantern',
|
|
14721
|
+
src: '',
|
|
14722
|
+
alt: 'A lantern in a pine forest',
|
|
14723
|
+
versions: 2,
|
|
14724
|
+
updated: 'Rendering…',
|
|
14725
|
+
},
|
|
14726
|
+
]);
|
|
14727
|
+
|
|
14728
|
+
constructor() {
|
|
14729
|
+
const timer = setTimeout(() => {
|
|
14730
|
+
this.models.update((models) =>
|
|
14731
|
+
models.map((m) =>
|
|
14732
|
+
m.id === 'lantern' ? { ...m, src: 'media/forest.svg', updated: 'Just now' } : m,
|
|
14733
|
+
),
|
|
14734
|
+
);
|
|
14735
|
+
this.rendering.set(false);
|
|
14736
|
+
}, RENDER_MS);
|
|
14737
|
+
inject(DestroyRef).onDestroy(() => clearTimeout(timer));
|
|
14738
|
+
}
|
|
14739
|
+
|
|
14740
|
+
protected currentName(): string {
|
|
14741
|
+
return this.models().find((m) => m.id === this.current())?.name ?? '';
|
|
14742
|
+
}
|
|
14743
|
+
}
|
|
14744
|
+
```
|
|
14745
|
+
|
|
14746
|
+
## States
|
|
14747
|
+
|
|
14748
|
+
Without a `src` the tile shows a placeholder: the `bfSlot="placeholder-icon"` icon and `placeholderLabel`, or the `imageTileEmpty` label ("No image yet"). `loading` shows a `bf-skeleton` shimmer while there is no `src` yet (a render that is still being made), and the same shimmer shows while a `src` loads. The picture fades in over 260 ms (`bfFadeIn`) while the skeleton fades out under it; under reduced motion it appears at once. A picture that fails to load, or one the app could not make (`failed`), shows `failedLabel` or the `imageLoadFailed` label ("The image could not be loaded"). A new `src` starts over and forgets a load error; `failed` is the app's to clear. `loaded` and `errored` report the picture's events.
|
|
14749
|
+
|
|
14750
|
+
```ts
|
|
14751
|
+
import { Component } from '@angular/core';
|
|
14752
|
+
import { BfImageTileComponent } from '@bitforgehq/ui';
|
|
14753
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
14754
|
+
import { ImagePlus } from 'lucide';
|
|
14755
|
+
|
|
14756
|
+
/**
|
|
14757
|
+
* The four states of a tile side by side.
|
|
14758
|
+
*
|
|
14759
|
+
* Without `src` the placeholder shows (an icon and "No image yet"); `loading` shows the skeleton;
|
|
14760
|
+
* a `src` fades in when it loads; `failed` (or a load error) shows a short message.
|
|
14761
|
+
*/
|
|
14762
|
+
@Component({
|
|
14763
|
+
selector: 'app-example-image-tile-states',
|
|
14764
|
+
imports: [BfIconComponent, BfImageTileComponent],
|
|
14765
|
+
template: `
|
|
14766
|
+
<div class="grid grid-cols-2 gap-4 @md:grid-cols-4">
|
|
14767
|
+
<bf-image-tile>
|
|
14768
|
+
<h2>Placeholder</h2>
|
|
14769
|
+
<bf-icon bfSlot="placeholder-icon" [icon]="imagePlus" />
|
|
14770
|
+
</bf-image-tile>
|
|
14771
|
+
<bf-image-tile loading><h2>Loading</h2></bf-image-tile>
|
|
14772
|
+
<bf-image-tile src="media/forest.svg" alt="A pine forest under a pale sun">
|
|
14773
|
+
<h2>Loaded</h2>
|
|
14774
|
+
</bf-image-tile>
|
|
14775
|
+
<bf-image-tile failed failedLabel="Render failed"><h2>Failed</h2></bf-image-tile>
|
|
14776
|
+
</div>
|
|
14777
|
+
`,
|
|
14778
|
+
})
|
|
14779
|
+
export class ImageTileStatesExampleComponent {
|
|
14780
|
+
protected readonly imagePlus = ImagePlus;
|
|
14781
|
+
}
|
|
14782
|
+
```
|
|
14783
|
+
|
|
14784
|
+
## Caption over the picture
|
|
14785
|
+
|
|
14786
|
+
`captionPlacement="overlay"` lays the title and the meta line on a soft gradient over the bottom of the picture, so the tile is as tall as its picture and a board of pictures stays dense. The gradient is the `scrim` colour, darkest under the text, and the caption uses the dark theme's text colours, so it reads on light and dark pictures in both themes. `aspect` is `1/1` (default), `4/3`, `16/9` or `3/4` (portrait); the picture covers its area and is cropped to it.
|
|
14787
|
+
|
|
14788
|
+
```ts
|
|
14789
|
+
import { Component } from '@angular/core';
|
|
14790
|
+
import { BfActionDirective, BfBadgeComponent, BfImageTileComponent } from '@bitforgehq/ui';
|
|
14791
|
+
import { BfIconComponent } from '@bitforgehq/ui/icons';
|
|
14792
|
+
import { Check } from 'lucide';
|
|
14793
|
+
|
|
14794
|
+
/** One reference picture on the board. */
|
|
14795
|
+
interface Reference {
|
|
14796
|
+
readonly id: string;
|
|
14797
|
+
readonly title: string;
|
|
14798
|
+
readonly source: string;
|
|
14799
|
+
readonly src: string;
|
|
14800
|
+
readonly reference: boolean;
|
|
14801
|
+
}
|
|
14802
|
+
|
|
14803
|
+
/**
|
|
14804
|
+
* A board of reference pictures with the caption over the picture.
|
|
14805
|
+
*
|
|
14806
|
+
* `captionPlacement="overlay"` lays the title and meta line on a soft gradient over the bottom of
|
|
14807
|
+
* a 4:3 picture; each tile is a link, and a check badge marks the chosen references.
|
|
14808
|
+
*/
|
|
14809
|
+
@Component({
|
|
14810
|
+
selector: 'app-example-image-tile-overlay',
|
|
14811
|
+
imports: [BfActionDirective, BfBadgeComponent, BfIconComponent, BfImageTileComponent],
|
|
14812
|
+
template: `
|
|
14813
|
+
<div class="grid grid-cols-1 gap-4 @sm:grid-cols-2 @lg:grid-cols-3">
|
|
14814
|
+
@for (item of references; track item.id) {
|
|
14815
|
+
<bf-image-tile [src]="item.src" alt="" aspect="4/3" captionPlacement="overlay">
|
|
14816
|
+
<h2>
|
|
14817
|
+
<a bfAction [href]="'#' + item.id">{{ item.title }}</a>
|
|
14818
|
+
</h2>
|
|
14819
|
+
<span bfSlot="meta">{{ item.source }}</span>
|
|
14820
|
+
@if (item.reference) {
|
|
14821
|
+
<bf-badge bfSlot="badge" variant="success" emphasis="strong"
|
|
14822
|
+
><bf-icon bfSlot="icon-start" [icon]="check" />Reference</bf-badge
|
|
14823
|
+
>
|
|
14824
|
+
}
|
|
14825
|
+
</bf-image-tile>
|
|
14826
|
+
}
|
|
14827
|
+
</div>
|
|
14828
|
+
`,
|
|
14829
|
+
})
|
|
14830
|
+
export class ImageTileOverlayExampleComponent {
|
|
14831
|
+
protected readonly check = Check;
|
|
14832
|
+
protected readonly references: Reference[] = [
|
|
14833
|
+
{
|
|
14834
|
+
id: 'skyline',
|
|
14835
|
+
title: 'Skyline at dusk',
|
|
14836
|
+
source: 'Moodboard',
|
|
14837
|
+
src: 'media/skyline.svg',
|
|
14838
|
+
reference: true,
|
|
14839
|
+
},
|
|
14840
|
+
{
|
|
14841
|
+
id: 'desert',
|
|
14842
|
+
title: 'Dunes in the late afternoon, warm light from the west',
|
|
14843
|
+
source: 'Upload',
|
|
14844
|
+
src: 'media/desert.svg',
|
|
14845
|
+
reference: false,
|
|
14846
|
+
},
|
|
14847
|
+
{
|
|
14848
|
+
id: 'glacier',
|
|
14849
|
+
title: 'Glacier',
|
|
14850
|
+
source: 'Moodboard',
|
|
14851
|
+
src: 'media/glacier.svg',
|
|
14852
|
+
reference: true,
|
|
14853
|
+
},
|
|
14854
|
+
];
|
|
14855
|
+
}
|
|
14856
|
+
```
|
|
14857
|
+
|
|
14858
|
+
## Grids of tiles
|
|
14859
|
+
|
|
14860
|
+
Put the tiles straight into a CSS grid (`grid grid-cols-2 gap-4`, more columns on wider screens). Each tile stretches to its row. The title is clamped to two lines with an ellipsis and always takes the height of two, so a short title and a long one make tiles of the same height; give every tile in a grid a meta line, or none, for the same reason. Below the picture the caption adds 12 px of padding on each side.
|
|
14861
|
+
|
|
14862
|
+
## Desktop
|
|
14863
|
+
|
|
14864
|
+
A tile with a `bfAction` shows the pointer and, while the pointer is over it, lifts by 2 px to `shadow-floating` and zooms its picture by 5 % inside its frame; pressing it sets it back down. The lift runs on the base timing (200 ms) and the zoom on the slow timing (260 ms). Under reduced motion neither moves: only the shadow changes. The focus ring shows around the whole tile while its action has keyboard focus, not after a click. Text under the stretched action cannot be selected with the mouse.
|
|
14865
|
+
|
|
14866
|
+
## Theming
|
|
14867
|
+
|
|
14868
|
+
- Tile: `bg-surface`, `text-text`, `rounded-card`, `shadow-raised` (`shadow-floating` on hover).
|
|
14869
|
+
- Picture area: `bg-surface-sunken`; the skeleton is a `bf-skeleton` rect; the placeholder and failure texts are `text-caption text-text-muted`.
|
|
14870
|
+
- Caption: the title is `text-label-sm` bold, the meta line `text-caption text-text-muted`; over the picture, a `scrim` gradient (80 % to transparent) with the `.dark` text tokens.
|
|
14871
|
+
- Selection: a 2 px `primary` outline 2 px outside the tile.
|
|
14872
|
+
- Forced colours: a 1 px `CanvasText` border keeps each tile's edge; the selection ring is `Highlight`.
|
|
14873
|
+
- Focus: `ring-4 ring-ring` around the tile.
|
|
14874
|
+
- Motion: `duration-base` (200 ms) for the lift, shadow and selection ring, `duration-slow` (260 ms) for the zoom and the picture's fade, on `ease-md`; no movement under reduced motion.
|
|
14875
|
+
|
|
14876
|
+
## Accessibility
|
|
14877
|
+
|
|
14878
|
+
The tile is a plain `<div>`; the picture is an `<img>` with `alt`. When the title names the picture well enough, as in a board of references, leave `alt` empty so it is not read twice. A clickable tile follows the stretched-link pattern of `bf-card`: the only interactive element is the `bfAction` link or button in its title, and its name is the title. A selected tile starts that name with the `imageTileSelected` label, so a screen reader hears "Selected, Harbour ship"; without a `bfAction` the same text is read in the tile. While loading, the picture area is `aria-busy="true"` with a visually hidden "Loading", not a live region, so a grid of loading tiles is not announced tile by tile. Under forced colours the selection ring becomes a `Highlight` outline and the focus indicator a system outline around the tile.
|
|
14879
|
+
|
|
14880
|
+
### Keyboard
|
|
14881
|
+
|
|
14882
|
+
| Key | Action |
|
|
14883
|
+
| ---------------- | ---------------------------------------------------------- |
|
|
14884
|
+
| <kbd>Tab</kbd> | Moves to the tile's `bfAction` link or button |
|
|
14885
|
+
| <kbd>Enter</kbd> | Follows a `bfAction` link or activates a `bfAction` button |
|
|
14886
|
+
| <kbd>Space</kbd> | Activates a `bfAction` button; a link ignores it |
|
|
14887
|
+
|
|
14888
|
+
## Inputs
|
|
14889
|
+
|
|
14890
|
+
| Name | Type | Default | Description |
|
|
14891
|
+
| --- | --- | --- | --- |
|
|
14892
|
+
| `src` | `string` | `''` | URL of the picture; empty shows the placeholder, or the skeleton while `loading`. A new URL starts over: skeleton, fade-in, and a failed load is forgotten. |
|
|
14893
|
+
| `alt` | `string` | `''` | Alternative text of the picture; empty marks it decorative (the title names the tile). |
|
|
14894
|
+
| `aspect` | `BfImageTileAspect` (`'1/1' \| '4/3' \| '16/9' \| '3/4'`) | `'1/1'` | Aspect ratio of the picture: `1/1` (default), `4/3`, `16/9` or `3/4`. |
|
|
14895
|
+
| `captionPlacement` | `BfImageTileCaptionPlacement` (`'below' \| 'overlay'`) | `'below'` | Where the title and the meta line sit: `below` the picture (default) or `overlay`, on a gradient over its bottom. |
|
|
14896
|
+
| `loading` | `boolean` | `false` | Shows the skeleton while there is no `src` yet (a picture that is still being made). |
|
|
14897
|
+
| `failed` | `boolean` | `false` | Shows the failure message (the app could not make the picture); the app clears it. A load error of `src` shows it too, until `src` changes. |
|
|
14898
|
+
| `selected` | `boolean` | `false` | Marks the tile as the selected one: a ring around it, and "Selected" read before its title. |
|
|
14899
|
+
| `placeholderLabel` | `string` | `''` | Placeholder text while there is no picture; empty uses `provideBfLabels()`'s `imageTileEmpty` ("No image yet"). |
|
|
14900
|
+
| `failedLabel` | `string` | `''` | Failure message; empty uses `provideBfLabels()`'s `imageLoadFailed`. |
|
|
14901
|
+
|
|
14902
|
+
## Outputs
|
|
14903
|
+
|
|
14904
|
+
| Name | Payload | Description |
|
|
14905
|
+
| --- | --- | --- |
|
|
14906
|
+
| `loaded` | `void` | The picture of `src` has loaded. |
|
|
14907
|
+
| `errored` | `void` | The picture of `src` failed to load (the tile shows the failure message). |
|
|
14908
|
+
|
|
14909
|
+
## Slots
|
|
14910
|
+
|
|
14911
|
+
- `placeholder-icon`: The placeholder's icon, shown with its text while there is no picture.
|
|
14912
|
+
- `badge`: A `bf-badge` in the picture's top start corner.
|
|
14913
|
+
- `default`: The title: a heading holding the tile's `bfAction` link or button.
|
|
14914
|
+
- `meta`: A short line under the title, such as a date or a count.
|
|
14915
|
+
|
|
14916
|
+
## Notes
|
|
14917
|
+
|
|
14918
|
+
### Do
|
|
14919
|
+
|
|
14920
|
+
- Use a heading for the title, at the level that fits the page, with the `bfAction` inside it.
|
|
14921
|
+
- Set `loading` while a picture is being made, so the tile holds its place.
|
|
14922
|
+
- Keep the meta line short: a date, a count, a source.
|
|
14923
|
+
|
|
14924
|
+
### Don't
|
|
14925
|
+
|
|
14926
|
+
- Don't put more than one `bfAction` in a tile, or other buttons in it; use a `bf-media-card` for a card with actions.
|
|
14927
|
+
- Don't project your own `<img>` for the picture; pass `src`, so the tile handles loading, fading and failing.
|
|
14928
|
+
- Don't rely on the ring alone to mark the selected tile in your own text: the tile already tells assistive technology.
|
|
14929
|
+
|
|
14930
|
+
## Migration notes
|
|
14931
|
+
|
|
14932
|
+
### 0.5.0
|
|
14933
|
+
|
|
14934
|
+
New: `bf-image-tile`, a small card for grids of thumbnails (3D models with one render each, a
|
|
14935
|
+
board of reference pictures). It is mostly picture, at a fixed `aspect` (new type
|
|
14936
|
+
`BfImageTileAspect`: `'1/1'` (default), `'4/3'`, `'16/9'`, `'3/4'`), with the title (default slot,
|
|
14937
|
+
a heading holding a `bfAction` link or button) and an optional `bfSlot="meta"` line below the
|
|
14938
|
+
picture, or with `captionPlacement="overlay"` (new type `BfImageTileCaptionPlacement`: `'below'`
|
|
14939
|
+
(default), `'overlay'`) on a soft `scrim` gradient over its bottom. States share
|
|
14940
|
+
`bf-media-card`'s logic: without `src` a placeholder (`bfSlot="placeholder-icon"` and
|
|
14941
|
+
`placeholderLabel`, default: the new label `imageTileEmpty`, "No image yet"); `loading`, and a
|
|
14942
|
+
`src` that is loading, show a `bf-skeleton` shimmer (`aria-busy`); the picture fades in over
|
|
14943
|
+
260 ms; a load error or `failed` shows `failedLabel` (default `imageLoadFailed`). `loaded` and
|
|
14944
|
+
`errored` report the picture's events. A `bfAction` makes the whole tile one link or button: the
|
|
14945
|
+
pointer, a 2 px lift to `shadow-floating` and a 5 % zoom of the picture on hover (no movement
|
|
14946
|
+
under reduced motion), and the focus ring. `selected` draws a 2 px primary ring and starts the
|
|
14947
|
+
action's name with the new label `imageTileSelected` ("Selected, Harbour ship"). `bfSlot="badge"`
|
|
14948
|
+
puts a `bf-badge` in the picture's top start corner. The title clamps to two lines and always
|
|
14949
|
+
takes two, so tiles in a grid row are as tall. `@bitforgehq/ui/testing`: new
|
|
14950
|
+
`BfImageTileHarness` (`getTitle()`, `getMeta()`, `getState()`, `getSrc()`, `getAlt()`,
|
|
14951
|
+
`getPlaceholderText()`, `getFailedMessage()`, `isSelected()`, `isClickable()`, `click()`,
|
|
14952
|
+
`loadImage()`, `failImage()`).
|
|
14953
|
+
|
|
14954
|
+
---
|
|
14955
|
+
|
|
14452
14956
|
# bf-image-viewer
|
|
14453
14957
|
|
|
14454
14958
|
Full-screen image viewer that zooms from a thumbnail and steps through a set
|
|
@@ -17576,6 +18080,55 @@ export class MenuButtonSizesExampleComponent {
|
|
|
17576
18080
|
}
|
|
17577
18081
|
```
|
|
17578
18082
|
|
|
18083
|
+
## Full width
|
|
18084
|
+
|
|
18085
|
+
`fullWidth` stretches the host and its button to the width of their container, as on
|
|
18086
|
+
`bf-button`: in a grid of actions, a "More" menu button then fills its cell like the buttons
|
|
18087
|
+
beside it. The label and caret stay centred. It does nothing with `iconOnly`, which stays a
|
|
18088
|
+
square.
|
|
18089
|
+
|
|
18090
|
+
```ts
|
|
18091
|
+
import { Component, signal } from '@angular/core';
|
|
18092
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
18093
|
+
import {
|
|
18094
|
+
BfMenuButtonComponent,
|
|
18095
|
+
BfMenuComponent,
|
|
18096
|
+
BfMenuItemDirective,
|
|
18097
|
+
} from '@bitforgehq/ui/overlay';
|
|
18098
|
+
|
|
18099
|
+
/**
|
|
18100
|
+
* A two-column grid of actions on a card, where a menu button fills its cell like the buttons
|
|
18101
|
+
* beside it.
|
|
18102
|
+
*
|
|
18103
|
+
* `fullWidth` stretches the host and its button, as on `bf-button`.
|
|
18104
|
+
*/
|
|
18105
|
+
@Component({
|
|
18106
|
+
selector: 'app-example-menu-button-full-width',
|
|
18107
|
+
imports: [BfButtonComponent, BfMenuButtonComponent, BfMenuComponent, BfMenuItemDirective],
|
|
18108
|
+
template: `
|
|
18109
|
+
<div class="grid max-w-sm grid-cols-2 gap-2">
|
|
18110
|
+
<bf-button variant="outline" fullWidth (clicked)="result.set('Opened.')">Open</bf-button>
|
|
18111
|
+
<bf-button variant="outline" fullWidth (clicked)="result.set('Duplicated.')"
|
|
18112
|
+
>Duplicate</bf-button
|
|
18113
|
+
>
|
|
18114
|
+
<bf-button variant="outline" fullWidth (clicked)="result.set('Shared.')">Share</bf-button>
|
|
18115
|
+
<bf-menu-button [menu]="more" fullWidth>More</bf-menu-button>
|
|
18116
|
+
</div>
|
|
18117
|
+
<p aria-live="polite" class="text-caption text-text-muted mt-3">{{ result() }}</p>
|
|
18118
|
+
|
|
18119
|
+
<ng-template #more>
|
|
18120
|
+
<bf-menu>
|
|
18121
|
+
<button bfMenuItem (triggered)="result.set('Renamed.')">Rename</button>
|
|
18122
|
+
<button bfMenuItem (triggered)="result.set('Archived.')">Archive</button>
|
|
18123
|
+
</bf-menu>
|
|
18124
|
+
</ng-template>
|
|
18125
|
+
`,
|
|
18126
|
+
})
|
|
18127
|
+
export class MenuButtonFullWidthExampleComponent {
|
|
18128
|
+
protected readonly result = signal('');
|
|
18129
|
+
}
|
|
18130
|
+
```
|
|
18131
|
+
|
|
17579
18132
|
## Desktop
|
|
17580
18133
|
|
|
17581
18134
|
Hover and focus look like `bf-button` (or `bf-icon-button` with `iconOnly`): the fill, border or text darkens on hover with the pointer cursor, and the focus ring shows on keyboard focus only. A click opens the menu; a second click on the button closes it. Under a fine pointer the visible box is the hit area. Desktop density is the default `md` step; `provideBfDefaults({ size: 'sm' })`, or `{ size: 'sm' }` in a component's `providers`, makes a dense toolbar, table or sidebar ([density guide](https://bitforge-ui.web.app/density)).
|
|
@@ -17623,6 +18176,7 @@ The keys inside the open menu are on the [bf-menu](https://bitforge-ui.web.app/c
|
|
|
17623
18176
|
| `placement` | `BfMenuPlacement` (`'below-start' \| 'below-end' \| 'above-start' \| 'above-end'`) | `'below-start'` | Where the menu opens against the button; it flips when it would overflow. |
|
|
17624
18177
|
| `label` | `string` | `''` | Accessible name (`aria-label`). Required with `iconOnly`; with a visible label leave it empty or start it with the visible text. |
|
|
17625
18178
|
| `iconOnly` | `boolean` | `false` | Square button holding only the projected icon, like `bf-icon-button`; set `label`. |
|
|
18179
|
+
| `fullWidth` | `boolean` | `false` | Stretches the host and the button to the width of their container, as `bf-button`'s `fullWidth`; the label and caret stay centred. Ignored with `iconOnly`. |
|
|
17626
18180
|
| `disabled` | `boolean` | `false` | Disables the button; the menu cannot open. |
|
|
17627
18181
|
|
|
17628
18182
|
## Outputs
|
|
@@ -17651,6 +18205,12 @@ The keys inside the open menu are on the [bf-menu](https://bitforge-ui.web.app/c
|
|
|
17651
18205
|
|
|
17652
18206
|
## Migration notes
|
|
17653
18207
|
|
|
18208
|
+
### 0.5.0
|
|
18209
|
+
|
|
18210
|
+
New `fullWidth` input, as on `bf-button`: the host and its button stretch to the width of their
|
|
18211
|
+
container (label and caret stay centred), so app CSS no longer needs to reach into the inner
|
|
18212
|
+
`<button>`. Ignored with `iconOnly`.
|
|
18213
|
+
|
|
17654
18214
|
### 0.3.0
|
|
17655
18215
|
|
|
17656
18216
|
New `bf-menu-button` in `@bitforgehq/ui/overlay`: a native `<button>` that opens a `bf-menu`
|
|
@@ -19968,7 +20528,9 @@ export class PaginationOverviewExampleComponent {
|
|
|
19968
20528
|
Page navigation for a long list or table: Previous and Next buttons around the page numbers,
|
|
19969
20529
|
the current one marked, the hidden runs shown as an ellipsis. `compact` shows "Page 3 of 12"
|
|
19970
20530
|
between the arrows instead, for narrow screens. The page count comes from `pageCount`, or from
|
|
19971
|
-
`total` and `pageSize`; the consumer shows the rows of `page`.
|
|
20531
|
+
`total` and `pageSize`; the consumer shows the rows of `page`. A compact pagination with its own
|
|
20532
|
+
`positionLabel`, `previousLabel` and `nextLabel` steps through anything numbered, such as the
|
|
20533
|
+
versions of a model ("‹ v3 of 7 ›").
|
|
19972
20534
|
|
|
19973
20535
|
Pagination splits a long list or table into pages and lets the user move between them: Previous and Next buttons around the page numbers, the current page marked, the first and the last page always in reach, and an ellipsis for each run of pages in between. `bf-pagination` holds only the page number. You bind `[(page)]` and show that page's rows, sliced on the client or fetched from the server. It works the same in both shells: in a desktop web app and in the Capacitor shell.
|
|
19974
20536
|
|
|
@@ -20015,6 +20577,41 @@ export class PaginationCompactExampleComponent {
|
|
|
20015
20577
|
}
|
|
20016
20578
|
```
|
|
20017
20579
|
|
|
20580
|
+
## Stepping through versions
|
|
20581
|
+
|
|
20582
|
+
A compact pagination also steps through anything else that is numbered, such as the versions of a model: "‹ v3 of 7 ›". `positionLabel` takes a `BfPaginationPositionLabel`, a function from the page and the count to the position text (`` (page, count) => `v${page} of ${count}` ``); unset, the text comes from `pageOf`. `previousLabel` and `nextLabel` name the arrows ("Previous version") where "Previous page" would be wrong, and `label` names the navigation. Use it instead of a [bf-segmented](https://bitforge-ui.web.app/components/segmented) control once there are more than four versions; the arrows are disabled at the ends, as on any pagination.
|
|
20583
|
+
|
|
20584
|
+
```ts
|
|
20585
|
+
import { Component, signal } from '@angular/core';
|
|
20586
|
+
import { BfPaginationComponent, type BfPaginationPositionLabel } from '@bitforgehq/ui';
|
|
20587
|
+
|
|
20588
|
+
/**
|
|
20589
|
+
* A compact pager that steps through the versions of a model, "‹ v3 of 7 ›".
|
|
20590
|
+
*
|
|
20591
|
+
* `positionLabel` writes the position text; `previousLabel` and `nextLabel` name the arrows.
|
|
20592
|
+
*/
|
|
20593
|
+
@Component({
|
|
20594
|
+
selector: 'app-example-pagination-versions',
|
|
20595
|
+
imports: [BfPaginationComponent],
|
|
20596
|
+
template: `
|
|
20597
|
+
<bf-pagination
|
|
20598
|
+
compact
|
|
20599
|
+
size="sm"
|
|
20600
|
+
label="Versions"
|
|
20601
|
+
previousLabel="Previous version"
|
|
20602
|
+
nextLabel="Next version"
|
|
20603
|
+
[positionLabel]="versionOf"
|
|
20604
|
+
[pageCount]="7"
|
|
20605
|
+
[(page)]="version"
|
|
20606
|
+
/>
|
|
20607
|
+
`,
|
|
20608
|
+
})
|
|
20609
|
+
export class PaginationVersionsExampleComponent {
|
|
20610
|
+
readonly version = signal(3);
|
|
20611
|
+
protected readonly versionOf: BfPaginationPositionLabel = (page, count) => `v${page} of ${count}`;
|
|
20612
|
+
}
|
|
20613
|
+
```
|
|
20614
|
+
|
|
20018
20615
|
## With a table
|
|
20019
20616
|
|
|
20020
20617
|
Put the pagination under the table it pages, aligned to the inline end or centred, and show only the current page's rows: slice them in a `computed()` from `page`, or fetch the page from the server in a `httpResource()` keyed on it. When the user sorts or filters, set `page` back to 1. The [table](https://bitforge-ui.web.app/components/table) page has a paged example.
|
|
@@ -20056,6 +20653,9 @@ At the first and the last page the arrow is disabled with `aria-disabled="true"`
|
|
|
20056
20653
|
| `pageSize` | `number` | `10` | Items per page, used with `total` while `pageCount` is 0. |
|
|
20057
20654
|
| `siblings` | `number` | `1` | How many pages to show on each side of the current one. |
|
|
20058
20655
|
| `compact` | `boolean` | `false` | Shows "Page 3 of 12" between the arrows instead of the page buttons, for narrow screens. |
|
|
20656
|
+
| `positionLabel` | `BfPaginationPositionLabel \| undefined` | `undefined` | Writes the compact position text from the page and the count, such as "v3 of 7"; unset uses `provideBfLabels()`'s `pageOf` ("Page 3 of 12"). Only used with `compact`. |
|
|
20657
|
+
| `previousLabel` | `string` | `''` | Accessible name of the previous button, such as "Previous version"; empty falls back to `provideBfLabels()`'s `previousPage` ("Previous page"). |
|
|
20658
|
+
| `nextLabel` | `string` | `''` | Accessible name of the next button, such as "Next version"; empty falls back to `provideBfLabels()`'s `nextPage` ("Next page"). |
|
|
20059
20659
|
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Button height step: sm 32, md 40, lg 48 px (44 px hit area under a coarse pointer); unset follows `provideBfDefaults()` (`controlSize`, `md` by default). |
|
|
20060
20660
|
| `label` | `string` | `''` | Accessible name of the navigation; empty falls back to `provideBfLabels()`'s `pagination` ("Pagination"). |
|
|
20061
20661
|
| `page` | `number` | `1` | The current page, 1-based; two-way bindable. The buttons set it; show the rows of this page. Two-way: `[(page)]`. |
|
|
@@ -20085,6 +20685,14 @@ Goes to a page: sets `page` when it is another page within 1…count; else does
|
|
|
20085
20685
|
|
|
20086
20686
|
## Migration notes
|
|
20087
20687
|
|
|
20688
|
+
### 0.5.0
|
|
20689
|
+
|
|
20690
|
+
New `positionLabel` input (new type `BfPaginationPositionLabel`, `(page, count) => string`) that
|
|
20691
|
+
writes a compact pagination's position text, such as "v3 of 7"; unset it is the `pageOf` label as
|
|
20692
|
+
before. New `previousLabel` and `nextLabel` inputs name the arrows ("Previous version"); empty
|
|
20693
|
+
they fall back to `previousPage` and `nextPage`. Together they make a compact pager for stepping
|
|
20694
|
+
through versions.
|
|
20695
|
+
|
|
20088
20696
|
### 0.3.1
|
|
20089
20697
|
|
|
20090
20698
|
New `bf-pagination` in `@bitforgehq/ui`, page navigation for both shells: a `nav` (named by
|
|
@@ -33254,6 +33862,67 @@ type BfImageInputShape = 'round' | 'square'
|
|
|
33254
33862
|
|
|
33255
33863
|
---
|
|
33256
33864
|
|
|
33865
|
+
# BfImageTileAspect
|
|
33866
|
+
|
|
33867
|
+
The four aspect ratios of an image tile's picture, square by default
|
|
33868
|
+
|
|
33869
|
+
Type from `@bitforgehq/ui` · since 0.5.0
|
|
33870
|
+
|
|
33871
|
+
The aspect ratio of a `bf-image-tile`'s picture: square (default), 4:3, 16:9 or 3:4.
|
|
33872
|
+
|
|
33873
|
+
```ts
|
|
33874
|
+
import { type BfImageTileAspect } from '@bitforgehq/ui';
|
|
33875
|
+
```
|
|
33876
|
+
|
|
33877
|
+
## Signature
|
|
33878
|
+
|
|
33879
|
+
```ts
|
|
33880
|
+
type BfImageTileAspect = '1/1' | '4/3' | '16/9' | '3/4'
|
|
33881
|
+
```
|
|
33882
|
+
|
|
33883
|
+
## Values
|
|
33884
|
+
|
|
33885
|
+
- `1/1`
|
|
33886
|
+
- `4/3`
|
|
33887
|
+
- `16/9`
|
|
33888
|
+
- `3/4`
|
|
33889
|
+
|
|
33890
|
+
## Used by
|
|
33891
|
+
|
|
33892
|
+
- [bf-image-tile](../components/image-tile.md)
|
|
33893
|
+
|
|
33894
|
+
---
|
|
33895
|
+
|
|
33896
|
+
# BfImageTileCaptionPlacement
|
|
33897
|
+
|
|
33898
|
+
Where an image tile's caption sits, below the picture or over it
|
|
33899
|
+
|
|
33900
|
+
Type from `@bitforgehq/ui` · since 0.5.0
|
|
33901
|
+
|
|
33902
|
+
Where a `bf-image-tile`'s title and meta line sit: `below` the picture (default), or `overlay`,
|
|
33903
|
+
on a soft gradient over the bottom of the picture.
|
|
33904
|
+
|
|
33905
|
+
```ts
|
|
33906
|
+
import { type BfImageTileCaptionPlacement } from '@bitforgehq/ui';
|
|
33907
|
+
```
|
|
33908
|
+
|
|
33909
|
+
## Signature
|
|
33910
|
+
|
|
33911
|
+
```ts
|
|
33912
|
+
type BfImageTileCaptionPlacement = 'below' | 'overlay'
|
|
33913
|
+
```
|
|
33914
|
+
|
|
33915
|
+
## Values
|
|
33916
|
+
|
|
33917
|
+
- `below`
|
|
33918
|
+
- `overlay`
|
|
33919
|
+
|
|
33920
|
+
## Used by
|
|
33921
|
+
|
|
33922
|
+
- [bf-image-tile](../components/image-tile.md)
|
|
33923
|
+
|
|
33924
|
+
---
|
|
33925
|
+
|
|
33257
33926
|
# BfInputAutocapitalize
|
|
33258
33927
|
|
|
33259
33928
|
A text field's capitalisation setting, a mode or empty to leave it off
|
|
@@ -33561,7 +34230,9 @@ import { type BfLabels } from '@bitforgehq/ui';
|
|
|
33561
34230
|
| `chatSend` | `string` | `'Send'` | bf-chat-composer's send button. |
|
|
33562
34231
|
| `chatStop` | `string` | `'Stop'` | bf-chat-composer's stop button, shown in place of send while a reply runs. |
|
|
33563
34232
|
| `mediaCardSelect` | `string` | `'Select'` | bf-media-card's select button, read before the card's name ("Select Starfall"). |
|
|
33564
|
-
| `imageLoadFailed` | `string` | `'The image could not be loaded'` | bf-media-card's and bf-image-viewer's message for an image that failed to load. |
|
|
34233
|
+
| `imageLoadFailed` | `string` | `'The image could not be loaded'` | bf-media-card's, bf-image-tile's and bf-image-viewer's message for an image that failed to load. |
|
|
34234
|
+
| `imageTileEmpty` | `string` | `'No image yet'` | bf-image-tile's placeholder text while it has no image yet. |
|
|
34235
|
+
| `imageTileSelected` | `string` | `'Selected'` | bf-image-tile's selected state, read before the tile's title ("Selected, Dragon"). |
|
|
33565
34236
|
| `imageViewer` | `string` | `'Image viewer'` | bf-image-viewer's name. |
|
|
33566
34237
|
| `imageViewerPrevious` | `string` | `'Previous image'` | bf-image-viewer's button that shows the previous image. |
|
|
33567
34238
|
| `imageViewerNext` | `string` | `'Next image'` | bf-image-viewer's button that shows the next image. |
|
|
@@ -33694,6 +34365,31 @@ type BfPageHeaderHeadingLevel = 1 | 2
|
|
|
33694
34365
|
|
|
33695
34366
|
---
|
|
33696
34367
|
|
|
34368
|
+
# BfPaginationPositionLabel
|
|
34369
|
+
|
|
34370
|
+
Function that writes a compact pager's position text, such as "v3 of 7"
|
|
34371
|
+
|
|
34372
|
+
Type from `@bitforgehq/ui` · since 0.5.0
|
|
34373
|
+
|
|
34374
|
+
Writes a compact `bf-pagination`'s position text from the current page and the page count, such
|
|
34375
|
+
as "v3 of 7" for page 3 of 7.
|
|
34376
|
+
|
|
34377
|
+
```ts
|
|
34378
|
+
import { type BfPaginationPositionLabel } from '@bitforgehq/ui';
|
|
34379
|
+
```
|
|
34380
|
+
|
|
34381
|
+
## Signature
|
|
34382
|
+
|
|
34383
|
+
```ts
|
|
34384
|
+
type BfPaginationPositionLabel = (page: number, count: number) => string
|
|
34385
|
+
```
|
|
34386
|
+
|
|
34387
|
+
## Used by
|
|
34388
|
+
|
|
34389
|
+
- [bf-pagination](../components/pagination.md)
|
|
34390
|
+
|
|
34391
|
+
---
|
|
34392
|
+
|
|
33697
34393
|
# BfProgressSize
|
|
33698
34394
|
|
|
33699
34395
|
The two track heights of a progress bar, thin and regular
|
|
@@ -34929,7 +35625,8 @@ every string. In a component's `providers` it covers that sub-tree's widgets and
|
|
|
34929
35625
|
`fileRejectedType`, `fileRejectedSize`, `fileRejectedCount`,
|
|
34930
35626
|
`imageInputChoose`, `imageInputUpload`, `imageInputReplace`, `imageInputRemove`,
|
|
34931
35627
|
`chatThread`, `chatJumpToLatest`, `chatMessageStopped`, `chatMessageFailed`, `chatComposer`,
|
|
34932
|
-
`chatSend`, `chatStop`, `mediaCardSelect`, `imageLoadFailed`, `
|
|
35628
|
+
`chatSend`, `chatStop`, `mediaCardSelect`, `imageLoadFailed`, `imageTileEmpty`,
|
|
35629
|
+
`imageTileSelected`, `validation`); the overlay
|
|
34933
35630
|
services (`ok`, `cancel`, `actions`, `sheet`, `modal`, `close`, `expand`, `collapse`,
|
|
34934
35631
|
`sheetPosition`, `dialog`, `notifications`, `imageViewer`, `imageViewerPrevious`,
|
|
34935
35632
|
`imageViewerNext`, `imageViewerPosition`) are root singletons and pick it up only when the caller passes its injector:
|
|
@@ -41685,6 +42382,79 @@ The reason the last pick was refused, until the next pick or Remove.
|
|
|
41685
42382
|
|
|
41686
42383
|
---
|
|
41687
42384
|
|
|
42385
|
+
# BfImageTileHarness
|
|
42386
|
+
|
|
42387
|
+
Test harness for an image tile: its picture state, title, meta, selection and action
|
|
42388
|
+
|
|
42389
|
+
Class from `@bitforgehq/ui/testing` · since 0.5.0
|
|
42390
|
+
|
|
42391
|
+
Harness for `bf-image-tile`. jsdom never loads images, so `loadImage()` and `failImage()` fire
|
|
42392
|
+
the picture's `load` and `error` events the way a browser would; `getState()` then tells what
|
|
42393
|
+
the tile shows. `click()` clicks the tile's `bfAction` link or button.
|
|
42394
|
+
|
|
42395
|
+
```ts
|
|
42396
|
+
import { BfImageTileHarness } from '@bitforgehq/ui/testing';
|
|
42397
|
+
```
|
|
42398
|
+
|
|
42399
|
+
## Signature
|
|
42400
|
+
|
|
42401
|
+
```ts
|
|
42402
|
+
class BfImageTileHarness extends ComponentHarness
|
|
42403
|
+
```
|
|
42404
|
+
|
|
42405
|
+
## Methods
|
|
42406
|
+
|
|
42407
|
+
### `getTitle(): Promise<string>`
|
|
42408
|
+
|
|
42409
|
+
The tile's title: the text of its default slot.
|
|
42410
|
+
|
|
42411
|
+
### `getMeta(): Promise<string>`
|
|
42412
|
+
|
|
42413
|
+
The meta line under the title.
|
|
42414
|
+
|
|
42415
|
+
### `getState(): Promise<'empty' | 'loading' | 'loaded' | 'failed'>`
|
|
42416
|
+
|
|
42417
|
+
What the picture area shows: `empty` (the placeholder), `loading` (the skeleton), `loaded`
|
|
42418
|
+
(the picture) or `failed` (the failure message).
|
|
42419
|
+
|
|
42420
|
+
### `getSrc(): Promise<string>`
|
|
42421
|
+
|
|
42422
|
+
The URL of the picture.
|
|
42423
|
+
|
|
42424
|
+
### `getAlt(): Promise<string>`
|
|
42425
|
+
|
|
42426
|
+
The alternative text of the picture.
|
|
42427
|
+
|
|
42428
|
+
### `getPlaceholderText(): Promise<string>`
|
|
42429
|
+
|
|
42430
|
+
The placeholder text while there is no picture.
|
|
42431
|
+
|
|
42432
|
+
### `getFailedMessage(): Promise<string>`
|
|
42433
|
+
|
|
42434
|
+
The failure message.
|
|
42435
|
+
|
|
42436
|
+
### `isSelected(): Promise<boolean>`
|
|
42437
|
+
|
|
42438
|
+
Whether the tile is marked selected.
|
|
42439
|
+
|
|
42440
|
+
### `isClickable(): Promise<boolean>`
|
|
42441
|
+
|
|
42442
|
+
Whether the tile is clickable (has a `bfAction` link or button).
|
|
42443
|
+
|
|
42444
|
+
### `click(): Promise<void>`
|
|
42445
|
+
|
|
42446
|
+
Clicks the tile's `bfAction` link or button. Throws for a tile without one.
|
|
42447
|
+
|
|
42448
|
+
### `loadImage(): Promise<void>`
|
|
42449
|
+
|
|
42450
|
+
Fires the picture's `load` event, as a browser does when it arrives. Throws without one.
|
|
42451
|
+
|
|
42452
|
+
### `failImage(): Promise<void>`
|
|
42453
|
+
|
|
42454
|
+
Fires the picture's `error` event, as a browser does when it fails. Throws without one.
|
|
42455
|
+
|
|
42456
|
+
---
|
|
42457
|
+
|
|
41688
42458
|
# BfImageViewerHarness
|
|
41689
42459
|
|
|
41690
42460
|
Test harness for an image viewer: its image, index, caption, steps and close
|