@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
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
# bf-media-card
|
|
2
|
+
|
|
3
|
+
Card with an image that loads, fades in or fails, plus a body and actions
|
|
4
|
+
|
|
5
|
+
`BfMediaCardComponent` from `@bitforgehq/ui` · Data display · since 0.5.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, DestroyRef, type WritableSignal, inject, signal } from '@angular/core';
|
|
11
|
+
import { BfButtonComponent, BfMediaCardComponent } from '@bitforgehq/ui';
|
|
12
|
+
|
|
13
|
+
/** One generated picture as the app tracks it. */
|
|
14
|
+
interface Generation {
|
|
15
|
+
readonly src: string;
|
|
16
|
+
readonly generating: boolean;
|
|
17
|
+
readonly failed: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** How long the simulated generation takes (a real one takes 30 to 90 seconds). */
|
|
21
|
+
const GENERATION_MS = 4000;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The bf-media-card overview.
|
|
25
|
+
*
|
|
26
|
+
* Live demo of `bf-media-card` with pictures that take a while to exist: the first is being drawn
|
|
27
|
+
* (a timer stands in for the image service) and fades in when it arrives; the second fails, and
|
|
28
|
+
* its Retry button starts it again; the third is already there.
|
|
29
|
+
*/
|
|
30
|
+
@Component({
|
|
31
|
+
selector: 'app-example-media-card-overview',
|
|
32
|
+
imports: [BfButtonComponent, BfMediaCardComponent],
|
|
33
|
+
template: `
|
|
34
|
+
<div class="grid gap-4 @md:grid-cols-3">
|
|
35
|
+
<bf-media-card
|
|
36
|
+
alt="A neon city skyline at dusk"
|
|
37
|
+
loadingLabel="Drawing…"
|
|
38
|
+
[src]="starfall().src"
|
|
39
|
+
[loading]="starfall().generating"
|
|
40
|
+
>
|
|
41
|
+
<h2 class="text-heading">Starfall</h2>
|
|
42
|
+
<p class="text-body text-text-secondary">A city that falls one floor a night.</p>
|
|
43
|
+
<bf-button
|
|
44
|
+
bfSlot="footer"
|
|
45
|
+
class="relative z-1 self-start"
|
|
46
|
+
variant="tonal"
|
|
47
|
+
size="sm"
|
|
48
|
+
[disabled]="starfall().generating"
|
|
49
|
+
(clicked)="generate(starfall, 'media/skyline.svg', false)"
|
|
50
|
+
>
|
|
51
|
+
Draw again
|
|
52
|
+
</bf-button>
|
|
53
|
+
</bf-media-card>
|
|
54
|
+
|
|
55
|
+
<bf-media-card
|
|
56
|
+
alt="A harbour under a low sun"
|
|
57
|
+
loadingLabel="Drawing…"
|
|
58
|
+
failedLabel="The picture could not be drawn"
|
|
59
|
+
[src]="tidewalk().src"
|
|
60
|
+
[loading]="tidewalk().generating"
|
|
61
|
+
[failed]="tidewalk().failed"
|
|
62
|
+
>
|
|
63
|
+
<h2 class="text-heading">Tidewalk</h2>
|
|
64
|
+
<p class="text-body text-text-secondary">A harbour town that floods at every tide.</p>
|
|
65
|
+
<bf-button
|
|
66
|
+
bfSlot="failed-action"
|
|
67
|
+
variant="tonal"
|
|
68
|
+
size="sm"
|
|
69
|
+
(clicked)="generate(tidewalk, 'media/harbour.svg', false)"
|
|
70
|
+
>
|
|
71
|
+
Retry
|
|
72
|
+
</bf-button>
|
|
73
|
+
</bf-media-card>
|
|
74
|
+
|
|
75
|
+
<bf-media-card src="media/forest.svg" alt="A pine forest under a pale sun">
|
|
76
|
+
<h2 class="text-heading">Rootbound</h2>
|
|
77
|
+
<p class="text-body text-text-secondary">A forest that rearranges itself at night.</p>
|
|
78
|
+
</bf-media-card>
|
|
79
|
+
</div>
|
|
80
|
+
`,
|
|
81
|
+
})
|
|
82
|
+
export class MediaCardOverviewExampleComponent {
|
|
83
|
+
protected readonly starfall = signal<Generation>({ src: '', generating: true, failed: false });
|
|
84
|
+
protected readonly tidewalk = signal<Generation>({ src: '', generating: true, failed: false });
|
|
85
|
+
private readonly timers = new Set<ReturnType<typeof setTimeout>>();
|
|
86
|
+
|
|
87
|
+
constructor() {
|
|
88
|
+
inject(DestroyRef).onDestroy(() => this.timers.forEach((timer) => clearTimeout(timer)));
|
|
89
|
+
this.generate(this.starfall, 'media/skyline.svg', false);
|
|
90
|
+
// The first attempt at Tidewalk fails, so Retry shows.
|
|
91
|
+
this.generate(this.tidewalk, 'media/harbour.svg', true);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Starts a (simulated) generation that ends with the picture, or with a failure. */
|
|
95
|
+
protected generate(target: WritableSignal<Generation>, src: string, fails: boolean): void {
|
|
96
|
+
target.set({ src: '', generating: true, failed: false });
|
|
97
|
+
const timer = setTimeout(() => {
|
|
98
|
+
this.timers.delete(timer);
|
|
99
|
+
target.set(
|
|
100
|
+
fails
|
|
101
|
+
? { src: '', generating: false, failed: true }
|
|
102
|
+
: { src, generating: false, failed: false },
|
|
103
|
+
);
|
|
104
|
+
}, GENERATION_MS);
|
|
105
|
+
this.timers.add(timer);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Overview
|
|
111
|
+
|
|
112
|
+
Card for a picture and its story: a game pitch, a generated image, a product. The top is an
|
|
113
|
+
image area at a fixed aspect ratio that shows a shimmering skeleton while the picture loads
|
|
114
|
+
(also while it does not exist yet: set `loading`), fades the picture in when it arrives, and
|
|
115
|
+
shows a message with a Retry slot when it fails. Below it sit a body and a footer. Make the card
|
|
116
|
+
clickable with a `bfAction` link or button in its title, as on `bf-card`, and selectable with
|
|
117
|
+
`selectable` and `[(selected)]`.
|
|
118
|
+
|
|
119
|
+
A card led by a picture: a game pitch, a generated image, a product. The top is an image area at a fixed aspect ratio that the card fills itself from `src` and `alt`, so it owns the whole life of the picture: a shimmering skeleton while it loads (also while it does not exist yet), a fade when it arrives, and a message with a Retry slot when it fails. Below the image sit a body and a footer, as on `bf-card`.
|
|
120
|
+
|
|
121
|
+
## When to use
|
|
122
|
+
|
|
123
|
+
Use `bf-media-card` when a picture is the main content of a card, and especially when it is slow to arrive (an image generated in 30 to 90 seconds) or can fail. Use [bf-card](https://bitforge-ui.web.app/components/card) instead for a card without a picture or with a small one inside its text, [bf-image-input](https://bitforge-ui.web.app/components/image-input) to let the user pick an image, and [bf-skeleton](https://bitforge-ui.web.app/components/skeleton) for a placeholder of other content. Open the full-size picture with [bf-image-viewer](https://bitforge-ui.web.app/components/image-viewer).
|
|
124
|
+
|
|
125
|
+
## Basic usage
|
|
126
|
+
|
|
127
|
+
Give the card the picture's `src` and `alt` (empty `alt` marks it decorative), and put a title and text in its body. The image area keeps its ratio (`16/9` by default) whether the picture is there or not, so a grid of cards never jumps.
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { Component } from '@angular/core';
|
|
131
|
+
import { BfBadgeComponent, BfButtonComponent, BfMediaCardComponent } from '@bitforgehq/ui';
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A media card.
|
|
135
|
+
*
|
|
136
|
+
* The card shows the picture from `src` in a 16:9 area, with a title, a line of text and a footer
|
|
137
|
+
* of a badge and a button under it.
|
|
138
|
+
*/
|
|
139
|
+
@Component({
|
|
140
|
+
selector: 'app-example-media-card-basic',
|
|
141
|
+
imports: [BfBadgeComponent, BfButtonComponent, BfMediaCardComponent],
|
|
142
|
+
template: `
|
|
143
|
+
<bf-media-card class="max-w-sm" src="media/skyline.svg" alt="A neon city skyline at dusk">
|
|
144
|
+
<h3 class="text-heading">Starfall</h3>
|
|
145
|
+
<p class="text-body text-text-secondary">
|
|
146
|
+
A roguelike set in a city that falls one floor a night.
|
|
147
|
+
</p>
|
|
148
|
+
<div bfSlot="footer" class="flex items-center justify-between gap-2">
|
|
149
|
+
<bf-badge>Roguelike</bf-badge>
|
|
150
|
+
<bf-button variant="tonal" size="sm">Read the pitch</bf-button>
|
|
151
|
+
</div>
|
|
152
|
+
</bf-media-card>
|
|
153
|
+
`,
|
|
154
|
+
})
|
|
155
|
+
export class MediaCardBasicExampleComponent {}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Loading, arriving and failing
|
|
159
|
+
|
|
160
|
+
While the picture loads, the area shows a `bf-skeleton` shimmer. A picture that does not exist yet (it is still being made) has no `src`: set `loading` and the skeleton shows until `src` arrives. `loadingLabel` puts a caption over the skeleton ("Drawing…"); without it, screen readers hear the `loading` label ("Loading") and the area is `aria-busy`.
|
|
161
|
+
|
|
162
|
+
When the picture arrives it fades in over 260 ms (`bfFadeIn`) while the skeleton fades out under it, instead of popping in line by line; under reduced motion it appears at once.
|
|
163
|
+
|
|
164
|
+
A picture that fails to load (its `error` event), or one the app could not make (`failed`), shows a message, `failedLabel` or the `imageLoadFailed` label ("The image could not be loaded"), and the `bfSlot="failed-action"` content under it, such as a Retry button. A new `src` starts over: skeleton, fade and a forgotten load error. To retry the same URL, change it (add a query such as `?retry=2`). `failed` is the app's to clear. `loaded` and `errored` report the picture's events.
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { Component, DestroyRef, type WritableSignal, inject, signal } from '@angular/core';
|
|
168
|
+
import { BfButtonComponent, BfMediaCardComponent } from '@bitforgehq/ui';
|
|
169
|
+
|
|
170
|
+
/** One generated picture as the app tracks it. */
|
|
171
|
+
interface Generation {
|
|
172
|
+
readonly src: string;
|
|
173
|
+
readonly generating: boolean;
|
|
174
|
+
readonly failed: boolean;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** How long the simulated generation takes (a real one takes 30 to 90 seconds). */
|
|
178
|
+
const GENERATION_MS = 4000;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The bf-media-card overview.
|
|
182
|
+
*
|
|
183
|
+
* Live demo of `bf-media-card` with pictures that take a while to exist: the first is being drawn
|
|
184
|
+
* (a timer stands in for the image service) and fades in when it arrives; the second fails, and
|
|
185
|
+
* its Retry button starts it again; the third is already there.
|
|
186
|
+
*/
|
|
187
|
+
@Component({
|
|
188
|
+
selector: 'app-example-media-card-overview',
|
|
189
|
+
imports: [BfButtonComponent, BfMediaCardComponent],
|
|
190
|
+
template: `
|
|
191
|
+
<div class="grid gap-4 @md:grid-cols-3">
|
|
192
|
+
<bf-media-card
|
|
193
|
+
alt="A neon city skyline at dusk"
|
|
194
|
+
loadingLabel="Drawing…"
|
|
195
|
+
[src]="starfall().src"
|
|
196
|
+
[loading]="starfall().generating"
|
|
197
|
+
>
|
|
198
|
+
<h2 class="text-heading">Starfall</h2>
|
|
199
|
+
<p class="text-body text-text-secondary">A city that falls one floor a night.</p>
|
|
200
|
+
<bf-button
|
|
201
|
+
bfSlot="footer"
|
|
202
|
+
class="relative z-1 self-start"
|
|
203
|
+
variant="tonal"
|
|
204
|
+
size="sm"
|
|
205
|
+
[disabled]="starfall().generating"
|
|
206
|
+
(clicked)="generate(starfall, 'media/skyline.svg', false)"
|
|
207
|
+
>
|
|
208
|
+
Draw again
|
|
209
|
+
</bf-button>
|
|
210
|
+
</bf-media-card>
|
|
211
|
+
|
|
212
|
+
<bf-media-card
|
|
213
|
+
alt="A harbour under a low sun"
|
|
214
|
+
loadingLabel="Drawing…"
|
|
215
|
+
failedLabel="The picture could not be drawn"
|
|
216
|
+
[src]="tidewalk().src"
|
|
217
|
+
[loading]="tidewalk().generating"
|
|
218
|
+
[failed]="tidewalk().failed"
|
|
219
|
+
>
|
|
220
|
+
<h2 class="text-heading">Tidewalk</h2>
|
|
221
|
+
<p class="text-body text-text-secondary">A harbour town that floods at every tide.</p>
|
|
222
|
+
<bf-button
|
|
223
|
+
bfSlot="failed-action"
|
|
224
|
+
variant="tonal"
|
|
225
|
+
size="sm"
|
|
226
|
+
(clicked)="generate(tidewalk, 'media/harbour.svg', false)"
|
|
227
|
+
>
|
|
228
|
+
Retry
|
|
229
|
+
</bf-button>
|
|
230
|
+
</bf-media-card>
|
|
231
|
+
|
|
232
|
+
<bf-media-card src="media/forest.svg" alt="A pine forest under a pale sun">
|
|
233
|
+
<h2 class="text-heading">Rootbound</h2>
|
|
234
|
+
<p class="text-body text-text-secondary">A forest that rearranges itself at night.</p>
|
|
235
|
+
</bf-media-card>
|
|
236
|
+
</div>
|
|
237
|
+
`,
|
|
238
|
+
})
|
|
239
|
+
export class MediaCardOverviewExampleComponent {
|
|
240
|
+
protected readonly starfall = signal<Generation>({ src: '', generating: true, failed: false });
|
|
241
|
+
protected readonly tidewalk = signal<Generation>({ src: '', generating: true, failed: false });
|
|
242
|
+
private readonly timers = new Set<ReturnType<typeof setTimeout>>();
|
|
243
|
+
|
|
244
|
+
constructor() {
|
|
245
|
+
inject(DestroyRef).onDestroy(() => this.timers.forEach((timer) => clearTimeout(timer)));
|
|
246
|
+
this.generate(this.starfall, 'media/skyline.svg', false);
|
|
247
|
+
// The first attempt at Tidewalk fails, so Retry shows.
|
|
248
|
+
this.generate(this.tidewalk, 'media/harbour.svg', true);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Starts a (simulated) generation that ends with the picture, or with a failure. */
|
|
252
|
+
protected generate(target: WritableSignal<Generation>, src: string, fails: boolean): void {
|
|
253
|
+
target.set({ src: '', generating: true, failed: false });
|
|
254
|
+
const timer = setTimeout(() => {
|
|
255
|
+
this.timers.delete(timer);
|
|
256
|
+
target.set(
|
|
257
|
+
fails
|
|
258
|
+
? { src: '', generating: false, failed: true }
|
|
259
|
+
: { src, generating: false, failed: false },
|
|
260
|
+
);
|
|
261
|
+
}, GENERATION_MS);
|
|
262
|
+
this.timers.add(timer);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Aspect ratios
|
|
268
|
+
|
|
269
|
+
`aspect` is `16/9` (default), `4/3`, `1/1` or `3/4` (portrait). The picture covers the area and is cropped to it (`object-cover`).
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { Component } from '@angular/core';
|
|
273
|
+
import { type BfMediaCardAspect, BfMediaCardComponent } from '@bitforgehq/ui';
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Aspect ratios.
|
|
277
|
+
*
|
|
278
|
+
* `aspect` sets the ratio of the image area: 16:9 (default), 4:3, square and portrait 3:4. The
|
|
279
|
+
* picture covers the area and is cropped to it.
|
|
280
|
+
*/
|
|
281
|
+
@Component({
|
|
282
|
+
selector: 'app-example-media-card-aspects',
|
|
283
|
+
imports: [BfMediaCardComponent],
|
|
284
|
+
template: `
|
|
285
|
+
<div class="grid grid-cols-2 items-start gap-4 @lg:grid-cols-4">
|
|
286
|
+
@for (aspect of aspects; track aspect) {
|
|
287
|
+
<bf-media-card src="media/desert.svg" alt="Dunes under an orange sun" [aspect]="aspect">
|
|
288
|
+
<h3 class="text-label">{{ aspect }}</h3>
|
|
289
|
+
</bf-media-card>
|
|
290
|
+
}
|
|
291
|
+
</div>
|
|
292
|
+
`,
|
|
293
|
+
})
|
|
294
|
+
export class MediaCardAspectsExampleComponent {
|
|
295
|
+
protected readonly aspects: readonly BfMediaCardAspect[] = ['16/9', '4/3', '1/1', '3/4'];
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Clickable and selectable cards
|
|
300
|
+
|
|
301
|
+
A card that opens something follows the `bf-card` pattern: put a `bfAction` link (navigation) or button (an action) in its title. The whole card is then its click target, with the pointer, the hover lift and the focus ring; other controls in it, such as footer buttons, take `class="relative z-1"`.
|
|
302
|
+
|
|
303
|
+
`selectable` adds a select button, a toggle (`aria-pressed`) bound with `[(selected)]`; `changed` emits each change the user makes. Without a `bfAction`, the whole card is that button, and a check disc in the image's top end corner shows the state. With a `bfAction`, the card opens on a click and the round check button in the corner (32 px, 44 px under a coarse pointer) selects, as in a photo library. A selected card has a 2 px primary ring outside its edge and a filled check. The select button is named "Select" followed by `label`, or by the body's text when `label` is empty: set `label` to the card's title.
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import { Component, computed, signal } from '@angular/core';
|
|
307
|
+
import { RouterLink } from '@angular/router';
|
|
308
|
+
import { BfActionDirective, BfMediaCardComponent } from '@bitforgehq/ui';
|
|
309
|
+
|
|
310
|
+
/** One concept the team can pick. */
|
|
311
|
+
interface Concept {
|
|
312
|
+
readonly id: string;
|
|
313
|
+
readonly title: string;
|
|
314
|
+
readonly src: string;
|
|
315
|
+
readonly alt: string;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Selectable cards.
|
|
320
|
+
*
|
|
321
|
+
* Pick the concepts to keep: each card is one select button bound with `[(selected)]`, with a
|
|
322
|
+
* check in its corner. The last card also opens its page through a `bfAction` link, so its check
|
|
323
|
+
* becomes a separate round button.
|
|
324
|
+
*/
|
|
325
|
+
@Component({
|
|
326
|
+
selector: 'app-example-media-card-selectable',
|
|
327
|
+
imports: [RouterLink, BfActionDirective, BfMediaCardComponent],
|
|
328
|
+
template: `
|
|
329
|
+
<div class="flex flex-col gap-3">
|
|
330
|
+
<div class="grid gap-4 @md:grid-cols-3">
|
|
331
|
+
@for (concept of concepts; track concept.id) {
|
|
332
|
+
<bf-media-card
|
|
333
|
+
selectable
|
|
334
|
+
aspect="4/3"
|
|
335
|
+
[src]="concept.src"
|
|
336
|
+
[alt]="concept.alt"
|
|
337
|
+
[label]="concept.title"
|
|
338
|
+
[selected]="picked().has(concept.id)"
|
|
339
|
+
(changed)="toggle(concept.id, $event)"
|
|
340
|
+
>
|
|
341
|
+
<h3 class="text-heading">{{ concept.title }}</h3>
|
|
342
|
+
</bf-media-card>
|
|
343
|
+
}
|
|
344
|
+
<bf-media-card
|
|
345
|
+
selectable
|
|
346
|
+
aspect="4/3"
|
|
347
|
+
src="media/glacier.svg"
|
|
348
|
+
alt="A glacier under a small sun"
|
|
349
|
+
label="Coldlight"
|
|
350
|
+
[selected]="picked().has('coldlight')"
|
|
351
|
+
(changed)="toggle('coldlight', $event)"
|
|
352
|
+
>
|
|
353
|
+
<h3 class="text-heading">
|
|
354
|
+
<a bfAction routerLink="/components/media-card">Coldlight</a>
|
|
355
|
+
</h3>
|
|
356
|
+
</bf-media-card>
|
|
357
|
+
</div>
|
|
358
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
359
|
+
</div>
|
|
360
|
+
`,
|
|
361
|
+
})
|
|
362
|
+
export class MediaCardSelectableExampleComponent {
|
|
363
|
+
protected readonly concepts: readonly Concept[] = [
|
|
364
|
+
{ id: 'starfall', title: 'Starfall', src: 'media/skyline.svg', alt: 'A neon city skyline' },
|
|
365
|
+
{ id: 'rootbound', title: 'Rootbound', src: 'media/forest.svg', alt: 'A pine forest' },
|
|
366
|
+
{ id: 'tidewalk', title: 'Tidewalk', src: 'media/harbour.svg', alt: 'A harbour at sunset' },
|
|
367
|
+
];
|
|
368
|
+
protected readonly picked = signal<ReadonlySet<string>>(new Set(['rootbound']));
|
|
369
|
+
protected readonly status = computed(() => {
|
|
370
|
+
const count = this.picked().size;
|
|
371
|
+
return count === 1 ? '1 concept picked.' : `${count} concepts picked.`;
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
protected toggle(id: string, selected: boolean): void {
|
|
375
|
+
this.picked.update((ids) => {
|
|
376
|
+
const next = new Set(ids);
|
|
377
|
+
if (selected) {
|
|
378
|
+
next.add(id);
|
|
379
|
+
} else {
|
|
380
|
+
next.delete(id);
|
|
381
|
+
}
|
|
382
|
+
return next;
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
## Desktop
|
|
389
|
+
|
|
390
|
+
A card with a `bfAction` or a stretched select button shows the pointer and lifts to `shadow-floating` while the pointer is over it, and drops back to `shadow-raised` while pressed; both fade on the fast timing (150 ms). The ring around the whole card shows while its action or select button has keyboard focus, not after a click. The corner check button draws its own focus ring. The selection ring fades in and out on the same timing. Text under a stretched action cannot be selected with the mouse; give text that must stay copyable `class="relative z-1"`.
|
|
391
|
+
|
|
392
|
+
## Theming
|
|
393
|
+
|
|
394
|
+
- Card: `bg-surface`, `text-text`, `rounded-card`, `shadow-raised` (`shadow-floating` on hover); body padding `p-card` (`--spacing-card`).
|
|
395
|
+
- Image area: `bg-surface-sunken`, the top corners `rounded-card`; the skeleton is a `bf-skeleton` rect; captions and messages `text-caption text-text-muted`.
|
|
396
|
+
- Selection: the ring is a 2 px `primary` outline 2 px outside the card; the check disc is `surface` with a `border-strong` edge, `primary` with an `on-primary` check while selected.
|
|
397
|
+
- Focus: `ring-4 ring-ring` around the card (stretched action) or the corner button.
|
|
398
|
+
- Motion: `duration-fast` (150 ms) for hover, press and selection, `duration-slow` (260 ms) for the picture's fade and the skeleton's fade-out, on `ease-md`; none under reduced motion.
|
|
399
|
+
|
|
400
|
+
## Accessibility
|
|
401
|
+
|
|
402
|
+
The card is a plain `<div>`; the picture is an `<img>` with `alt`. While loading, the image area is `aria-busy="true"` and its caption (or the visually hidden "Loading") is plain text, not a live region, so a grid of loading cards is not announced card by card; the skeleton is hidden from assistive technology. The failure message is plain text too, followed by the `failed-action` content.
|
|
403
|
+
|
|
404
|
+
A clickable card follows the stretched-link pattern of `bf-card`: the only interactive element is the `bfAction` link or button in its title. The select button is a native `<button>` with `aria-pressed`, named by `aria-labelledby` ("Select" and `label`, or the body's text); its check is decorative. Under forced colours the selection ring becomes a system-coloured outline, and the check is drawn with borders, so both stay visible; an unselected card has no outline there.
|
|
405
|
+
|
|
406
|
+
### Keyboard
|
|
407
|
+
|
|
408
|
+
| Key | Action |
|
|
409
|
+
| ---------------- | ----------------------------------------------------------------------------- |
|
|
410
|
+
| <kbd>Tab</kbd> | Moves to the card's `bfAction` link or button, then to its select button |
|
|
411
|
+
| <kbd>Enter</kbd> | Follows a `bfAction` link, activates a `bfAction` button or toggles selection |
|
|
412
|
+
| <kbd>Space</kbd> | Activates a `bfAction` button or toggles selection; a link ignores it |
|
|
413
|
+
|
|
414
|
+
## Inputs
|
|
415
|
+
|
|
416
|
+
| Name | Type | Default | Description |
|
|
417
|
+
| -------------- | --------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
418
|
+
| `src` | `string` | `''` | URL of the image; empty shows the skeleton while `loading`, else an empty area. A new URL starts over: skeleton, fade-in, and a failed load is forgotten. |
|
|
419
|
+
| `alt` | `string` | `''` | Alternative text of the image; empty marks it decorative. |
|
|
420
|
+
| `aspect` | `BfMediaCardAspect` (`'16/9' \| '4/3' \| '1/1' \| '3/4'`) | `'16/9'` | Aspect ratio of the image area: `16/9` (default), `4/3`, `1/1` or `3/4`. |
|
|
421
|
+
| `loading` | `boolean` | `false` | Shows the skeleton while there is no `src` yet (a picture that is still being made). |
|
|
422
|
+
| `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. |
|
|
423
|
+
| `loadingLabel` | `string` | `''` | Caption shown over the skeleton while loading ("Drawing…"); empty shows none and tells screen readers `provideBfLabels()`'s `loading` ("Loading"). |
|
|
424
|
+
| `failedLabel` | `string` | `''` | Failure message; empty uses `provideBfLabels()`'s `imageLoadFailed`. |
|
|
425
|
+
| `selectable` | `boolean` | `false` | Adds a select button (a toggle with `aria-pressed`): the whole card toggles `selected`, or, with a `bfAction` in the card, a round check button in the image's corner does. |
|
|
426
|
+
| `label` | `string` | `''` | The card's name in its select button ("Select Starfall"); empty names it by the body's text. |
|
|
427
|
+
| `selected` | `boolean` | `false` | Whether the card is selected (a ring around it and a filled check); two-way bindable. Drawn only on a `selectable` card. Two-way: `[(selected)]`. |
|
|
428
|
+
|
|
429
|
+
## Outputs
|
|
430
|
+
|
|
431
|
+
| Name | Payload | Description |
|
|
432
|
+
| --------- | --------- | ----------------------------------------------------------------------- |
|
|
433
|
+
| `changed` | `boolean` | The user selected or cleared the card; emits the new `selected`. |
|
|
434
|
+
| `loaded` | `void` | The image of `src` has loaded. |
|
|
435
|
+
| `errored` | `void` | The image of `src` failed to load (the card shows the failure message). |
|
|
436
|
+
|
|
437
|
+
## Slots
|
|
438
|
+
|
|
439
|
+
- `failed-action`: An action shown under the failure message, such as a Retry button.
|
|
440
|
+
- `default`: The card body: a title (with an optional bfAction link or button) and text.
|
|
441
|
+
- `footer`: Content below the body, such as chips and buttons; it sits on the card's bottom edge.
|
|
442
|
+
|
|
443
|
+
## Notes
|
|
444
|
+
|
|
445
|
+
### Do
|
|
446
|
+
|
|
447
|
+
- Give every meaningful picture an `alt` that says what it shows.
|
|
448
|
+
- Set `loading` (and a `loadingLabel` such as "Drawing…") while a picture is being made, so the card holds its place.
|
|
449
|
+
- Put a Retry button in `bfSlot="failed-action"`, and retry with a changed `src`.
|
|
450
|
+
- Set `label` on a selectable card to its title, so the select button reads "Select Starfall".
|
|
451
|
+
|
|
452
|
+
### Don't
|
|
453
|
+
|
|
454
|
+
- Don't project your own `<img>` into the body for the main picture; pass `src`, so the card handles loading, fading and failing.
|
|
455
|
+
- Don't put more than one `bfAction` in a card.
|
|
456
|
+
- Don't use `selected` as a highlight without `selectable`: the ring only shows on a selectable card, whose button tells assistive technology the state.
|
|
457
|
+
|
|
458
|
+
## Migration notes
|
|
459
|
+
|
|
460
|
+
### 0.5.0
|
|
461
|
+
|
|
462
|
+
New: `bf-media-card`, a card led by a picture (a game pitch, a generated image). The card renders
|
|
463
|
+
the picture itself from `src` and `alt` in a top area at a fixed `aspect` (new type
|
|
464
|
+
`BfMediaCardAspect`: `'16/9'` (default), `'4/3'`, `'1/1'`, `'3/4'`). While it loads, and while
|
|
465
|
+
`src` is empty but `loading` is set (a picture still being made), the area shows a `bf-skeleton`
|
|
466
|
+
shimmer with an optional `loadingLabel` caption ("Drawing…") and is `aria-busy`. The picture fades
|
|
467
|
+
in over 260 ms (`bfFadeIn`) while the skeleton fades out under it; at once under reduced motion.
|
|
468
|
+
A load error, or `failed` set by the app, shows `failedLabel` (default: the new label
|
|
469
|
+
`imageLoadFailed`) and the `bfSlot="failed-action"` content (Retry); a new `src` starts over.
|
|
470
|
+
`loaded` and `errored` report the image's events. A body slot and a `footer` slot follow. A
|
|
471
|
+
`bfAction` link or button in the title makes the whole card clickable, as on `bf-card` (pointer,
|
|
472
|
+
`shadow-floating` hover, focus ring, fading on `duration-fast`). `selectable` adds a toggle
|
|
473
|
+
button (`aria-pressed`) bound with `[(selected)]` (a `model()`), emitting `changed` on each user
|
|
474
|
+
change: the whole card without a `bfAction`, else a round check button in the image's corner; a
|
|
475
|
+
selected card has a 2 px primary ring and a filled check. `label` names it ("Select" + `label`,
|
|
476
|
+
new label `mediaCardSelect`). `@bitforgehq/ui/testing`: new `BfMediaCardHarness`
|
|
477
|
+
(`getState()`, `loadImage()`, `failImage()`, `getLoadingLabel()`, `getFailedMessage()`,
|
|
478
|
+
`isSelected()`, `toggleSelected()`).
|
|
@@ -311,6 +311,8 @@ Hover and focus look like `bf-button` (or `bf-icon-button` with `iconOnly`): the
|
|
|
311
311
|
last. Closing the menu (`Escape`, `Tab`, an item) returns focus to the button.
|
|
312
312
|
- The caret is drawn in CSS and adds nothing to the name.
|
|
313
313
|
- Hit area: at least 32 px, 44 px under a coarse pointer (as `bf-button`).
|
|
314
|
+
- Motion: the menu fades and grows out of the button as it opens, and closes at once. Under
|
|
315
|
+
`prefers-reduced-motion` it only fades in.
|
|
314
316
|
|
|
315
317
|
### Keyboard
|
|
316
318
|
|
package/ai/components/menu.md
CHANGED
|
@@ -570,6 +570,9 @@ Built for desktop: hovering an item gives it the `surface-muted` fill and the po
|
|
|
570
570
|
closing the dialog.
|
|
571
571
|
- Pointer: rows are at least 32 px tall, 44 px under a coarse pointer. An open submenu stays open
|
|
572
572
|
while the pointer moves diagonally towards it across other items (CDK menu aim).
|
|
573
|
+
- Motion: the menu grows out of its trigger as it opens (a fade, a slight scale and a 4 px slide,
|
|
574
|
+
180 ms; a submenu out of its item). It closes at once, because the CDK menu removes it
|
|
575
|
+
immediately. Under `prefers-reduced-motion` it only fades in.
|
|
573
576
|
|
|
574
577
|
### Keyboard
|
|
575
578
|
|
|
@@ -619,6 +622,13 @@ From the [APG menu button](https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/
|
|
|
619
622
|
|
|
620
623
|
## Migration notes
|
|
621
624
|
|
|
625
|
+
### 0.5.0
|
|
626
|
+
|
|
627
|
+
The menu animates open: it grows out of its trigger (a fade, a scale from 96 % and a 4 px slide,
|
|
628
|
+
180 ms, ease-out), a submenu out of its item. It still closes at once, because the CDK menu
|
|
629
|
+
removes it immediately. Under `prefers-reduced-motion` it only fades in. `bf-menu-button` and
|
|
630
|
+
`bf-theme-menu-button` get the same enter.
|
|
631
|
+
|
|
622
632
|
### 0.3.1
|
|
623
633
|
|
|
624
634
|
The menu's positions now come from the anchored-panel positioning the popover and tooltip share.
|
|
@@ -171,6 +171,8 @@ coarse pointer its hit area grows to 44 px and every option row is at least 44 p
|
|
|
171
171
|
order before the input.
|
|
172
172
|
- Labels, help and error text, `aria-describedby`, `aria-invalid` and `aria-errormessage` follow
|
|
173
173
|
`bf-select`. `required` sets the native attribute only while no tag is picked.
|
|
174
|
+
- Motion: the list opens and closes with the same short fade, scale and slide as on
|
|
175
|
+
`bf-combobox`, and only fades under `prefers-reduced-motion`.
|
|
174
176
|
|
|
175
177
|
### Keyboard
|
|
176
178
|
|
|
@@ -247,6 +249,11 @@ Focuses the input; Signal Forms calls it from `focusBoundControl()`.
|
|
|
247
249
|
|
|
248
250
|
## Migration notes
|
|
249
251
|
|
|
252
|
+
### 0.5.0
|
|
253
|
+
|
|
254
|
+
The list panel is exactly as wide as the field. The inner text input no longer shows a square
|
|
255
|
+
border and ring on focus in apps that load `@tailwindcss/forms`.
|
|
256
|
+
|
|
250
257
|
### 0.3.1
|
|
251
258
|
|
|
252
259
|
New `bf-multi-combobox` (`BfMultiComboboxComponent<T>`) in `@bitforgehq/ui/overlay`: the
|
|
@@ -72,7 +72,7 @@ Put the group's links in its default slot and its name in `label`. Groups stack
|
|
|
72
72
|
|
|
73
73
|
## In a collapsed sidebar
|
|
74
74
|
|
|
75
|
-
While the sidebar is collapsed to an icon rail the heading
|
|
75
|
+
While the sidebar is collapsed to an icon rail the heading's row closes and the text fades out (on `--bf-duration-layout`, at once under reduced motion), so only the icons show, and it still names the group for screen readers. The heading stays on one line and truncates, so its height never changes while the sidebar's width moves.
|
|
76
76
|
|
|
77
77
|
```ts
|
|
78
78
|
import { Component } from '@angular/core';
|
|
@@ -84,7 +84,7 @@ import { Layers, LayoutList, PanelTop, SquareStack } from 'lucide';
|
|
|
84
84
|
/**
|
|
85
85
|
* Groups in a collapsed sidebar.
|
|
86
86
|
*
|
|
87
|
-
* The headings
|
|
87
|
+
* The headings close and fade out (at once under reduced motion) and still name their groups for screen readers; the items
|
|
88
88
|
* show their icons only.
|
|
89
89
|
*/
|
|
90
90
|
@Component({
|
|
@@ -146,7 +146,7 @@ The group itself is static; its links carry the hover, pressed and focus styles
|
|
|
146
146
|
|
|
147
147
|
## Accessibility
|
|
148
148
|
|
|
149
|
-
The host has `role="group"` and `aria-labelledby` pointing at its heading, so a screen reader announces the group's name when focus enters it ("Workspace, group"). The heading is text, not a heading element, so it does not add to the page outline. Collapsed, the heading is
|
|
149
|
+
The host has `role="group"` and `aria-labelledby` pointing at its heading, so a screen reader announces the group's name when focus enters it ("Workspace, group"). The heading is text, not a heading element, so it does not add to the page outline. Collapsed, the heading is closed to no height and transparent, but stays in the accessibility tree and keeps naming the group.
|
|
150
150
|
|
|
151
151
|
## Inputs
|
|
152
152
|
|
|
@@ -172,6 +172,13 @@ The host has `role="group"` and `aria-labelledby` pointing at its heading, so a
|
|
|
172
172
|
|
|
173
173
|
## Migration notes
|
|
174
174
|
|
|
175
|
+
### 0.5.0
|
|
176
|
+
|
|
177
|
+
Collapsed, the heading's row closes and its text fades out on `--bf-duration-layout` instead of
|
|
178
|
+
switching to `sr-only`; it stays in the accessibility tree and still names the group. The heading
|
|
179
|
+
stays on one line and truncates (it wrapped before), so its height does not change while the
|
|
180
|
+
width moves.
|
|
181
|
+
|
|
175
182
|
### 0.3.1
|
|
176
183
|
|
|
177
184
|
New `bf-nav-group`: a titled group of sidebar links, `role="group"` named by its small heading,
|
|
@@ -109,7 +109,7 @@ The router sets `aria-current="page"` on the current page's link (`routerLinkAct
|
|
|
109
109
|
|
|
110
110
|
## Collapsed
|
|
111
111
|
|
|
112
|
-
In a collapsed `bf-sidebar` the item shows only its icon in a square of its height step. The label stays in the DOM
|
|
112
|
+
In a collapsed `bf-sidebar` the item shows only its icon in a square of its height step: the row keeps its layout, its start padding centres a 20 px icon, and the label fades out while the sidebar narrows, so the icon never moves. The label stays in the DOM, so the link's name does not change, and becomes the link's `title` (the tooltip on hover, kept up to date when the label changes); your own `title` comes back when the sidebar expands. The badge pill shrinks away while a small dot grows on the icon's corner, on the same timing, and the count is still announced. Give every item an icon, or it shows nothing while collapsed. For a styled tooltip that also shows on keyboard focus, add `bfTooltip` from `@bitforgehq/ui/overlay` with the label's text while collapsed (an empty text while expanded shows none) and `bfTooltipPlacement="end"` (see [bf-tooltip](https://bitforge-ui.web.app/components/tooltip)).
|
|
113
113
|
|
|
114
114
|
## Sizes
|
|
115
115
|
|
|
@@ -161,11 +161,12 @@ Hover gives a `surface-muted` fill with the `text` colour and the pointer; press
|
|
|
161
161
|
- Current page (`aria-current="page"`): `primary-soft` with `primary-strong`, hover `primary-soft-hover`, pressed `primary-soft-pressed`; under forced colours a `LinkText` outline.
|
|
162
162
|
- Shape: `rounded-control`; label `font-bold`, `text-label-xs` / `text-label-sm` / `text-label` by size.
|
|
163
163
|
- Badge: `primary` with `on-primary`, `text-micro`; collapsed, a `primary` dot (`CanvasText` under forced colours).
|
|
164
|
+
- Motion: in a sidebar, the padding, the label and the badge move on `--bf-duration-layout` with `ease-md` (none under reduced motion); hover and press colours fade over 150 ms.
|
|
164
165
|
- Focus: `ring` (4 px).
|
|
165
166
|
|
|
166
167
|
## Accessibility
|
|
167
168
|
|
|
168
|
-
The item is the consumer's native `<a>`: exposed as a link, reached with Tab and followed with Enter, its name being its label (plus the badge announcement). The current page carries `aria-current="page"`, which screen readers announce; the fill is not the only cue, as an outline marks it under forced colours. The icon slot is decorative (a `bf-icon` is `aria-hidden` by default). Collapsed, the label is
|
|
169
|
+
The item is the consumer's native `<a>`: exposed as a link, reached with Tab and followed with Enter, its name being its label (plus the badge announcement). The current page carries `aria-current="page"`, which screen readers announce; the fill is not the only cue, as an outline marks it under forced colours. The icon slot is decorative (a `bf-icon` is `aria-hidden` by default). Collapsed, the label is transparent and clipped but still the name, and `title` adds a pointer tooltip only; the badge dot is `aria-hidden`, its count announced through the screen-reader text. In Safari, Tab reaches links only when the user turns on "Press Tab to highlight each item" (Option+Tab otherwise).
|
|
169
170
|
|
|
170
171
|
### Keyboard
|
|
171
172
|
|
|
@@ -200,6 +201,14 @@ The item is the consumer's native `<a>`: exposed as a link, reached with Tab and
|
|
|
200
201
|
|
|
201
202
|
## Migration notes
|
|
202
203
|
|
|
204
|
+
### 0.5.0
|
|
205
|
+
|
|
206
|
+
In a collapsing sidebar the item no longer switches to a centred square: the row keeps its layout
|
|
207
|
+
and its start padding moves so the icon ends centred in the rail without moving, while the label
|
|
208
|
+
fades out (it stays the accessible name; it is no longer `sr-only`). The count pill shrinks away
|
|
209
|
+
while the dot (new element, `[data-badge-dot]`) grows in on the same timing. Each item clips
|
|
210
|
+
what does not fit the rail. No animation under reduced motion or in a top nav.
|
|
211
|
+
|
|
203
212
|
### 0.3.1
|
|
204
213
|
|
|
205
214
|
New `a[bfNavItem]`: one destination of a `bf-sidebar` or `bf-top-nav` on the app's own
|
|
@@ -165,13 +165,8 @@ The control works in Reactive Forms through Angular's Signal Forms interop (no `
|
|
|
165
165
|
```ts
|
|
166
166
|
import { Component } from '@angular/core';
|
|
167
167
|
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
168
|
-
import {
|
|
169
|
-
|
|
170
|
-
BfCheckboxComponent,
|
|
171
|
-
BfInputComponent,
|
|
172
|
-
BfSelectComponent,
|
|
173
|
-
type BfSelectOption,
|
|
174
|
-
} from '@bitforgehq/ui';
|
|
168
|
+
import { BfButtonComponent, BfCheckboxComponent, BfInputComponent } from '@bitforgehq/ui';
|
|
169
|
+
import { BfSelectComponent, type BfSelectOption } from '@bitforgehq/ui/overlay';
|
|
175
170
|
import { BfNumberInputComponent } from '@bitforgehq/ui/number-input';
|
|
176
171
|
|
|
177
172
|
/**
|
package/ai/components/popover.md
CHANGED
|
@@ -207,6 +207,9 @@ in the Capacitor shell a `bf-sheet` suits phone screens better.
|
|
|
207
207
|
`Shift+Tab` before the first control closes it onto the trigger.
|
|
208
208
|
- `Escape` goes to the popover first: inside a dialog, the first `Escape` closes the popover and
|
|
209
209
|
the next one the dialog.
|
|
210
|
+
- Motion: the panel grows out of the trigger as it opens (a fade, a slight scale and a 4 px slide,
|
|
211
|
+
180 ms) and shrinks back as it closes (130 ms). Focus and `aria-expanded` change at once; the
|
|
212
|
+
closing panel is inert while it fades. Under `prefers-reduced-motion` it only fades.
|
|
210
213
|
|
|
211
214
|
### Keyboard
|
|
212
215
|
|
|
@@ -238,6 +241,16 @@ in the Capacitor shell a `bf-sheet` suits phone screens better.
|
|
|
238
241
|
|
|
239
242
|
## Migration notes
|
|
240
243
|
|
|
244
|
+
### 0.5.0
|
|
245
|
+
|
|
246
|
+
The panel animates open and closed. It grows out of the trigger (a fade, a scale from 96 % and a
|
|
247
|
+
4 px slide away from the trigger, 180 ms, ease-out; transform origin on the trigger's corner,
|
|
248
|
+
following a flip above or below) and shrinks back on close (130 ms). Focus, `aria-expanded` and
|
|
249
|
+
`bfPopoverClosed` still change at once; the closing panel stays in the overlay container, inert
|
|
250
|
+
and click-through, until its exit has played, and opening again during the exit replaces it. Under
|
|
251
|
+
`prefers-reduced-motion` it only fades. Where the Web Animations API is missing or stubbed
|
|
252
|
+
(jsdom), the panel is still removed at once.
|
|
253
|
+
|
|
241
254
|
### 0.3.1
|
|
242
255
|
|
|
243
256
|
New `[bfPopoverTriggerFor]` (`BfPopoverTriggerDirective`, exported as `bfPopoverTrigger`) and the
|