@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/CHANGELOG.md
CHANGED
|
@@ -303,6 +303,53 @@ large image loads it shows the thumbnail and a spinner, then fades the image in.
|
|
|
303
303
|
`getCounterText()`, `getSrc()`, `getAlt()`, `getCaption()`, `next()`, `previous()`,
|
|
304
304
|
`pressKey()`, `close()`, `loadImage()`, `failImage()`, `isLoaded()`, `hasFailed()`).
|
|
305
305
|
|
|
306
|
+
### image-tile
|
|
307
|
+
|
|
308
|
+
New: `bf-image-tile`, a small card for grids of thumbnails (3D models with one render each, a
|
|
309
|
+
board of reference pictures). It is mostly picture, at a fixed `aspect` (new type
|
|
310
|
+
`BfImageTileAspect`: `'1/1'` (default), `'4/3'`, `'16/9'`, `'3/4'`), with the title (default slot,
|
|
311
|
+
a heading holding a `bfAction` link or button) and an optional `bfSlot="meta"` line below the
|
|
312
|
+
picture, or with `captionPlacement="overlay"` (new type `BfImageTileCaptionPlacement`: `'below'`
|
|
313
|
+
(default), `'overlay'`) on a soft `scrim` gradient over its bottom. States share
|
|
314
|
+
`bf-media-card`'s logic: without `src` a placeholder (`bfSlot="placeholder-icon"` and
|
|
315
|
+
`placeholderLabel`, default: the new label `imageTileEmpty`, "No image yet"); `loading`, and a
|
|
316
|
+
`src` that is loading, show a `bf-skeleton` shimmer (`aria-busy`); the picture fades in over
|
|
317
|
+
260 ms; a load error or `failed` shows `failedLabel` (default `imageLoadFailed`). `loaded` and
|
|
318
|
+
`errored` report the picture's events. A `bfAction` makes the whole tile one link or button: the
|
|
319
|
+
pointer, a 2 px lift to `shadow-floating` and a 5 % zoom of the picture on hover (no movement
|
|
320
|
+
under reduced motion), and the focus ring. `selected` draws a 2 px primary ring and starts the
|
|
321
|
+
action's name with the new label `imageTileSelected` ("Selected, Harbour ship"). `bfSlot="badge"`
|
|
322
|
+
puts a `bf-badge` in the picture's top start corner. The title clamps to two lines and always
|
|
323
|
+
takes two, so tiles in a grid row are as tall. `@bitforgehq/ui/testing`: new
|
|
324
|
+
`BfImageTileHarness` (`getTitle()`, `getMeta()`, `getState()`, `getSrc()`, `getAlt()`,
|
|
325
|
+
`getPlaceholderText()`, `getFailedMessage()`, `isSelected()`, `isClickable()`, `click()`,
|
|
326
|
+
`loadImage()`, `failImage()`).
|
|
327
|
+
|
|
328
|
+
### badge
|
|
329
|
+
|
|
330
|
+
New `icon-start` slot: `<bf-icon bfSlot="icon-start" … />` before the label text, drawn at 12 px
|
|
331
|
+
whatever the icon's `size`, 4 px from the text, with the start padding tightened to 8 px. It
|
|
332
|
+
takes the text colour, so it follows the variant and forced colours. A count pill does not show
|
|
333
|
+
it.
|
|
334
|
+
|
|
335
|
+
### menu-button
|
|
336
|
+
|
|
337
|
+
New `fullWidth` input, as on `bf-button`: the host and its button stretch to the width of their
|
|
338
|
+
container (label and caret stay centred), so app CSS no longer needs to reach into the inner
|
|
339
|
+
`<button>`. Ignored with `iconOnly`.
|
|
340
|
+
|
|
341
|
+
### pagination
|
|
342
|
+
|
|
343
|
+
New `positionLabel` input (new type `BfPaginationPositionLabel`, `(page, count) => string`) that
|
|
344
|
+
writes a compact pagination's position text, such as "v3 of 7"; unset it is the `pageOf` label as
|
|
345
|
+
before. New `previousLabel` and `nextLabel` inputs name the arrows ("Previous version"); empty
|
|
346
|
+
they fall back to `previousPage` and `nextPage`. Together they make a compact pager for stepping
|
|
347
|
+
through versions.
|
|
348
|
+
|
|
349
|
+
## 0.5.0-next.4 (2026-11-07)
|
|
350
|
+
|
|
351
|
+
Pre-release of 0.5.0: its changes are listed under "0.5.0 (unreleased)" above.
|
|
352
|
+
|
|
306
353
|
## 0.5.0-next.3 (2026-11-07)
|
|
307
354
|
|
|
308
355
|
Pre-release of 0.5.0: its changes are listed under "0.5.0 (unreleased)" above.
|
package/README.md
CHANGED
|
@@ -140,10 +140,10 @@ Render library components only in the browser, for example in a `@defer` block o
|
|
|
140
140
|
route. Support will be decided before 1.0.
|
|
141
141
|
|
|
142
142
|
The one server-side check is a render smoke, not a support test: the library repository's
|
|
143
|
-
`tools/ssr-smoke.ts` renders
|
|
143
|
+
`tools/ssr-smoke.ts` renders 50 fixtures with `renderApplication()` (every root widget, `bf-icon`,
|
|
144
144
|
`bf-number-input`, `bf-select` from `/overlay`, a `/charts` line chart, a `/mobile` page with
|
|
145
145
|
`provideBfShell()`, `bf-page`, `bf-header` and `bf-content`, `bf-tabs` with `bf-stack-outlet` under
|
|
146
|
-
the router, and an `/overlay` dialog), and all of them render without an error.
|
|
146
|
+
the router, and an `/overlay` dialog), and all of them render without an error. 32 of them render
|
|
147
147
|
identically twice. The other 18 number their ids differently on every render, from a module-level
|
|
148
148
|
counter, so they could not hydrate: `bf-app-frame`, `bf-calendar`, `bf-checkbox`, `bf-file-input`, `bf-image-input`, `bf-input`,
|
|
149
149
|
`bf-radio-group`, `bf-segmented`, `bf-select`, `bf-sidebar`, `bf-tab-group`, `bf-table`,
|
|
@@ -163,7 +163,7 @@ overlays and themes leave on the document after each test (when TestBed destroys
|
|
|
163
163
|
environment, so keep its default `teardown: { destroyAfterEach: true }`), and the CDK harnesses
|
|
164
164
|
(`BfSelectHarness`, `BfRadioGroupHarness`, `BfSegmentedHarness`, `BfDialogHarness`,
|
|
165
165
|
`BfMenuButtonHarness`, `BfMenuHarness`, `BfMenuItemHarness`, `BfDesktopToastHarness`,
|
|
166
|
-
`BfTableHarness`, `BfPaginationHarness`, `BfTabGroupHarness`, `BfCalendarHarness`, `BfFileInputHarness`, `BfImageInputHarness`, `BfMediaCardHarness`, `BfImageViewerHarness`, `BfChatComposerHarness`, `BfChartHarness`) drive the
|
|
166
|
+
`BfTableHarness`, `BfPaginationHarness`, `BfTabGroupHarness`, `BfCalendarHarness`, `BfFileInputHarness`, `BfImageInputHarness`, `BfMediaCardHarness`, `BfImageTileHarness`, `BfImageViewerHarness`, `BfChatComposerHarness`, `BfChartHarness`) drive the
|
|
167
167
|
widgets whose DOM a spec should not query by hand; overlays come from
|
|
168
168
|
`TestbedHarnessEnvironment.documentRootLoader(fixture)`. An app on the mobile shell uses
|
|
169
169
|
`provideBfShellTesting()` from `@bitforgehq/ui/mobile` instead (router, shell, an in-memory theme
|
package/ai/MANIFEST.index.json
CHANGED
|
@@ -334,6 +334,15 @@
|
|
|
334
334
|
"summary": "Single-image picker with a round or square preview tile and replace or remove",
|
|
335
335
|
"since": "0.5.0"
|
|
336
336
|
},
|
|
337
|
+
{
|
|
338
|
+
"name": "image-tile",
|
|
339
|
+
"selector": "bf-image-tile",
|
|
340
|
+
"className": "BfImageTileComponent",
|
|
341
|
+
"category": "Data display",
|
|
342
|
+
"entryPoint": "@bitforgehq/ui",
|
|
343
|
+
"summary": "Small card for thumbnail grids: a picture and title that act as one target",
|
|
344
|
+
"since": "0.5.0"
|
|
345
|
+
},
|
|
337
346
|
{
|
|
338
347
|
"name": "image-viewer",
|
|
339
348
|
"selector": "bf-image-viewer",
|
package/ai/MANIFEST.json
CHANGED
|
@@ -876,7 +876,7 @@
|
|
|
876
876
|
"summary": "Small status label in semantic tones, or a compact numeric pill for counts",
|
|
877
877
|
"description": "Status label (\"On track\", \"At risk\") or, with `count`, a compact numeric pill for unread counts.",
|
|
878
878
|
"overview": "Badges label a status (\"On track\", \"At risk\") or, as a count pill, show how many items are waiting. They are read-only; use a `bf-chip` for anything the user can toggle.",
|
|
879
|
-
"example": "<bf-badge variant=\"warning\">At risk</bf-badge>\n<bf-badge variant=\"danger\" emphasis=\"strong\">Critical</bf-badge>\n<bf-badge [count]=\"unread()\" countLabel=\"unread messages\" />",
|
|
879
|
+
"example": "<bf-badge variant=\"warning\">At risk</bf-badge>\n<bf-badge variant=\"danger\" emphasis=\"strong\">Critical</bf-badge>\n<bf-badge [count]=\"unread()\" countLabel=\"unread messages\" />\n<bf-badge variant=\"success\"><bf-icon bfSlot=\"icon-start\" [icon]=\"Check\" />Reference</bf-badge>",
|
|
880
880
|
"primaryExample": "badge/overview",
|
|
881
881
|
"examples": [
|
|
882
882
|
{
|
|
@@ -900,6 +900,13 @@
|
|
|
900
900
|
"source": "import { Component } from '@angular/core';\nimport { BfBadgeComponent } from '@bitforgehq/ui';\n\n/** A strong badge marks the one critical finding among soft status badges. */\n@Component({\n selector: 'app-example-badge-strong',\n imports: [BfBadgeComponent],\n template: `\n <div class=\"flex flex-wrap items-center gap-2\">\n <bf-badge variant=\"danger\" emphasis=\"strong\">Critical</bf-badge>\n <bf-badge variant=\"danger\">High</bf-badge>\n <bf-badge variant=\"warning\">Medium</bf-badge>\n <bf-badge>Low</bf-badge>\n <bf-badge variant=\"danger\" emphasis=\"strong\" [count]=\"3\" countLabel=\"critical findings\" />\n </div>\n `,\n})\nexport class BadgeStrongExampleComponent {}\n",
|
|
901
901
|
"preview": "inline"
|
|
902
902
|
},
|
|
903
|
+
{
|
|
904
|
+
"id": "badge/icon",
|
|
905
|
+
"title": "Badges with a leading icon: the icon repeats the meaning, the text still says it",
|
|
906
|
+
"file": "projects/docs/src/app/examples/badge/icon-example.component.ts",
|
|
907
|
+
"source": "import { Component } from '@angular/core';\nimport { BfBadgeComponent } from '@bitforgehq/ui';\nimport { BfIconComponent } from '@bitforgehq/ui/icons';\nimport { Box, Check, Clock } from 'lucide';\n\n/** Badges with a leading icon: the icon repeats the meaning, the text still says it. */\n@Component({\n selector: 'app-example-badge-icon',\n imports: [BfBadgeComponent, BfIconComponent],\n template: `\n <div class=\"flex flex-wrap items-center gap-2\">\n <bf-badge variant=\"success\"><bf-icon bfSlot=\"icon-start\" [icon]=\"check\" />Reference</bf-badge>\n <bf-badge variant=\"info\"><bf-icon bfSlot=\"icon-start\" [icon]=\"box\" />3 versions</bf-badge>\n <bf-badge variant=\"warning\" emphasis=\"strong\"\n ><bf-icon bfSlot=\"icon-start\" [icon]=\"clock\" />Queued</bf-badge\n >\n </div>\n `,\n})\nexport class BadgeIconExampleComponent {\n protected readonly check = Check;\n protected readonly box = Box;\n protected readonly clock = Clock;\n}\n",
|
|
908
|
+
"preview": "inline"
|
|
909
|
+
},
|
|
903
910
|
{
|
|
904
911
|
"id": "badge/count",
|
|
905
912
|
"title": "A count pill that caps at 99+",
|
|
@@ -924,6 +931,10 @@
|
|
|
924
931
|
"heading": "Emphasis",
|
|
925
932
|
"body": "`emphasis=\"strong\"` turns the soft tint into a solid fill with contrasting text, for the one state that must stand out from the rest: a \"Critical\" finding among \"High\" and \"Medium\" ones. It takes every variant (`primary` uses the brand fill) and the count pill too: a strong count takes its fill from `variant`, so `variant=\"danger\" emphasis=\"strong\" [count]=\"3\"` is a red pill, while a soft count stays the primary pill. `emphasis` takes a `BfBadgeEmphasis` (`soft`, the default, or `strong`).\n\n<!-- example: badge/strong -->"
|
|
926
933
|
},
|
|
934
|
+
{
|
|
935
|
+
"heading": "Icon",
|
|
936
|
+
"body": "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.\n\n<!-- example: badge/icon -->"
|
|
937
|
+
},
|
|
927
938
|
{
|
|
928
939
|
"heading": "Count pill",
|
|
929
940
|
"body": "Setting `count` switches to the compact primary pill. Values above 99 render as \"99+\"; zero renders as \"0\".\n\n<!-- example: badge/count -->"
|
|
@@ -972,6 +983,10 @@
|
|
|
972
983
|
"outputs": [],
|
|
973
984
|
"models": [],
|
|
974
985
|
"slots": [
|
|
986
|
+
{
|
|
987
|
+
"name": "icon-start",
|
|
988
|
+
"doc": "An icon before the text, drawn at 12 px; not shown on a count pill."
|
|
989
|
+
},
|
|
975
990
|
{
|
|
976
991
|
"name": "default",
|
|
977
992
|
"doc": "The badge text, shown when no `count` is set."
|
|
@@ -979,6 +994,10 @@
|
|
|
979
994
|
],
|
|
980
995
|
"methods": [],
|
|
981
996
|
"changelog": [
|
|
997
|
+
{
|
|
998
|
+
"version": "0.5.0",
|
|
999
|
+
"text": "New `icon-start` slot: `<bf-icon bfSlot=\"icon-start\" … />` before the label text, drawn at 12 px\nwhatever the icon's `size`, 4 px from the text, with the start padding tightened to 8 px. It\ntakes the text colour, so it follows the variant and forced colours. A count pill does not show\nit."
|
|
1000
|
+
},
|
|
982
1001
|
{
|
|
983
1002
|
"version": "0.4.0",
|
|
984
1003
|
"text": "New `emphasis` input (`BfBadgeEmphasis`: `soft`, the default, or `strong`). `strong` is a solid\nfill with contrasting text in every variant, for the one state that must stand out (\"Critical\");\n`primary` uses the brand fill. A strong count pill takes its fill from `variant` (a soft count pill\nstays primary, as before). In forced colours a strong badge keeps a `CanvasText` outline. Opt-in:\nwithout `emphasis` nothing changes."
|
|
@@ -994,8 +1013,8 @@
|
|
|
994
1013
|
],
|
|
995
1014
|
"since": "0.1.0",
|
|
996
1015
|
"whenToUse": "Use `bf-badge` for a read-only status or count next to what it describes: \"At risk\" on a project, \"3\" on an inbox row. Use [bf-chip](https://bitforge-ui.web.app/components/chip) instead when the user can toggle it, and [bf-alert](https://bitforge-ui.web.app/components/alert) when the status needs a sentence of explanation.",
|
|
997
|
-
"theming": "Consumes tokens by variant, background then text:\n\n- `neutral` (default): `neutral-soft` / `neutral`\n- `primary`: `primary-soft` / `primary-strong`\n- `success`: `success-soft` / `success`\n- `warning`: `warning-soft` / `warning`\n- `danger`: `danger-soft` / `danger`\n- `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)\n- Count pill (`count` set): `primary` / `on-primary`, ignoring `variant`\n- 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\n- Shape (label and count pill): `rounded-control` (`--radius-control`, `9999px` by default).",
|
|
998
|
-
"accessibility": "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.",
|
|
1016
|
+
"theming": "Consumes tokens by variant, background then text:\n\n- `neutral` (default): `neutral-soft` / `neutral`\n- `primary`: `primary-soft` / `primary-strong`\n- `success`: `success-soft` / `success`\n- `warning`: `warning-soft` / `warning`\n- `danger`: `danger-soft` / `danger`\n- `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)\n- Count pill (`count` set): `primary` / `on-primary`, ignoring `variant`\n- 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\n- Icon (`icon-start`): drawn in the text colour (`currentColor`), so it follows the variant and stays visible in forced colours\n- Shape (label and count pill): `rounded-control` (`--radius-control`, `9999px` by default).",
|
|
1017
|
+
"accessibility": "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.",
|
|
999
1018
|
"notes": "## Do\n\n- Keep labels to one or two words; badges are scanned, not read.\n- Pair a count pill with a label or icon that says what is being counted, and set `countLabel` so the number is not read alone.\n- Keep `emphasis=\"strong\"` for the one state that must stand out on the screen, such as critical findings.\n\n## Don't\n\n- Don't use a badge as a button; it has no interactive states.\n- Don't invent new colors on it; pick the variant that matches the meaning.\n- Don't use `primary` or `success` for a category; use `info`, so the status tones keep their meaning.\n- Don't make every badge strong: when all of them shout, none stands out."
|
|
1000
1019
|
},
|
|
1001
1020
|
{
|
|
@@ -6318,6 +6337,185 @@
|
|
|
6318
6337
|
"accessibility": "The tile is a native `<button>` named \"Choose image\" (`imageInputChoose`; `ariaLabel` replaces it while there is no visible label) and described by the label, the help and the error paragraph, so a screen reader hears \"Choose image, Profile picture\". `aria-invalid` and `aria-errormessage` are set once touched and invalid. Upload / Replace and Remove are native buttons described by the label, so \"Remove\" says what it removes. The preview image is decorative (`alt=\"\"`): the label names what it shows. The native `<input type=\"file\">` is visually hidden and out of the tab order. `required` gives the label its marker; there is no `aria-required`, which is not allowed on a button. The rejection reason shows at once under the buttons, and a polite live region announces it. After Remove, focus returns to the tile. `focus()` focuses the tile; Signal Forms calls it from `focusBoundControl()`.\n\n### Keyboard\n\n| Key | Where | Action |\n| ------------------------------------------------ | ------------ | ---------------------------------------------------------------- |\n| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Control | Moves through the tile, Upload / Replace and Remove |\n| <kbd>Enter</kbd> / <kbd>Space</kbd> | Tile, Upload | Opens the native file picker |\n| <kbd>Enter</kbd> / <kbd>Space</kbd> | Remove | Clears the picked and the existing image; focus goes to the tile |",
|
|
6319
6338
|
"notes": "## Do\n\n- Bind `[(src)]` to the saved picture, and compare `value` and `src` on save to know what changed.\n- Set `accept` and `maxSize` to what the server takes.\n- Use `square` for logos and covers, `round` for people.\n\n## Don't\n\n- Don't use it for several files or documents; use `bf-file-input`.\n- Don't bind both `[(value)]` and `[formField]`.\n- Don't count on `required()` while a `src` can fill the field; use the `validate()` rule."
|
|
6320
6339
|
},
|
|
6340
|
+
{
|
|
6341
|
+
"name": "image-tile",
|
|
6342
|
+
"selector": "bf-image-tile",
|
|
6343
|
+
"selectors": ["bf-image-tile"],
|
|
6344
|
+
"className": "BfImageTileComponent",
|
|
6345
|
+
"entryPoint": "@bitforgehq/ui",
|
|
6346
|
+
"file": "src/components/image-tile/image-tile.component.ts",
|
|
6347
|
+
"category": "Data display",
|
|
6348
|
+
"summary": "Small card for thumbnail grids: a picture and title that act as one target",
|
|
6349
|
+
"description": "Small card for a grid of thumbnails: mostly picture, at a fixed aspect ratio (square by\ndefault), with a title and an optional meta line below it or, with `captionPlacement=\"overlay\"`,\non a soft gradient over its bottom. Without a picture yet it shows a placeholder (an icon and a\ntext); while one loads a shimmering skeleton; the picture fades in when it arrives; a failed load\nshows a short message. Make the whole tile one link or button with a `bfAction` in its title, as\non `bf-card`: it then lifts on hover, zooms the picture slightly and shows the focus ring.\n`selected` draws a ring. The title clamps to two lines and always takes two, so tiles in a grid\nrow are as tall whatever their titles.",
|
|
6350
|
+
"overview": "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.",
|
|
6351
|
+
"example": "<bf-image-tile [src]=\"model.thumbnail\" [selected]=\"model.id === current()\">\n <h3><a bfAction [routerLink]=\"['/models', model.id]\">{{ model.name }}</a></h3>\n <span bfSlot=\"meta\">{{ model.updated }}</span>\n <bf-badge bfSlot=\"badge\" variant=\"info\">3 versions</bf-badge>\n</bf-image-tile>",
|
|
6352
|
+
"primaryExample": "image-tile/overview",
|
|
6353
|
+
"examples": [
|
|
6354
|
+
{
|
|
6355
|
+
"id": "image-tile/overview",
|
|
6356
|
+
"title": "The bf-image-tile overview",
|
|
6357
|
+
"file": "projects/docs/src/app/examples/image-tile/overview-example.component.ts",
|
|
6358
|
+
"source": "import { Component, DestroyRef, inject, signal } from '@angular/core';\nimport { BfActionDirective, BfBadgeComponent, BfImageTileComponent } from '@bitforgehq/ui';\nimport { BfIconComponent } from '@bitforgehq/ui/icons';\nimport { Box, ImagePlus } from 'lucide';\n\n/** One model in the grid, as the app tracks it. */\ninterface Model {\n readonly id: string;\n readonly name: string;\n readonly src: string;\n readonly alt: string;\n readonly versions: number;\n readonly updated: string;\n}\n\n/** How long the simulated render of the last model takes. */\nconst RENDER_MS = 3000;\n\n/**\n * The bf-image-tile overview.\n *\n * A grid of 3D models, one picture each: a click selects a tile (the ring, and \"Selected\" read\n * before its title), a badge counts the versions, the last picture is still rendering and fades\n * in when it arrives, and a model without a render yet shows the placeholder. The titles differ\n * in length, yet every tile in a row is as tall.\n */\n@Component({\n selector: 'app-example-image-tile-overview',\n imports: [BfActionDirective, BfBadgeComponent, BfIconComponent, BfImageTileComponent],\n template: `\n <div class=\"grid grid-cols-2 gap-4 @md:grid-cols-4\">\n @for (model of models(); track model.id) {\n <bf-image-tile\n [src]=\"model.src\"\n [alt]=\"model.alt\"\n [loading]=\"model.id === 'lantern' && rendering()\"\n [selected]=\"model.id === current()\"\n >\n <h2>\n <button bfAction type=\"button\" (click)=\"current.set(model.id)\">{{ model.name }}</button>\n </h2>\n <span bfSlot=\"meta\">{{ model.updated }}</span>\n @if (model.versions > 1) {\n <bf-badge bfSlot=\"badge\" variant=\"info\"\n ><bf-icon bfSlot=\"icon-start\" [icon]=\"box\" />{{ model.versions }} versions</bf-badge\n >\n }\n <bf-icon bfSlot=\"placeholder-icon\" [icon]=\"imagePlus\" />\n </bf-image-tile>\n }\n </div>\n <p aria-live=\"polite\" class=\"text-caption text-text-muted mt-3\">\n Selected: {{ currentName() }}\n </p>\n `,\n})\nexport class ImageTileOverviewExampleComponent {\n protected readonly box = Box;\n protected readonly imagePlus = ImagePlus;\n protected readonly current = signal('ship');\n protected readonly rendering = signal(true);\n protected readonly models = signal<Model[]>([\n {\n id: 'ship',\n name: 'Harbour ship',\n src: 'media/harbour.svg',\n alt: 'A ship in a harbour',\n versions: 3,\n updated: 'Today',\n },\n {\n id: 'tower',\n name: 'Glacier watchtower with a long rope bridge',\n src: 'media/glacier.svg',\n alt: 'A tower on a glacier',\n versions: 1,\n updated: 'Yesterday',\n },\n { id: 'well', name: 'Desert well', src: '', alt: '', versions: 1, updated: 'Last week' },\n {\n id: 'lantern',\n name: 'Forest lantern',\n src: '',\n alt: 'A lantern in a pine forest',\n versions: 2,\n updated: 'Rendering…',\n },\n ]);\n\n constructor() {\n const timer = setTimeout(() => {\n this.models.update((models) =>\n models.map((m) =>\n m.id === 'lantern' ? { ...m, src: 'media/forest.svg', updated: 'Just now' } : m,\n ),\n );\n this.rendering.set(false);\n }, RENDER_MS);\n inject(DestroyRef).onDestroy(() => clearTimeout(timer));\n }\n\n protected currentName(): string {\n return this.models().find((m) => m.id === this.current())?.name ?? '';\n }\n}\n",
|
|
6359
|
+
"preview": "inline"
|
|
6360
|
+
},
|
|
6361
|
+
{
|
|
6362
|
+
"id": "image-tile/states",
|
|
6363
|
+
"title": "The four states of a tile side by side",
|
|
6364
|
+
"file": "projects/docs/src/app/examples/image-tile/states-example.component.ts",
|
|
6365
|
+
"source": "import { Component } from '@angular/core';\nimport { BfImageTileComponent } from '@bitforgehq/ui';\nimport { BfIconComponent } from '@bitforgehq/ui/icons';\nimport { ImagePlus } from 'lucide';\n\n/**\n * The four states of a tile side by side.\n *\n * Without `src` the placeholder shows (an icon and \"No image yet\"); `loading` shows the skeleton;\n * a `src` fades in when it loads; `failed` (or a load error) shows a short message.\n */\n@Component({\n selector: 'app-example-image-tile-states',\n imports: [BfIconComponent, BfImageTileComponent],\n template: `\n <div class=\"grid grid-cols-2 gap-4 @md:grid-cols-4\">\n <bf-image-tile>\n <h2>Placeholder</h2>\n <bf-icon bfSlot=\"placeholder-icon\" [icon]=\"imagePlus\" />\n </bf-image-tile>\n <bf-image-tile loading><h2>Loading</h2></bf-image-tile>\n <bf-image-tile src=\"media/forest.svg\" alt=\"A pine forest under a pale sun\">\n <h2>Loaded</h2>\n </bf-image-tile>\n <bf-image-tile failed failedLabel=\"Render failed\"><h2>Failed</h2></bf-image-tile>\n </div>\n `,\n})\nexport class ImageTileStatesExampleComponent {\n protected readonly imagePlus = ImagePlus;\n}\n",
|
|
6366
|
+
"preview": "inline"
|
|
6367
|
+
},
|
|
6368
|
+
{
|
|
6369
|
+
"id": "image-tile/overlay",
|
|
6370
|
+
"title": "A board of reference pictures with the caption over the picture",
|
|
6371
|
+
"file": "projects/docs/src/app/examples/image-tile/overlay-example.component.ts",
|
|
6372
|
+
"source": "import { Component } from '@angular/core';\nimport { BfActionDirective, BfBadgeComponent, BfImageTileComponent } from '@bitforgehq/ui';\nimport { BfIconComponent } from '@bitforgehq/ui/icons';\nimport { Check } from 'lucide';\n\n/** One reference picture on the board. */\ninterface Reference {\n readonly id: string;\n readonly title: string;\n readonly source: string;\n readonly src: string;\n readonly reference: boolean;\n}\n\n/**\n * A board of reference pictures with the caption over the picture.\n *\n * `captionPlacement=\"overlay\"` lays the title and meta line on a soft gradient over the bottom of\n * a 4:3 picture; each tile is a link, and a check badge marks the chosen references.\n */\n@Component({\n selector: 'app-example-image-tile-overlay',\n imports: [BfActionDirective, BfBadgeComponent, BfIconComponent, BfImageTileComponent],\n template: `\n <div class=\"grid grid-cols-1 gap-4 @sm:grid-cols-2 @lg:grid-cols-3\">\n @for (item of references; track item.id) {\n <bf-image-tile [src]=\"item.src\" alt=\"\" aspect=\"4/3\" captionPlacement=\"overlay\">\n <h2>\n <a bfAction [href]=\"'#' + item.id\">{{ item.title }}</a>\n </h2>\n <span bfSlot=\"meta\">{{ item.source }}</span>\n @if (item.reference) {\n <bf-badge bfSlot=\"badge\" variant=\"success\" emphasis=\"strong\"\n ><bf-icon bfSlot=\"icon-start\" [icon]=\"check\" />Reference</bf-badge\n >\n }\n </bf-image-tile>\n }\n </div>\n `,\n})\nexport class ImageTileOverlayExampleComponent {\n protected readonly check = Check;\n protected readonly references: Reference[] = [\n {\n id: 'skyline',\n title: 'Skyline at dusk',\n source: 'Moodboard',\n src: 'media/skyline.svg',\n reference: true,\n },\n {\n id: 'desert',\n title: 'Dunes in the late afternoon, warm light from the west',\n source: 'Upload',\n src: 'media/desert.svg',\n reference: false,\n },\n {\n id: 'glacier',\n title: 'Glacier',\n source: 'Moodboard',\n src: 'media/glacier.svg',\n reference: true,\n },\n ];\n}\n",
|
|
6373
|
+
"preview": "inline"
|
|
6374
|
+
}
|
|
6375
|
+
],
|
|
6376
|
+
"usage": [
|
|
6377
|
+
{
|
|
6378
|
+
"heading": "Basic usage",
|
|
6379
|
+
"body": "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.\n\n<!-- example: image-tile/overview -->"
|
|
6380
|
+
},
|
|
6381
|
+
{
|
|
6382
|
+
"heading": "States",
|
|
6383
|
+
"body": "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.\n\n<!-- example: image-tile/states -->"
|
|
6384
|
+
},
|
|
6385
|
+
{
|
|
6386
|
+
"heading": "Caption over the picture",
|
|
6387
|
+
"body": "`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.\n\n<!-- example: image-tile/overlay -->"
|
|
6388
|
+
},
|
|
6389
|
+
{
|
|
6390
|
+
"heading": "Grids of tiles",
|
|
6391
|
+
"body": "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."
|
|
6392
|
+
}
|
|
6393
|
+
],
|
|
6394
|
+
"inputs": [
|
|
6395
|
+
{
|
|
6396
|
+
"name": "src",
|
|
6397
|
+
"kind": "input",
|
|
6398
|
+
"type": "string",
|
|
6399
|
+
"required": false,
|
|
6400
|
+
"doc": "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.",
|
|
6401
|
+
"default": "''"
|
|
6402
|
+
},
|
|
6403
|
+
{
|
|
6404
|
+
"name": "alt",
|
|
6405
|
+
"kind": "input",
|
|
6406
|
+
"type": "string",
|
|
6407
|
+
"required": false,
|
|
6408
|
+
"doc": "Alternative text of the picture; empty marks it decorative (the title names the tile).",
|
|
6409
|
+
"default": "''"
|
|
6410
|
+
},
|
|
6411
|
+
{
|
|
6412
|
+
"name": "aspect",
|
|
6413
|
+
"kind": "input",
|
|
6414
|
+
"type": "BfImageTileAspect",
|
|
6415
|
+
"required": false,
|
|
6416
|
+
"doc": "Aspect ratio of the picture: `1/1` (default), `4/3`, `16/9` or `3/4`.",
|
|
6417
|
+
"default": "'1/1'",
|
|
6418
|
+
"expandedType": "'1/1' | '4/3' | '16/9' | '3/4'"
|
|
6419
|
+
},
|
|
6420
|
+
{
|
|
6421
|
+
"name": "captionPlacement",
|
|
6422
|
+
"kind": "input",
|
|
6423
|
+
"type": "BfImageTileCaptionPlacement",
|
|
6424
|
+
"required": false,
|
|
6425
|
+
"doc": "Where the title and the meta line sit: `below` the picture (default) or `overlay`, on a gradient over its bottom.",
|
|
6426
|
+
"default": "'below'",
|
|
6427
|
+
"expandedType": "'below' | 'overlay'"
|
|
6428
|
+
},
|
|
6429
|
+
{
|
|
6430
|
+
"name": "loading",
|
|
6431
|
+
"kind": "input",
|
|
6432
|
+
"type": "boolean",
|
|
6433
|
+
"required": false,
|
|
6434
|
+
"doc": "Shows the skeleton while there is no `src` yet (a picture that is still being made).",
|
|
6435
|
+
"default": "false"
|
|
6436
|
+
},
|
|
6437
|
+
{
|
|
6438
|
+
"name": "failed",
|
|
6439
|
+
"kind": "input",
|
|
6440
|
+
"type": "boolean",
|
|
6441
|
+
"required": false,
|
|
6442
|
+
"doc": "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.",
|
|
6443
|
+
"default": "false"
|
|
6444
|
+
},
|
|
6445
|
+
{
|
|
6446
|
+
"name": "selected",
|
|
6447
|
+
"kind": "input",
|
|
6448
|
+
"type": "boolean",
|
|
6449
|
+
"required": false,
|
|
6450
|
+
"doc": "Marks the tile as the selected one: a ring around it, and \"Selected\" read before its title.",
|
|
6451
|
+
"default": "false"
|
|
6452
|
+
},
|
|
6453
|
+
{
|
|
6454
|
+
"name": "placeholderLabel",
|
|
6455
|
+
"kind": "input",
|
|
6456
|
+
"type": "string",
|
|
6457
|
+
"required": false,
|
|
6458
|
+
"doc": "Placeholder text while there is no picture; empty uses `provideBfLabels()`'s `imageTileEmpty` (\"No image yet\").",
|
|
6459
|
+
"default": "''"
|
|
6460
|
+
},
|
|
6461
|
+
{
|
|
6462
|
+
"name": "failedLabel",
|
|
6463
|
+
"kind": "input",
|
|
6464
|
+
"type": "string",
|
|
6465
|
+
"required": false,
|
|
6466
|
+
"doc": "Failure message; empty uses `provideBfLabels()`'s `imageLoadFailed`.",
|
|
6467
|
+
"default": "''"
|
|
6468
|
+
}
|
|
6469
|
+
],
|
|
6470
|
+
"outputs": [
|
|
6471
|
+
{
|
|
6472
|
+
"name": "loaded",
|
|
6473
|
+
"kind": "output",
|
|
6474
|
+
"type": "void",
|
|
6475
|
+
"required": false,
|
|
6476
|
+
"doc": "The picture of `src` has loaded."
|
|
6477
|
+
},
|
|
6478
|
+
{
|
|
6479
|
+
"name": "errored",
|
|
6480
|
+
"kind": "output",
|
|
6481
|
+
"type": "void",
|
|
6482
|
+
"required": false,
|
|
6483
|
+
"doc": "The picture of `src` failed to load (the tile shows the failure message)."
|
|
6484
|
+
}
|
|
6485
|
+
],
|
|
6486
|
+
"models": [],
|
|
6487
|
+
"slots": [
|
|
6488
|
+
{
|
|
6489
|
+
"name": "placeholder-icon",
|
|
6490
|
+
"doc": "The placeholder's icon, shown with its text while there is no picture."
|
|
6491
|
+
},
|
|
6492
|
+
{
|
|
6493
|
+
"name": "badge",
|
|
6494
|
+
"doc": "A `bf-badge` in the picture's top start corner."
|
|
6495
|
+
},
|
|
6496
|
+
{
|
|
6497
|
+
"name": "default",
|
|
6498
|
+
"doc": "The title: a heading holding the tile's `bfAction` link or button."
|
|
6499
|
+
},
|
|
6500
|
+
{
|
|
6501
|
+
"name": "meta",
|
|
6502
|
+
"doc": "A short line under the title, such as a date or a count."
|
|
6503
|
+
}
|
|
6504
|
+
],
|
|
6505
|
+
"methods": [],
|
|
6506
|
+
"changelog": [
|
|
6507
|
+
{
|
|
6508
|
+
"version": "0.5.0",
|
|
6509
|
+
"text": "New: `bf-image-tile`, a small card for grids of thumbnails (3D models with one render each, a\nboard of reference pictures). It is mostly picture, at a fixed `aspect` (new type\n`BfImageTileAspect`: `'1/1'` (default), `'4/3'`, `'16/9'`, `'3/4'`), with the title (default slot,\na heading holding a `bfAction` link or button) and an optional `bfSlot=\"meta\"` line below the\npicture, or with `captionPlacement=\"overlay\"` (new type `BfImageTileCaptionPlacement`: `'below'`\n(default), `'overlay'`) on a soft `scrim` gradient over its bottom. States share\n`bf-media-card`'s logic: without `src` a placeholder (`bfSlot=\"placeholder-icon\"` and\n`placeholderLabel`, default: the new label `imageTileEmpty`, \"No image yet\"); `loading`, and a\n`src` that is loading, show a `bf-skeleton` shimmer (`aria-busy`); the picture fades in over\n260 ms; a load error or `failed` shows `failedLabel` (default `imageLoadFailed`). `loaded` and\n`errored` report the picture's events. A `bfAction` makes the whole tile one link or button: the\npointer, a 2 px lift to `shadow-floating` and a 5 % zoom of the picture on hover (no movement\nunder reduced motion), and the focus ring. `selected` draws a 2 px primary ring and starts the\naction's name with the new label `imageTileSelected` (\"Selected, Harbour ship\"). `bfSlot=\"badge\"`\nputs a `bf-badge` in the picture's top start corner. The title clamps to two lines and always\ntakes two, so tiles in a grid row are as tall. `@bitforgehq/ui/testing`: new\n`BfImageTileHarness` (`getTitle()`, `getMeta()`, `getState()`, `getSrc()`, `getAlt()`,\n`getPlaceholderText()`, `getFailedMessage()`, `isSelected()`, `isClickable()`, `click()`,\n`loadImage()`, `failImage()`)."
|
|
6510
|
+
}
|
|
6511
|
+
],
|
|
6512
|
+
"since": "0.5.0",
|
|
6513
|
+
"whenToUse": "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).",
|
|
6514
|
+
"desktop": "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.",
|
|
6515
|
+
"theming": "- Tile: `bg-surface`, `text-text`, `rounded-card`, `shadow-raised` (`shadow-floating` on hover).\n- Picture area: `bg-surface-sunken`; the skeleton is a `bf-skeleton` rect; the placeholder and failure texts are `text-caption text-text-muted`.\n- 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.\n- Selection: a 2 px `primary` outline 2 px outside the tile.\n- Forced colours: a 1 px `CanvasText` border keeps each tile's edge; the selection ring is `Highlight`.\n- Focus: `ring-4 ring-ring` around the tile.\n- 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.",
|
|
6516
|
+
"accessibility": "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.\n\n### Keyboard\n\n| Key | Action |\n| ---------------- | ---------------------------------------------------------- |\n| <kbd>Tab</kbd> | Moves to the tile's `bfAction` link or button |\n| <kbd>Enter</kbd> | Follows a `bfAction` link or activates a `bfAction` button |\n| <kbd>Space</kbd> | Activates a `bfAction` button; a link ignores it |",
|
|
6517
|
+
"notes": "## Do\n\n- Use a heading for the title, at the level that fits the page, with the `bfAction` inside it.\n- Set `loading` while a picture is being made, so the tile holds its place.\n- Keep the meta line short: a date, a count, a source.\n\n## Don't\n\n- 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.\n- Don't project your own `<img>` for the picture; pass `src`, so the tile handles loading, fading and failing.\n- Don't rely on the ring alone to mark the selected tile in your own text: the tile already tells assistive technology."
|
|
6518
|
+
},
|
|
6321
6519
|
{
|
|
6322
6520
|
"name": "image-viewer",
|
|
6323
6521
|
"selector": "bf-image-viewer",
|
|
@@ -7663,6 +7861,13 @@
|
|
|
7663
7861
|
"file": "projects/docs/src/app/examples/menu-button/sizes-example.component.ts",
|
|
7664
7862
|
"source": "import { Component, signal } from '@angular/core';\nimport {\n BfMenuButtonComponent,\n BfMenuComponent,\n BfMenuItemDirective,\n} from '@bitforgehq/ui/overlay';\n\n/**\n * A small menu button that opens below its end.\n *\n * `size=\"sm\"` sizes the trigger; `placement=\"below-end\"` aligns the menu to its inline end.\n */\n@Component({\n selector: 'app-example-menu-button-sizes',\n imports: [BfMenuButtonComponent, BfMenuComponent, BfMenuItemDirective],\n template: `\n <div class=\"flex justify-end\">\n <bf-menu-button [menu]=\"rowActions\" size=\"sm\" placement=\"below-end\">Actions</bf-menu-button>\n </div>\n <p aria-live=\"polite\" class=\"text-caption text-text-muted mt-3\">{{ result() }}</p>\n\n <ng-template #rowActions>\n <bf-menu>\n <button bfMenuItem (triggered)=\"result.set('Renamed.')\">Rename</button>\n <button bfMenuItem (triggered)=\"result.set('Archived.')\">Archive</button>\n </bf-menu>\n </ng-template>\n `,\n})\nexport class MenuButtonSizesExampleComponent {\n protected readonly result = signal('');\n}\n",
|
|
7665
7863
|
"preview": "inline"
|
|
7864
|
+
},
|
|
7865
|
+
{
|
|
7866
|
+
"id": "menu-button/full-width",
|
|
7867
|
+
"title": "A two-column grid of actions on a card, where a menu button fills its cell like the buttons beside it",
|
|
7868
|
+
"file": "projects/docs/src/app/examples/menu-button/full-width-example.component.ts",
|
|
7869
|
+
"source": "import { Component, signal } from '@angular/core';\nimport { BfButtonComponent } from '@bitforgehq/ui';\nimport {\n BfMenuButtonComponent,\n BfMenuComponent,\n BfMenuItemDirective,\n} from '@bitforgehq/ui/overlay';\n\n/**\n * A two-column grid of actions on a card, where a menu button fills its cell like the buttons\n * beside it.\n *\n * `fullWidth` stretches the host and its button, as on `bf-button`.\n */\n@Component({\n selector: 'app-example-menu-button-full-width',\n imports: [BfButtonComponent, BfMenuButtonComponent, BfMenuComponent, BfMenuItemDirective],\n template: `\n <div class=\"grid max-w-sm grid-cols-2 gap-2\">\n <bf-button variant=\"outline\" fullWidth (clicked)=\"result.set('Opened.')\">Open</bf-button>\n <bf-button variant=\"outline\" fullWidth (clicked)=\"result.set('Duplicated.')\"\n >Duplicate</bf-button\n >\n <bf-button variant=\"outline\" fullWidth (clicked)=\"result.set('Shared.')\">Share</bf-button>\n <bf-menu-button [menu]=\"more\" fullWidth>More</bf-menu-button>\n </div>\n <p aria-live=\"polite\" class=\"text-caption text-text-muted mt-3\">{{ result() }}</p>\n\n <ng-template #more>\n <bf-menu>\n <button bfMenuItem (triggered)=\"result.set('Renamed.')\">Rename</button>\n <button bfMenuItem (triggered)=\"result.set('Archived.')\">Archive</button>\n </bf-menu>\n </ng-template>\n `,\n})\nexport class MenuButtonFullWidthExampleComponent {\n protected readonly result = signal('');\n}\n",
|
|
7870
|
+
"preview": "inline"
|
|
7666
7871
|
}
|
|
7667
7872
|
],
|
|
7668
7873
|
"usage": [
|
|
@@ -7681,6 +7886,10 @@
|
|
|
7681
7886
|
{
|
|
7682
7887
|
"heading": "Sizes and placement",
|
|
7683
7888
|
"body": "`size` is `sm` 32, `md` 40 or `lg` 48 px; unset it follows `provideBfDefaults()` (`controlSize`,\n`md` by default). Under a coarse pointer `sm` and `md` reach 44 px without changing their look.\n`placement` sets where the menu opens: `'below-start'` (default), `'below-end'`,\n`'above-start'` or `'above-end'`, flipping when it would overflow the viewport.\n\n<!-- example: menu-button/sizes -->"
|
|
7889
|
+
},
|
|
7890
|
+
{
|
|
7891
|
+
"heading": "Full width",
|
|
7892
|
+
"body": "`fullWidth` stretches the host and its button to the width of their container, as on\n`bf-button`: in a grid of actions, a \"More\" menu button then fills its cell like the buttons\nbeside it. The label and caret stay centred. It does nothing with `iconOnly`, which stays a\nsquare.\n\n<!-- example: menu-button/full-width -->"
|
|
7684
7893
|
}
|
|
7685
7894
|
],
|
|
7686
7895
|
"inputs": [
|
|
@@ -7734,6 +7943,14 @@
|
|
|
7734
7943
|
"doc": "Square button holding only the projected icon, like `bf-icon-button`; set `label`.",
|
|
7735
7944
|
"default": "false"
|
|
7736
7945
|
},
|
|
7946
|
+
{
|
|
7947
|
+
"name": "fullWidth",
|
|
7948
|
+
"kind": "input",
|
|
7949
|
+
"type": "boolean",
|
|
7950
|
+
"required": false,
|
|
7951
|
+
"doc": "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`.",
|
|
7952
|
+
"default": "false"
|
|
7953
|
+
},
|
|
7737
7954
|
{
|
|
7738
7955
|
"name": "disabled",
|
|
7739
7956
|
"kind": "input",
|
|
@@ -7772,6 +7989,10 @@
|
|
|
7772
7989
|
],
|
|
7773
7990
|
"methods": [],
|
|
7774
7991
|
"changelog": [
|
|
7992
|
+
{
|
|
7993
|
+
"version": "0.5.0",
|
|
7994
|
+
"text": "New `fullWidth` input, as on `bf-button`: the host and its button stretch to the width of their\ncontainer (label and caret stay centred), so app CSS no longer needs to reach into the inner\n`<button>`. Ignored with `iconOnly`."
|
|
7995
|
+
},
|
|
7775
7996
|
{
|
|
7776
7997
|
"version": "0.3.0",
|
|
7777
7998
|
"text": "New `bf-menu-button` in `@bitforgehq/ui/overlay`: a native `<button>` that opens a `bf-menu`\n(`menu`, a `TemplateRef`) with `aria-haspopup=\"menu\"` and `aria-expanded`, styled like\n`bf-button` (`variant` `outline` (default), `text`, `tonal`; a CSS caret) or, with `iconOnly`,\nlike `bf-icon-button`. `size` (unset follows `controlSize`), `placement`, `label` (the\n`aria-label`, required with `iconOnly`), `disabled`, and `opened`/`closed` outputs. Its classes\ncome from `bf-button`'s and `bf-icon-button`'s own class maps (internal `ɵ` exports of\n`@bitforgehq/ui`), so the looks never drift.\n\nJSDoc of `menu-button.component.ts` for the API reference: `@summary`/`@since` on the exported\nsymbols; `@slot` docs for `icon-start`, `default`. No behaviour change.\n\n**Behaviour:** a pressed state (F-045): the button and icon-only forms take `bf-button`'s and `bf-icon-button`'s pressed fills from their shared class maps. None while disabled; under forced colours the system colours win."
|
|
@@ -8964,9 +9185,9 @@
|
|
|
8964
9185
|
"file": "src/components/pagination/pagination.component.ts",
|
|
8965
9186
|
"category": "Navigation",
|
|
8966
9187
|
"summary": "Previous, next and numbered page buttons for a long list or table",
|
|
8967
|
-
"description": "Page navigation for a long list or table: Previous and Next buttons around the page numbers,\nthe current one marked, the hidden runs shown as an ellipsis. `compact` shows \"Page 3 of 12\"\nbetween the arrows instead, for narrow screens. The page count comes from `pageCount`, or from\n`total` and `pageSize`; the consumer shows the rows of `page`.",
|
|
9188
|
+
"description": "Page navigation for a long list or table: Previous and Next buttons around the page numbers,\nthe current one marked, the hidden runs shown as an ellipsis. `compact` shows \"Page 3 of 12\"\nbetween the arrows instead, for narrow screens. The page count comes from `pageCount`, or from\n`total` and `pageSize`; the consumer shows the rows of `page`. A compact pagination with its own\n`positionLabel`, `previousLabel` and `nextLabel` steps through anything numbered, such as the\nversions of a model (\"‹ v3 of 7 ›\").",
|
|
8968
9189
|
"overview": "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.",
|
|
8969
|
-
"example": "<bf-pagination [(page)]=\"page\" [pageCount]=\"12\" />\n<bf-pagination [(page)]=\"page\" [total]=\"invoices().length\" [pageSize]=\"10\" compact />",
|
|
9190
|
+
"example": "<bf-pagination [(page)]=\"page\" [pageCount]=\"12\" />\n<bf-pagination [(page)]=\"page\" [total]=\"invoices().length\" [pageSize]=\"10\" compact />\n<bf-pagination [(page)]=\"version\" [pageCount]=\"7\" compact [positionLabel]=\"versionOf\"\n label=\"Versions\" previousLabel=\"Previous version\" nextLabel=\"Next version\" />",
|
|
8970
9191
|
"primaryExample": "pagination/overview",
|
|
8971
9192
|
"examples": [
|
|
8972
9193
|
{
|
|
@@ -8982,6 +9203,13 @@
|
|
|
8982
9203
|
"file": "projects/docs/src/app/examples/pagination/compact-example.component.ts",
|
|
8983
9204
|
"source": "import { Component, signal } from '@angular/core';\nimport { BfPaginationComponent } from '@bitforgehq/ui';\n\n/**\n * Compact pagination for a narrow space.\n *\n * `compact` shows \"Page 3 of 12\" between the arrows instead of the page buttons; the text is a\n * polite live region, so each step is announced.\n */\n@Component({\n selector: 'app-example-pagination-compact',\n imports: [BfPaginationComponent],\n template: `<bf-pagination\n label=\"Compact result pages\"\n compact\n [(page)]=\"page\"\n [pageCount]=\"12\"\n />`,\n})\nexport class PaginationCompactExampleComponent {\n readonly page = signal(3);\n}\n",
|
|
8984
9205
|
"preview": "inline"
|
|
9206
|
+
},
|
|
9207
|
+
{
|
|
9208
|
+
"id": "pagination/versions",
|
|
9209
|
+
"title": "A compact pager that steps through the versions of a model, \"‹ v3 of 7 ›\"",
|
|
9210
|
+
"file": "projects/docs/src/app/examples/pagination/versions-example.component.ts",
|
|
9211
|
+
"source": "import { Component, signal } from '@angular/core';\nimport { BfPaginationComponent, type BfPaginationPositionLabel } from '@bitforgehq/ui';\n\n/**\n * A compact pager that steps through the versions of a model, \"‹ v3 of 7 ›\".\n *\n * `positionLabel` writes the position text; `previousLabel` and `nextLabel` name the arrows.\n */\n@Component({\n selector: 'app-example-pagination-versions',\n imports: [BfPaginationComponent],\n template: `\n <bf-pagination\n compact\n size=\"sm\"\n label=\"Versions\"\n previousLabel=\"Previous version\"\n nextLabel=\"Next version\"\n [positionLabel]=\"versionOf\"\n [pageCount]=\"7\"\n [(page)]=\"version\"\n />\n `,\n})\nexport class PaginationVersionsExampleComponent {\n readonly version = signal(3);\n protected readonly versionOf: BfPaginationPositionLabel = (page, count) => `v${page} of ${count}`;\n}\n",
|
|
9212
|
+
"preview": "inline"
|
|
8985
9213
|
}
|
|
8986
9214
|
],
|
|
8987
9215
|
"usage": [
|
|
@@ -8997,6 +9225,10 @@
|
|
|
8997
9225
|
"heading": "Compact",
|
|
8998
9226
|
"body": "`compact` replaces the page buttons with the position as text, \"Page 3 of 12\" (`provideBfLabels()`'s `pageOf`), between the arrows. Use it where the full row does not fit: a narrow column, a phone, a side panel. A responsive page can render the full row from `md` up and the compact one below it (a `hidden md:block` and a `md:hidden` wrapper around two `bf-pagination`s bound to the same `page`).\n\n<!-- example: pagination/compact -->"
|
|
8999
9227
|
},
|
|
9228
|
+
{
|
|
9229
|
+
"heading": "Stepping through versions",
|
|
9230
|
+
"body": "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.\n\n<!-- example: pagination/versions -->"
|
|
9231
|
+
},
|
|
9000
9232
|
{
|
|
9001
9233
|
"heading": "With a table",
|
|
9002
9234
|
"body": "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."
|
|
@@ -9047,6 +9279,30 @@
|
|
|
9047
9279
|
"doc": "Shows \"Page 3 of 12\" between the arrows instead of the page buttons, for narrow screens.",
|
|
9048
9280
|
"default": "false"
|
|
9049
9281
|
},
|
|
9282
|
+
{
|
|
9283
|
+
"name": "positionLabel",
|
|
9284
|
+
"kind": "input",
|
|
9285
|
+
"type": "BfPaginationPositionLabel | undefined",
|
|
9286
|
+
"required": false,
|
|
9287
|
+
"doc": "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`.",
|
|
9288
|
+
"default": "undefined"
|
|
9289
|
+
},
|
|
9290
|
+
{
|
|
9291
|
+
"name": "previousLabel",
|
|
9292
|
+
"kind": "input",
|
|
9293
|
+
"type": "string",
|
|
9294
|
+
"required": false,
|
|
9295
|
+
"doc": "Accessible name of the previous button, such as \"Previous version\"; empty falls back to `provideBfLabels()`'s `previousPage` (\"Previous page\").",
|
|
9296
|
+
"default": "''"
|
|
9297
|
+
},
|
|
9298
|
+
{
|
|
9299
|
+
"name": "nextLabel",
|
|
9300
|
+
"kind": "input",
|
|
9301
|
+
"type": "string",
|
|
9302
|
+
"required": false,
|
|
9303
|
+
"doc": "Accessible name of the next button, such as \"Next version\"; empty falls back to `provideBfLabels()`'s `nextPage` (\"Next page\").",
|
|
9304
|
+
"default": "''"
|
|
9305
|
+
},
|
|
9050
9306
|
{
|
|
9051
9307
|
"name": "size",
|
|
9052
9308
|
"kind": "input",
|
|
@@ -9095,6 +9351,10 @@
|
|
|
9095
9351
|
}
|
|
9096
9352
|
],
|
|
9097
9353
|
"changelog": [
|
|
9354
|
+
{
|
|
9355
|
+
"version": "0.5.0",
|
|
9356
|
+
"text": "New `positionLabel` input (new type `BfPaginationPositionLabel`, `(page, count) => string`) that\nwrites a compact pagination's position text, such as \"v3 of 7\"; unset it is the `pageOf` label as\nbefore. New `previousLabel` and `nextLabel` inputs name the arrows (\"Previous version\"); empty\nthey fall back to `previousPage` and `nextPage`. Together they make a compact pager for stepping\nthrough versions."
|
|
9357
|
+
},
|
|
9098
9358
|
{
|
|
9099
9359
|
"version": "0.3.1",
|
|
9100
9360
|
"text": "New `bf-pagination` in `@bitforgehq/ui`, page navigation for both shells: a `nav` (named by\n`label`, else the new `pagination` label) with Previous and Next buttons around the page buttons.\n`[(page)]` is 1-based; the count comes from `pageCount`, else `total` over `pageSize` (default 10).\nThe row keeps the first and the last page and `siblings` (default 1) on each side of the current\none, which carries `aria-current=\"page\"`, and turns hidden runs into an `aria-hidden` ellipsis, at\na constant number of slots. `compact` shows \"Page 3 of 12\" (`pageOf`) between the arrows as a\npolite live region. At the ends the arrows are `aria-disabled` and stay focusable. `size` follows\n`controlSize`; the chevrons are CSS and mirror under `dir=\"rtl\"`. New `BfPaginationHarness` in\n`@bitforgehq/ui/testing` (`getCurrentPage()`, `getPageCount()`, `getVisiblePages()`, `goTo()`,\n`next()`, `previous()`)."
|
package/ai/REFERENCE.index.json
CHANGED
|
@@ -337,6 +337,20 @@
|
|
|
337
337
|
"entryPoint": "@bitforgehq/ui",
|
|
338
338
|
"summary": "The two tile shapes of an image picker, a circle or a rounded square"
|
|
339
339
|
},
|
|
340
|
+
{
|
|
341
|
+
"name": "BfImageTileAspect",
|
|
342
|
+
"slug": "bf-image-tile-aspect",
|
|
343
|
+
"kind": "type",
|
|
344
|
+
"entryPoint": "@bitforgehq/ui",
|
|
345
|
+
"summary": "The four aspect ratios of an image tile's picture, square by default"
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
"name": "BfImageTileCaptionPlacement",
|
|
349
|
+
"slug": "bf-image-tile-caption-placement",
|
|
350
|
+
"kind": "type",
|
|
351
|
+
"entryPoint": "@bitforgehq/ui",
|
|
352
|
+
"summary": "Where an image tile's caption sits, below the picture or over it"
|
|
353
|
+
},
|
|
340
354
|
{
|
|
341
355
|
"name": "BfInputAutocapitalize",
|
|
342
356
|
"slug": "bf-input-autocapitalize",
|
|
@@ -407,6 +421,13 @@
|
|
|
407
421
|
"entryPoint": "@bitforgehq/ui",
|
|
408
422
|
"summary": "The heading level of a page header title, one for a page or two"
|
|
409
423
|
},
|
|
424
|
+
{
|
|
425
|
+
"name": "BfPaginationPositionLabel",
|
|
426
|
+
"slug": "bf-pagination-position-label",
|
|
427
|
+
"kind": "type",
|
|
428
|
+
"entryPoint": "@bitforgehq/ui",
|
|
429
|
+
"summary": "Function that writes a compact pager's position text, such as \"v3 of 7\""
|
|
430
|
+
},
|
|
410
431
|
{
|
|
411
432
|
"name": "BfProgressSize",
|
|
412
433
|
"slug": "bf-progress-size",
|
|
@@ -1982,6 +2003,13 @@
|
|
|
1982
2003
|
"entryPoint": "@bitforgehq/ui/testing",
|
|
1983
2004
|
"summary": "Test harness for the image input: drops an image on it and reads the preview"
|
|
1984
2005
|
},
|
|
2006
|
+
{
|
|
2007
|
+
"name": "BfImageTileHarness",
|
|
2008
|
+
"slug": "bf-image-tile-harness",
|
|
2009
|
+
"kind": "class",
|
|
2010
|
+
"entryPoint": "@bitforgehq/ui/testing",
|
|
2011
|
+
"summary": "Test harness for an image tile: its picture state, title, meta, selection and action"
|
|
2012
|
+
},
|
|
1985
2013
|
{
|
|
1986
2014
|
"name": "BfImageViewerHarness",
|
|
1987
2015
|
"slug": "bf-image-viewer-harness",
|