@bitforgehq/ui 0.3.3 → 0.4.0
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 +91 -0
- package/README.md +4 -4
- package/ai/MANIFEST.index.json +36 -0
- package/ai/MANIFEST.json +414 -14
- package/ai/REFERENCE.index.json +35 -0
- package/ai/REFERENCE.json +184 -8
- package/ai/UI_GUIDE.md +25 -4
- package/ai/components/badge.md +39 -2
- package/ai/components/list-item.md +105 -9
- package/ai/components/step.md +112 -0
- package/ai/components/steps.md +124 -0
- package/ai/components/table.md +98 -2
- package/ai/components/timeline-event.md +144 -0
- package/ai/components/timeline.md +201 -0
- package/ai/components/toggle.md +11 -1
- package/ai/reference/bf-action-directive.md +3 -1
- package/ai/reference/bf-badge-emphasis.md +27 -0
- package/ai/reference/bf-labels.md +1 -0
- package/ai/reference/bf-step-heading-level.md +28 -0
- package/ai/reference/bf-table-align.md +1 -1
- package/ai/reference/bf-table-column-align.md +33 -0
- package/ai/reference/bf-table-column.md +7 -7
- package/ai/reference/bf-table-harness.md +12 -0
- package/ai/reference/bf-timeline-tone.md +31 -0
- package/ai/reference/bf-toggle-harness.md +55 -0
- package/ai/reference/provide-bf-labels.md +1 -1
- package/dark-ink.css +124 -0
- package/fesm2022/bitforgehq-ui-mobile.mjs +2 -1
- package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui-testing.mjs +51 -2
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -1
- package/fesm2022/bitforgehq-ui.mjs +682 -32
- package/fesm2022/bitforgehq-ui.mjs.map +1 -1
- package/llms-full.txt +1072 -15
- package/llms.txt +9 -0
- package/package.json +2 -1
- package/theme.css +24 -0
- package/types/bitforgehq-ui-testing.d.ts +76 -1
- package/types/bitforgehq-ui.d.ts +288 -8
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 [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\" />",
|
|
880
880
|
"primaryExample": "badge/overview",
|
|
881
881
|
"examples": [
|
|
882
882
|
{
|
|
@@ -893,6 +893,13 @@
|
|
|
893
893
|
"source": "import { Component } from '@angular/core';\nimport { BfBadgeComponent } from '@bitforgehq/ui';\n\n/** Status badges in the four status tones, and an informational info label. */\n@Component({\n selector: 'app-example-badge-status',\n imports: [BfBadgeComponent],\n template: `\n <div class=\"flex flex-wrap items-center gap-2\">\n <bf-badge variant=\"success\">On track</bf-badge>\n <bf-badge variant=\"warning\">At risk</bf-badge>\n <bf-badge variant=\"danger\">Blocked</bf-badge>\n <bf-badge>Draft</bf-badge>\n <bf-badge variant=\"info\">Design</bf-badge>\n </div>\n `,\n})\nexport class BadgeStatusExampleComponent {}\n",
|
|
894
894
|
"preview": "inline"
|
|
895
895
|
},
|
|
896
|
+
{
|
|
897
|
+
"id": "badge/strong",
|
|
898
|
+
"title": "A strong badge marks the one critical finding among soft status badges",
|
|
899
|
+
"file": "projects/docs/src/app/examples/badge/strong-example.component.ts",
|
|
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
|
+
"preview": "inline"
|
|
902
|
+
},
|
|
896
903
|
{
|
|
897
904
|
"id": "badge/count",
|
|
898
905
|
"title": "A count pill that caps at 99+",
|
|
@@ -913,6 +920,10 @@
|
|
|
913
920
|
"heading": "Status labels",
|
|
914
921
|
"body": "The variant carries the meaning: `success`, `warning`, `danger`, `primary`, `info` or the default `neutral`. `info` is for an informational label that is not a status, such as a category (\"Design\", \"Billing\"); it is a calm blue, distinct from the brand `primary`, so a category never reads as the selected or main thing. `variant` takes a `BfBadgeAppearance` (the five `BfBadgeVariant` tones plus `info`).\n\n<!-- example: badge/status -->"
|
|
915
922
|
},
|
|
923
|
+
{
|
|
924
|
+
"heading": "Emphasis",
|
|
925
|
+
"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
|
+
},
|
|
916
927
|
{
|
|
917
928
|
"heading": "Count pill",
|
|
918
929
|
"body": "Setting `count` switches to the compact primary pill. Values above 99 render as \"99+\"; zero renders as \"0\".\n\n<!-- example: badge/count -->"
|
|
@@ -928,10 +939,19 @@
|
|
|
928
939
|
"kind": "input",
|
|
929
940
|
"type": "BfBadgeAppearance",
|
|
930
941
|
"required": false,
|
|
931
|
-
"doc": "Tint for the label style: neutral, primary, success, warning, danger or info. Ignored
|
|
942
|
+
"doc": "Tint for the label style: neutral, primary, success, warning, danger or info. Ignored by a soft count pill; a strong one takes its fill from it.",
|
|
932
943
|
"default": "'neutral'",
|
|
933
944
|
"expandedType": "'neutral' | 'primary' | 'success' | 'warning' | 'danger' | 'info'"
|
|
934
945
|
},
|
|
946
|
+
{
|
|
947
|
+
"name": "emphasis",
|
|
948
|
+
"kind": "input",
|
|
949
|
+
"type": "BfBadgeEmphasis",
|
|
950
|
+
"required": false,
|
|
951
|
+
"doc": "`soft` (a pale tint, the default) or `strong` (a solid fill with contrasting text, also for the count pill).",
|
|
952
|
+
"default": "'soft'",
|
|
953
|
+
"expandedType": "'soft' | 'strong'"
|
|
954
|
+
},
|
|
935
955
|
{
|
|
936
956
|
"name": "count",
|
|
937
957
|
"kind": "input",
|
|
@@ -959,6 +979,10 @@
|
|
|
959
979
|
],
|
|
960
980
|
"methods": [],
|
|
961
981
|
"changelog": [
|
|
982
|
+
{
|
|
983
|
+
"version": "0.4.0",
|
|
984
|
+
"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."
|
|
985
|
+
},
|
|
962
986
|
{
|
|
963
987
|
"version": "0.3.1",
|
|
964
988
|
"text": "New `info` tone for informational labels such as a category, on the new `info` / `info-soft`\ntokens (7.7:1 in light, 7.2:1 in dark; not derived from the primary). `variant` is now typed\n`BfBadgeAppearance` (the five `BfBadgeVariant` tones plus `info`); `BfBadgeVariant` keeps its five\nmembers, so code that maps over it keeps compiling."
|
|
@@ -970,9 +994,9 @@
|
|
|
970
994
|
],
|
|
971
995
|
"since": "0.1.0",
|
|
972
996
|
"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.",
|
|
973
|
-
"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- Shape (label and count pill): `rounded-control` (`--radius-control`, `9999px` by default).",
|
|
974
|
-
"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\").",
|
|
975
|
-
"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\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."
|
|
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.",
|
|
999
|
+
"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."
|
|
976
1000
|
},
|
|
977
1001
|
{
|
|
978
1002
|
"name": "bar-chart",
|
|
@@ -5817,7 +5841,7 @@
|
|
|
5817
5841
|
"file": "src/components/list/list-item.component.ts",
|
|
5818
5842
|
"category": "Data display",
|
|
5819
5843
|
"summary": "List row with leading slot, title, subtitle, trailing slot and an optional chevron",
|
|
5820
|
-
"description": "One row of a list: leading slot (avatar, icon tile), title and subtitle, trailing slot\n(time, toggle, badge) and an optional chevron for rows that navigate. A navigation row puts a\n`bfAction` link in the `title` slot, so the row stays a list item and the link covers it.",
|
|
5844
|
+
"description": "One row of a list: leading slot (avatar, icon tile), title and subtitle, trailing slot\n(time, toggle, badge) and an optional chevron for rows that navigate. A navigation row puts a\n`bfAction` link in the `title` slot, so the row stays a list item and the link covers it.\n`unread` marks a row not yet opened with a dot and a bolder title.",
|
|
5821
5845
|
"overview": "A row of a list: a leading slot (an avatar, an icon), the title and subtitle, a trailing slot (a time, a toggle, a chip) and an optional chevron. Place rows in a [`bf-list`](https://bitforge-ui.web.app/components/list), which gives them the list semantics and the inset container; a row can also stand alone. A row is static text, a navigation link or an action button, never more than one of them.",
|
|
5822
5846
|
"example": "<bf-list-item subtitle=\"lena@studio.ch\" chevron>\n <bf-avatar bfSlot=\"leading\" initials=\"LM\" />\n <a bfSlot=\"title\" bfAction routerLink=\"/account\">Account</a>\n</bf-list-item>",
|
|
5823
5847
|
"primaryExample": "list-item/overview",
|
|
@@ -5856,6 +5880,13 @@
|
|
|
5856
5880
|
"file": "projects/docs/src/app/examples/list-item/actions-example.component.ts",
|
|
5857
5881
|
"source": "import { Component, signal } from '@angular/core';\nimport { BfListComponent, BfListItemComponent, BfToggleComponent } from '@bitforgehq/ui';\n\n/**\n * An action row and a settings row.\n *\n * \"Sync now\" is an `interactive` row (a native button); \"Weekly summary\" holds a trailing toggle.\n */\n@Component({\n selector: 'app-example-list-item-actions',\n imports: [BfListComponent, BfListItemComponent, BfToggleComponent],\n template: `\n <bf-list>\n <bf-list-item\n title=\"Sync now\"\n [subtitle]=\"synced() ? 'Synced just now' : 'Last synced 5 min ago'\"\n [interactive]=\"true\"\n (clicked)=\"synced.set(true)\"\n />\n <bf-list-item title=\"Weekly summary\" subtitle=\"Every Monday at 8:00\">\n <bf-toggle bfSlot=\"trailing\" ariaLabel=\"Weekly summary\" [(checked)]=\"weekly\" />\n </bf-list-item>\n </bf-list>\n `,\n})\nexport class ListItemActionsExampleComponent {\n protected readonly synced = signal(false);\n protected readonly weekly = signal(true);\n}\n",
|
|
5858
5882
|
"preview": "inline"
|
|
5883
|
+
},
|
|
5884
|
+
{
|
|
5885
|
+
"id": "list-item/unread",
|
|
5886
|
+
"title": "An inbox with unread rows",
|
|
5887
|
+
"file": "projects/docs/src/app/examples/list-item/unread-example.component.ts",
|
|
5888
|
+
"source": "import { Component, computed, signal } from '@angular/core';\nimport { BfActionDirective, BfListComponent, BfListItemComponent } from '@bitforgehq/ui';\n\ninterface Notice {\n readonly id: string;\n readonly title: string;\n readonly subtitle: string;\n readonly time: string;\n readonly unread: boolean;\n}\n\n/**\n * An inbox with unread rows.\n *\n * Each title is a stretched `<button bfAction>`; opening a row marks it read.\n */\n@Component({\n selector: 'app-example-list-item-unread',\n imports: [BfActionDirective, BfListComponent, BfListItemComponent],\n template: `\n <bf-list>\n @for (notice of notices(); track notice.id) {\n <bf-list-item [unread]=\"notice.unread\" [subtitle]=\"notice.subtitle\">\n <button bfSlot=\"title\" bfAction type=\"button\" class=\"text-start\" (click)=\"open(notice)\">\n {{ notice.title }}\n </button>\n <span bfSlot=\"trailing\" class=\"text-caption text-text-muted\">{{ notice.time }}</span>\n </bf-list-item>\n }\n </bf-list>\n <p data-status aria-live=\"polite\" class=\"text-caption text-text-muted mt-3\">{{ status() }}</p>\n `,\n})\nexport class ListItemUnreadExampleComponent {\n protected readonly notices = signal<readonly Notice[]>([\n {\n id: 'scan',\n title: 'Scan finished',\n subtitle: '3 new findings on api.example.com',\n time: '9:41',\n unread: true,\n },\n {\n id: 'cert',\n title: 'Certificate expires soon',\n subtitle: 'shop.example.com, in 12 days',\n time: '8:15',\n unread: true,\n },\n {\n id: 'report',\n title: 'Weekly report',\n subtitle: 'Ready to download',\n time: 'Mon',\n unread: false,\n },\n ]);\n private readonly opened = signal('');\n\n protected readonly status = computed(() => {\n const unread = this.notices().filter((notice) => notice.unread).length;\n const opened = this.opened();\n return opened ? `${unread} unread · Opened: ${opened}` : `${unread} unread`;\n });\n\n protected open(notice: Notice): void {\n this.opened.set(notice.title);\n this.notices.update((list) =>\n list.map((item) => (item.id === notice.id ? { ...item, unread: false } : item)),\n );\n }\n}\n",
|
|
5889
|
+
"preview": "inline"
|
|
5859
5890
|
}
|
|
5860
5891
|
],
|
|
5861
5892
|
"usage": [
|
|
@@ -5874,6 +5905,10 @@
|
|
|
5874
5905
|
{
|
|
5875
5906
|
"heading": "Action and settings rows",
|
|
5876
5907
|
"body": "A row that performs an action on the page (opens a sheet, starts a sync) is a button: `[interactive]=\"true\"` renders the whole row as a native `<button>` that emits `clicked`, and `disabled` disables it. A trailing `bf-toggle` makes a settings row. Text starts at the inline start and the chevron sits at, and points to, the inline end, so both mirror under `dir=\"rtl\"`.\n\n<!-- example: list-item/actions -->"
|
|
5908
|
+
},
|
|
5909
|
+
{
|
|
5910
|
+
"heading": "Unread rows",
|
|
5911
|
+
"body": "`unread` marks a row the user has not opened yet, such as a new message or notification: a small `primary` dot at the row's inline start, on the title line, and the title in a bolder weight. It is not colour alone: the row also reads a visually hidden state before the title, from the `listItemUnread` label (\"Unread\"; set it with `provideBfLabels({ listItemUnread: 'Ungelesen' })` in a German app), so a screen reader says \"Unread, Scan finished\". The dot sits in the row's own inline-start padding (or the gap after a leading avatar or icon), so read and unread rows keep their text aligned and nothing moves when a row is marked read; without `unread` nothing changes. Clear `unread` when the row is opened.\n\n<!-- example: list-item/unread -->\n\nWith a `bfAction` link or button in the `title` slot, the state becomes part of that element's accessible name: the directive sets `aria-labelledby` to the state text and the element itself (it gives the element an id if it has none, and keeps an `aria-labelledby` of its own after the state), so the link reads \"Unread, Scan finished\" when tabbed to or listed, and the state text is hidden from the tree so it is read once. An `interactive` row holds the state text inside its button, so its name starts with it too."
|
|
5877
5912
|
}
|
|
5878
5913
|
],
|
|
5879
5914
|
"inputs": [
|
|
@@ -5924,6 +5959,14 @@
|
|
|
5924
5959
|
"required": false,
|
|
5925
5960
|
"doc": "Keep the title, the title slot and the subtitle on one line each, cut with an ellipsis. By default they wrap, so nothing is lost at large text sizes.",
|
|
5926
5961
|
"default": "false"
|
|
5962
|
+
},
|
|
5963
|
+
{
|
|
5964
|
+
"name": "unread",
|
|
5965
|
+
"kind": "input",
|
|
5966
|
+
"type": "boolean",
|
|
5967
|
+
"required": false,
|
|
5968
|
+
"doc": "Mark the row as not yet opened: a dot at the inline start, a bolder title and a visually hidden state (the `listItemUnread` label) read before the title.",
|
|
5969
|
+
"default": "false"
|
|
5927
5970
|
}
|
|
5928
5971
|
],
|
|
5929
5972
|
"outputs": [
|
|
@@ -5956,6 +5999,10 @@
|
|
|
5956
5999
|
],
|
|
5957
6000
|
"methods": [],
|
|
5958
6001
|
"changelog": [
|
|
6002
|
+
{
|
|
6003
|
+
"version": "0.4.0",
|
|
6004
|
+
"text": "New `unread` input (boolean, default false) for a row not yet opened: a `primary` dot at the\nrow's inline start on the title line, a bolder title (`font-extrabold`) and a visually hidden\nstate read before the title, from the new label key `listItemUnread` (\"Unread\"). A row's\n`bfAction` link or button then names itself `aria-labelledby=\"<state> <itself>\"` (\"Unread,\nReport\"; an id is added if it has none, and its own `aria-labelledby` is kept after the state).\nThe dot sits in the row's own inline-start padding, so read and unread rows stay aligned. Opt-in: without `unread` nothing\nchanges."
|
|
6005
|
+
},
|
|
5959
6006
|
{
|
|
5960
6007
|
"version": "0.3.0",
|
|
5961
6008
|
"text": "**Behaviour:** logical properties: text aligns to the inline start and the chevron sits at the\ninline end, pointing there (mirrored under `dir=\"rtl\"`).\n\nNew navigation rows: a `title` content slot (`bfSlot=\"title\"`), rendered in the title position\nand type, takes an `<a bfAction routerLink=\"…\">` (the new `BfActionDirective`), whose `::after`\nstretches over the row. The row stays a `listitem`, the link's name is the title, and the row\nshows the pointer, the `surface-muted` hover and the focus ring (on keyboard focus) while it holds\nits own `bfAction`; the row box is `relative`. Text under the stretched link is not selectable.\n\n**Behaviour:** the trailing slot is wrapped in a `relative z-1` flex box (`gap-3`, as between the\nrow's items, hidden when empty), so a toggle or button there stays clickable above a stretched\nlink. `interactive` stays for rows that act on the page.\n\n**Docs:** `bf-list-item` has a page of its own (`list/list-item.docs.md`): the row's slots, the\nnavigation rows and the action and settings rows (moved from the `bf-list` page), with the row's own\ntheming and accessibility. The `bf-list` page keeps the container: a list of rows and the plain\nlist, and links to the row page.\n\nJSDoc of `list-item.component.ts` for the API reference: `@slot` docs for `leading`, `title`,\n`default`, `trailing`. No behaviour change.\n\n**Behaviour:** a pressed state (F-045): an `interactive` row takes `surface-pressed` while held down, and a row holding a `bfAction` while its action is (`has-[[data-bf-action]:active]`). None while disabled; under forced colours the system colours win.\n\nWhile a row is pressed its chevron turns `text-muted` (`text-subtle` misses 3:1 on\n`surface-pressed`).\n\n**Behaviour:** the title, the title slot and the subtitle wrap by default (`wrap-anywhere`, no\nclamp) instead of truncating, so no text is lost at large text sizes (F-051). New\n`truncate = input(false, { transform: booleanAttribute })` restores one line with an ellipsis for\neach. See Migration 32. The mobile demo's Inbox rows use `truncate` (a mail list); its Today,\nproject and Me rows wrap."
|
|
@@ -5964,9 +6011,9 @@
|
|
|
5964
6011
|
"since": "0.1.0",
|
|
5965
6012
|
"whenToUse": "Use `bf-list-item` for one row of a list: static text, a navigation link (`bfAction` in the title) or an action (`interactive`). Use [bf-card](https://bitforge-ui.web.app/components/card) instead for a block with its own heading and several parts, and [bf-menu](https://bitforge-ui.web.app/components/menu) for a short list of actions that opens from a button.",
|
|
5966
6013
|
"desktop": "An `interactive` row and a row holding a `bfAction` link take a `surface-muted` fill and the pointer cursor while hovered; a static row has none. Hover styles apply only on devices that can hover, so a tap never leaves one stuck. The ring is drawn around the row on keyboard focus only. A navigation row keeps the browser's link behaviour: Ctrl/Cmd-click and a middle click open it in a new tab. The trailing slot sits above the stretched link, so a toggle or button there takes its own click. Rows have one height; they do not follow `provideBfDefaults()`.",
|
|
5967
|
-
"theming": "- Row: `surface` / `text`; the subtitle `text-muted`.\n- Text: the title, the title slot and the subtitle wrap (`wrap-anywhere`); with `truncate` each keeps one line (`truncate`).\n- `interactive` rows and rows holding a `bfAction` link add a pointer, a `surface-muted` hover, a `surface-pressed` fill while pressed (the row's action, for a `bfAction` row) and a `ring` focus ring; `interactive` rows dim to `text-disabled` when `disabled` and have no pressed state then.\n- The `chevron` mark uses `text-subtle`.",
|
|
5968
|
-
"accessibility": "`bf-list-item` sets `role=\"listitem\"` on its host only inside a `bf-list`; a standalone row has no role, so it never claims a list parent that is not there. A non-interactive row is a plain `<div>`, while `[interactive]=\"true\"` renders it as a native `<button>` so it gets keyboard activation and the button role for free. A navigation row's `bfAction` link in the `title` slot is a real `<a>` inside the list item ([WAI-ARIA APG link pattern](https://www.w3.org/WAI/ARIA/apg/patterns/link/)): exposed as `link` with the title as its name, reached with Tab (in Safari and iOS WKWebView, Tab reaches links only when the user turns on \"Press Tab to highlight each item\" (Option+Tab otherwise); VoiceOver and Full Keyboard Access are unaffected), followed with Enter (not Space), opened in a new tab with Ctrl/Cmd-click; under forced colours a system-coloured outline around the whole row marks the focused link. Making the host itself a link would take the row out of the list's `listitem` structure, so the link lives inside it. `chevron` is a purely visual CSS mark (an `::after` pseudo-element), so it adds nothing to the accessibility tree
|
|
5969
|
-
"notes": "## Do\n\n- Keep titles short; a long one wraps. Add `truncate` only where one line per row is the pattern (a mail list).\n- Make a row that navigates a `bfAction` link in the `title` slot, and a row that acts on the page `interactive`; put secondary actions in the trailing slot.\n\n## Don't\n\n- Don't nest interactive controls inside an `interactive` row (it is one button); use a navigation row with a trailing control instead.\n- Don't wrap a `bf-list-item` in an `<a>`, and don't navigate from an `interactive` row's `(clicked)
|
|
6014
|
+
"theming": "- Row: `surface` / `text`; the subtitle `text-muted`.\n- Text: the title, the title slot and the subtitle wrap (`wrap-anywhere`); with `truncate` each keeps one line (`truncate`).\n- `interactive` rows and rows holding a `bfAction` link add a pointer, a `surface-muted` hover, a `surface-pressed` fill while pressed (the row's action, for a `bfAction` row) and a `ring` focus ring; `interactive` rows dim to `text-disabled` when `disabled` and have no pressed state then.\n- The `chevron` mark uses `text-subtle`.\n- `unread`: the dot is `primary` (`CanvasText` in forced colours), `rounded-full` and 8 px (`size-2`); the title turns `font-extrabold`.",
|
|
6015
|
+
"accessibility": "`bf-list-item` sets `role=\"listitem\"` on its host only inside a `bf-list`; a standalone row has no role, so it never claims a list parent that is not there. A non-interactive row is a plain `<div>`, while `[interactive]=\"true\"` renders it as a native `<button>` so it gets keyboard activation and the button role for free. A navigation row's `bfAction` link in the `title` slot is a real `<a>` inside the list item ([WAI-ARIA APG link pattern](https://www.w3.org/WAI/ARIA/apg/patterns/link/)): exposed as `link` with the title as its name, reached with Tab (in Safari and iOS WKWebView, Tab reaches links only when the user turns on \"Press Tab to highlight each item\" (Option+Tab otherwise); VoiceOver and Full Keyboard Access are unaffected), followed with Enter (not Space), opened in a new tab with Ctrl/Cmd-click; under forced colours a system-coloured outline around the whole row marks the focused link. Making the host itself a link would take the row out of the list's `listitem` structure, so the link lives inside it. `chevron` is a purely visual CSS mark (an `::after` pseudo-element), so it adds nothing to the accessibility tree. An `unread` row's dot carries a visually hidden (`sr-only`) state text from the `listItemUnread` label, read before the title (\"Unread, Scan finished\"); on a row with a `bfAction` the text is `aria-hidden` and the action names itself with `aria-labelledby` from it and its own content, so the state is part of the link's or button's name and is read once. The row stays a `listitem`.\n\n### Keyboard\n\n| Key | Action |\n| ---------------- | ------------------------------------------------------------------------------------------------------ |\n| <kbd>Tab</kbd> | Moves to an `interactive` row, or to a navigation row's `bfAction` link, then to its trailing controls |\n| <kbd>Enter</kbd> | Follows a navigation row's link, or activates an `interactive` row (emits `clicked`) |\n| <kbd>Space</kbd> | Activates an `interactive` row; a link ignores it |",
|
|
6016
|
+
"notes": "## Do\n\n- Keep titles short; a long one wraps. Add `truncate` only where one line per row is the pattern (a mail list).\n- Make a row that navigates a `bfAction` link in the `title` slot, and a row that acts on the page `interactive`; put secondary actions in the trailing slot.\n- Set `unread` on rows not yet opened (messages, notifications) and clear it when the row is opened.\n\n## Don't\n\n- Don't nest interactive controls inside an `interactive` row (it is one button); use a navigation row with a trailing control instead.\n- Don't wrap a `bf-list-item` in an `<a>`, and don't navigate from an `interactive` row's `(clicked)`.\n- Don't mark unread rows with a coloured title or a badge of your own; `unread` gives the dot, the weight and the screen-reader state together."
|
|
5970
6017
|
},
|
|
5971
6018
|
{
|
|
5972
6019
|
"name": "menu",
|
|
@@ -10061,6 +10108,149 @@
|
|
|
10061
10108
|
"accessibility": "`bf-stat` renders a native `dl` holding the label as the `dt`, the value as a `dd` and the supporting line as a second `dd`, so a screen reader announces the label with its value and context. The icon tile sits outside the `dl`; keep the icon decorative (a `bf-icon` without a `label` is hidden from assistive technology). The delta pill sits in the value's `dd`, so it is read with the value; its arrow mark is `aria-hidden`, and a visually hidden word (the `statTrendUp`, `statTrendDown` and `statTrendFlat` labels, \"Up\", \"Down\", \"No change\"; translate them with `provideBfLabels()`) names the direction before the delta. The pill uses the badge's tone pairs, so its text keeps the badge's contrast in both modes; in forced colours the mark still shows, as it is drawn with borders. The stat adds no other ARIA.",
|
|
10062
10109
|
"notes": "## Do\n\n- Format the value before passing it (`45 min`, `1,204`, `98%`); the stat prints it as given.\n- Keep the supporting line to a few words.\n- Set `trendIntent=\"negative\"` for figures where a fall is good news (costs, churn, errors).\n\n## Don't\n\n- Don't put `card` stats (outlined) inside a raised `bf-card`; use `tile`.\n- Don't use a stat for a value the user edits or acts on; give it its own control.\n- Don't put long text in `value`; it is set at the `title` size for a short figure.\n- Don't put the comparison period in `delta`; keep it to the change (`+12%`) and say \"vs. last month\" in the supporting line."
|
|
10063
10110
|
},
|
|
10111
|
+
{
|
|
10112
|
+
"name": "step",
|
|
10113
|
+
"selector": "bf-step",
|
|
10114
|
+
"selectors": ["bf-step"],
|
|
10115
|
+
"className": "BfStepComponent",
|
|
10116
|
+
"entryPoint": "@bitforgehq/ui",
|
|
10117
|
+
"file": "src/components/steps/step.component.ts",
|
|
10118
|
+
"category": "Data display",
|
|
10119
|
+
"summary": "One numbered step of an instruction list, with a title and its details",
|
|
10120
|
+
"description": "One step of a `bf-steps` list: a numbered marker, a short instruction as its title and the\nstep's details in the content.",
|
|
10121
|
+
"overview": "One step of a [`bf-steps`](https://bitforge-ui.web.app/components/steps) list: `title` is the step's short instruction and the content holds its details (text, a code to type, an image or a button). The list numbers the steps by position; the step draws the numbered circle and the line to the next one. It works the same in both shells.",
|
|
10122
|
+
"example": "<bf-steps>\n <bf-step title=\"Install the app\">Download it from the App Store or Google Play.</bf-step>\n <bf-step title=\"Sign in\">Use your work email address.</bf-step>\n</bf-steps>",
|
|
10123
|
+
"primaryExample": "step/overview",
|
|
10124
|
+
"examples": [
|
|
10125
|
+
{
|
|
10126
|
+
"id": "step/overview",
|
|
10127
|
+
"title": "Steps with a short instruction as the title and the details in the content",
|
|
10128
|
+
"file": "projects/docs/src/app/examples/step/overview-example.component.ts",
|
|
10129
|
+
"source": "import { Component } from '@angular/core';\nimport { BfStepComponent, BfStepsComponent } from '@bitforgehq/ui';\n\n/** Steps with a short instruction as the title and the details in the content. */\n@Component({\n selector: 'app-example-step-overview',\n imports: [BfStepComponent, BfStepsComponent],\n template: `\n <bf-steps>\n <bf-step title=\"Export your contacts\"\n >In your old app, choose Export and save the file.</bf-step\n >\n <bf-step title=\"Import the file\">\n Pick the file you saved. Contacts that are already in your address book are merged, not\n added twice.\n </bf-step>\n <bf-step title=\"Check the result\" />\n </bf-steps>\n `,\n})\nexport class StepOverviewExampleComponent {}\n",
|
|
10130
|
+
"preview": "inline"
|
|
10131
|
+
},
|
|
10132
|
+
{
|
|
10133
|
+
"id": "step/headings",
|
|
10134
|
+
"title": "Long steps with level 3 headings, so a screen-reader user can move between them",
|
|
10135
|
+
"file": "projects/docs/src/app/examples/step/headings-example.component.ts",
|
|
10136
|
+
"source": "import { Component } from '@angular/core';\nimport { BfStepComponent, BfStepsComponent } from '@bitforgehq/ui';\n\n/** Long steps with level 3 headings, so a screen-reader user can move between them. */\n@Component({\n selector: 'app-example-step-headings',\n imports: [BfStepComponent, BfStepsComponent],\n template: `\n <bf-steps>\n <bf-step title=\"Create an API key\" [headingLevel]=\"3\">\n In the console, open Settings, then API keys, and choose New key. Give it a name that says\n where it is used, such as \"CI pipeline\", and copy it: it is shown only once.\n </bf-step>\n <bf-step title=\"Store the key as a secret\" [headingLevel]=\"3\">\n Add it to your CI provider's secrets as BITFORGE_API_KEY. Never commit it to the repository.\n </bf-step>\n </bf-steps>\n `,\n})\nexport class StepHeadingsExampleComponent {}\n",
|
|
10137
|
+
"preview": "inline"
|
|
10138
|
+
}
|
|
10139
|
+
],
|
|
10140
|
+
"usage": [
|
|
10141
|
+
{
|
|
10142
|
+
"heading": "Headings",
|
|
10143
|
+
"body": "The title is text by default, which suits short steps. For a long guide whose steps hold screenshots or code, set `headingLevel` (`2`, `3` or `4`, a `BfStepHeadingLevel`) to the level that fits the page outline, so a screen-reader user can move from step to step by heading. The look does not change.\n\n<!-- example: step/headings -->"
|
|
10144
|
+
}
|
|
10145
|
+
],
|
|
10146
|
+
"inputs": [
|
|
10147
|
+
{
|
|
10148
|
+
"name": "title",
|
|
10149
|
+
"kind": "input",
|
|
10150
|
+
"type": "string",
|
|
10151
|
+
"required": false,
|
|
10152
|
+
"doc": "The step's short instruction (\"Install the app\"), shown next to the number.",
|
|
10153
|
+
"default": "''"
|
|
10154
|
+
},
|
|
10155
|
+
{
|
|
10156
|
+
"name": "headingLevel",
|
|
10157
|
+
"kind": "input",
|
|
10158
|
+
"type": "BfStepHeadingLevel | undefined",
|
|
10159
|
+
"required": false,
|
|
10160
|
+
"doc": "Renders the title as a heading of this level instead of text; leave it off for short steps.",
|
|
10161
|
+
"default": "undefined",
|
|
10162
|
+
"expandedType": "2 | 3 | 4 | undefined"
|
|
10163
|
+
}
|
|
10164
|
+
],
|
|
10165
|
+
"outputs": [],
|
|
10166
|
+
"models": [],
|
|
10167
|
+
"slots": [
|
|
10168
|
+
{
|
|
10169
|
+
"name": "default",
|
|
10170
|
+
"doc": "The step's details: text, a code snippet, an image or a button."
|
|
10171
|
+
}
|
|
10172
|
+
],
|
|
10173
|
+
"methods": [],
|
|
10174
|
+
"changelog": [
|
|
10175
|
+
{
|
|
10176
|
+
"version": "0.4.0",
|
|
10177
|
+
"text": "New `bf-step`: one step of a `bf-steps` list, with `title` (the short instruction, text by\ndefault; `headingLevel` 2, 3 or 4, a `BfStepHeadingLevel`, makes it a heading) and the details\nin the default slot. Its number is drawn in a `primary-soft` circle and hidden from assistive\ntechnology, which reads the list position instead."
|
|
10178
|
+
}
|
|
10179
|
+
],
|
|
10180
|
+
"since": "0.4.0",
|
|
10181
|
+
"whenToUse": "Use `bf-step` for each instruction inside a `bf-steps` list. Use [bf-list-item](https://bitforge-ui.web.app/components/list-item) instead for a row the user opens or acts on, and [bf-description-list](https://bitforge-ui.web.app/components/description-list) for facts with no order.",
|
|
10182
|
+
"desktop": "A step is not interactive: no hover, no pointer cursor, no focus stop; only a link or button in its content takes focus. It renders the same inside `provideBfShell()`.",
|
|
10183
|
+
"theming": "- Marker: `bg-primary-soft` / `text-primary-strong`, `text-label-sm` bold, a 24 px `rounded-full` circle; the line under it is `border-border`, hidden on the last step.\n- Title: `text-label` bold in `text`, wrapping anywhere; content: `text-body` in `text-secondary`.",
|
|
10184
|
+
"accessibility": "Inside `bf-steps` the host is a `role=\"listitem\"`, so its position is announced (\"2 of 3\"); the visible number is `aria-hidden`. The title is a `p`, or an `h2`, `h3` or `h4` with `headingLevel`. Outside `bf-steps` the host claims no role.",
|
|
10185
|
+
"notes": "## Do\n\n- Keep the title to one instruction and put everything else in the content.\n\n## Don't\n\n- Don't use `bf-step` outside a `bf-steps` list: it then has no number to show but 1."
|
|
10186
|
+
},
|
|
10187
|
+
{
|
|
10188
|
+
"name": "steps",
|
|
10189
|
+
"selector": "bf-steps",
|
|
10190
|
+
"selectors": ["bf-steps"],
|
|
10191
|
+
"className": "BfStepsComponent",
|
|
10192
|
+
"entryPoint": "@bitforgehq/ui",
|
|
10193
|
+
"file": "src/components/steps/steps.component.ts",
|
|
10194
|
+
"category": "Data display",
|
|
10195
|
+
"summary": "Numbered instruction steps a user follows in order, with joined markers",
|
|
10196
|
+
"description": "Numbered instructions a user follows in order, such as setting up an app on a phone: an\nordered list of `bf-step` items, each with a numbered marker joined to the next by a line.",
|
|
10197
|
+
"overview": "Numbered instructions a user follows in order: a `bf-steps` list of `bf-step` items, each with a numbered circle, a short instruction as its title and the details under it, the circles joined by a thin line. It works the same in a desktop web app and in the Capacitor shell.",
|
|
10198
|
+
"example": "<bf-steps>\n <bf-step title=\"Install the app\">Download it from the App Store or Google Play.</bf-step>\n <bf-step title=\"Sign in\">Use your work email address.</bf-step>\n <bf-step title=\"Turn on notifications\">Allow them when the app asks.</bf-step>\n</bf-steps>",
|
|
10199
|
+
"primaryExample": "steps/overview",
|
|
10200
|
+
"examples": [
|
|
10201
|
+
{
|
|
10202
|
+
"id": "steps/overview",
|
|
10203
|
+
"title": "Setting up the app on a phone, in four numbered steps with a code to type and a link",
|
|
10204
|
+
"file": "projects/docs/src/app/examples/steps/overview-example.component.ts",
|
|
10205
|
+
"source": "import { Component } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { BfStepComponent, BfStepsComponent } from '@bitforgehq/ui';\n\n/** Setting up the app on a phone, in four numbered steps with a code to type and a link. */\n@Component({\n selector: 'app-example-steps-overview',\n imports: [RouterLink, BfStepComponent, BfStepsComponent],\n template: `\n <bf-steps>\n <bf-step title=\"Install the app\">\n Download Bitforge Guard from the App Store or Google Play.\n </bf-step>\n <bf-step title=\"Sign in\">Use your work email address and the password you chose.</bf-step>\n <bf-step title=\"Pair your phone\">\n Open Settings, then Devices, and enter this code:\n <code\n class=\"rounded-tile bg-surface-sunken text-label-sm text-text mt-2 block w-fit px-3 py-1.5 font-mono\"\n >K7F-29Q</code\n >\n </bf-step>\n <bf-step title=\"Turn on notifications\">\n Allow them when the app asks, or later in\n <a routerLink=\"/components/steps\" class=\"text-primary-text underline\"\n >notification settings</a\n >.\n </bf-step>\n </bf-steps>\n `,\n})\nexport class StepsOverviewExampleComponent {}\n",
|
|
10206
|
+
"preview": "inline"
|
|
10207
|
+
},
|
|
10208
|
+
{
|
|
10209
|
+
"id": "steps/continued",
|
|
10210
|
+
"title": "A list continued after a paragraph: the second list starts at 3",
|
|
10211
|
+
"file": "projects/docs/src/app/examples/steps/continued-example.component.ts",
|
|
10212
|
+
"source": "import { Component } from '@angular/core';\nimport { BfStepComponent, BfStepsComponent } from '@bitforgehq/ui';\n\n/** A list continued after a paragraph: the second list starts at 3. */\n@Component({\n selector: 'app-example-steps-continued',\n imports: [BfStepComponent, BfStepsComponent],\n template: `\n <bf-steps>\n <bf-step title=\"Open the admin console\">Sign in with an administrator account.</bf-step>\n <bf-step title=\"Add your domain\">Enter it without https:// in front.</bf-step>\n </bf-steps>\n <p class=\"text-body text-text-muted my-4\">The console now shows a TXT record for the domain.</p>\n <bf-steps start=\"3\">\n <bf-step title=\"Add the TXT record\">Paste it at your DNS provider.</bf-step>\n <bf-step title=\"Verify the domain\">Changes can take up to an hour to arrive.</bf-step>\n </bf-steps>\n `,\n})\nexport class StepsContinuedExampleComponent {}\n",
|
|
10213
|
+
"preview": "inline"
|
|
10214
|
+
}
|
|
10215
|
+
],
|
|
10216
|
+
"usage": [
|
|
10217
|
+
{
|
|
10218
|
+
"heading": "Continued lists",
|
|
10219
|
+
"body": "`start` sets the number of the first step, for a list continued after a paragraph or on another part of the page: `start=\"3\"` numbers the steps 3, 4, 5. It takes a number or a plain attribute.\n\n<!-- example: steps/continued -->"
|
|
10220
|
+
}
|
|
10221
|
+
],
|
|
10222
|
+
"inputs": [
|
|
10223
|
+
{
|
|
10224
|
+
"name": "start",
|
|
10225
|
+
"kind": "input",
|
|
10226
|
+
"type": "number",
|
|
10227
|
+
"required": false,
|
|
10228
|
+
"doc": "The number of the first step, for a list continued from an earlier one.",
|
|
10229
|
+
"default": "1"
|
|
10230
|
+
}
|
|
10231
|
+
],
|
|
10232
|
+
"outputs": [],
|
|
10233
|
+
"models": [],
|
|
10234
|
+
"slots": [
|
|
10235
|
+
{
|
|
10236
|
+
"name": "default",
|
|
10237
|
+
"doc": "The steps, each a `bf-step`."
|
|
10238
|
+
}
|
|
10239
|
+
],
|
|
10240
|
+
"methods": [],
|
|
10241
|
+
"changelog": [
|
|
10242
|
+
{
|
|
10243
|
+
"version": "0.4.0",
|
|
10244
|
+
"text": "New `bf-steps`: numbered instructions a user follows in order (\"Set up the app on your phone\"),\na `role=\"list\"` of `bf-step` items whose markers are joined by a line. `start` (a number, 1 by\ndefault) continues a list; a continued list sets `aria-posinset` and `aria-setsize` on its steps\nso the announced position matches the visible number."
|
|
10245
|
+
}
|
|
10246
|
+
],
|
|
10247
|
+
"since": "0.4.0",
|
|
10248
|
+
"whenToUse": "Use `bf-steps` for instructions the user carries out in order, outside the app or across it: \"Set up the app on your phone\", \"Connect your domain\". It is static content, not a wizard: a multi-page stepper that holds state and moves the user from step to step is a flow of pages (in the Capacitor shell, pages pushed in [bf-tabs](https://bitforge-ui.web.app/components/tabs) stacks), and a progress figure for such a flow is a [bf-progress](https://bitforge-ui.web.app/components/progress). Use [bf-list](https://bitforge-ui.web.app/components/list) instead for items that have no order.",
|
|
10249
|
+
"desktop": "Steps are not interactive: no hover, no pointer cursor and no focus stop of their own; only a link or button in a step's content takes focus, with its own focus ring. They render the same at every width: the marker column keeps its 24 px and the content wraps (a long word or URL breaks anywhere), so nothing scrolls sideways at 320 px. Put a code snippet that must not wrap in its own `overflow-x-auto` block.",
|
|
10250
|
+
"theming": "- Marker: `bg-primary-soft` / `text-primary-strong` (the tonal pair, ≥ 4.5:1 in both modes), `text-label-sm` bold in a 24 px circle (`size-6`, `rounded-full`); a `CanvasText` outline in forced colours.\n- Line: `border-border`, 1 px, hidden after the last step.\n- Title: `text-label` bold in `text`; content: `text-body` in `text-secondary`; `pb-6` between steps.",
|
|
10251
|
+
"accessibility": "`bf-steps` renders a `role=\"list\"` container and each `bf-step` host is a `role=\"listitem\"` directly inside it, so a screen reader announces the list and each step's position (\"1 of 3\"). The visible number is `aria-hidden`, so it is not read twice. A list with a `start` other than 1 also sets `aria-posinset` and `aria-setsize` on each step (the size counts the earlier steps too: steps 3 to 5 read \"3 of 5\"), so the announcement matches the number on screen. The title is text by default; give long steps `headingLevel` on `bf-step` so users can move between them by heading. A lone `bf-step` outside `bf-steps` claims no role.",
|
|
10252
|
+
"notes": "## Do\n\n- Start each title with a verb and keep it to one instruction (\"Install the app\").\n- Put the details, a code to type or a link to open, in the step's content.\n\n## Don't\n\n- Don't use steps for a multi-page flow that tracks where the user is; that is a set of pages.\n- Don't number steps yourself in the title; the marker shows the number."
|
|
10253
|
+
},
|
|
10064
10254
|
{
|
|
10065
10255
|
"name": "tab",
|
|
10066
10256
|
"selector": "bf-tab",
|
|
@@ -10679,6 +10869,13 @@
|
|
|
10679
10869
|
"source": "import { Component, signal } from '@angular/core';\nimport {\n BfAvatarComponent,\n BfBadgeComponent,\n BfButtonComponent,\n BfTableCellDirective,\n BfTableComponent,\n type BfBadgeAppearance,\n type BfTableColumn,\n} from '@bitforgehq/ui';\n\ninterface Task {\n id: string;\n title: string;\n project: string;\n owner: string;\n initials: string;\n status: 'Open' | 'Review' | 'Done';\n}\n\nconst TONE: Record<Task['status'], BfBadgeAppearance> = {\n Open: 'neutral',\n Review: 'warning',\n Done: 'success',\n};\n\n/**\n * Custom cells.\n *\n * Each `ng-template[bfTableCell]` replaces the text of one column: a title with its project, an\n * avatar, a status badge and a row action. `[bfTableCellOf]` types `let-task` as a `Task`.\n */\n@Component({\n selector: 'app-example-table-custom-cells',\n imports: [\n BfTableComponent,\n BfTableCellDirective,\n BfAvatarComponent,\n BfBadgeComponent,\n BfButtonComponent,\n ],\n template: `\n <div class=\"flex flex-col gap-3\">\n <bf-table label=\"Tasks\" [rows]=\"tasks\" [columns]=\"columns\">\n <ng-template bfTableCell=\"title\" [bfTableCellOf]=\"tasks\" let-task>\n <span class=\"flex flex-col\">\n <span class=\"font-bold\">{{ task.title }}</span>\n <span class=\"text-caption text-text-muted\">{{ task.project }}</span>\n </span>\n </ng-template>\n <ng-template bfTableCell=\"owner\" [bfTableCellOf]=\"tasks\" let-task>\n <span class=\"flex items-center gap-2\">\n <bf-avatar size=\"sm\" [initials]=\"task.initials\" />\n {{ task.owner }}\n </span>\n </ng-template>\n <ng-template bfTableCell=\"status\" [bfTableCellOf]=\"tasks\" let-task>\n <bf-badge [variant]=\"tone[task.status]\">{{ task.status }}</bf-badge>\n </ng-template>\n <ng-template bfTableCell=\"open\" [bfTableCellOf]=\"tasks\" let-task>\n <bf-button variant=\"text\" size=\"sm\" (clicked)=\"opened.set(task.title)\">Open</bf-button>\n </ng-template>\n </bf-table>\n <p data-status aria-live=\"polite\" class=\"text-caption text-text-muted\">\n {{ opened() ? 'Opened ' + opened() + '.' : 'Open a task.' }}\n </p>\n </div>\n `,\n})\nexport class TableCustomCellsExampleComponent {\n readonly tone = TONE;\n readonly columns: readonly BfTableColumn[] = [\n { key: 'title', label: 'Task' },\n { key: 'owner', label: 'Owner' },\n { key: 'status', label: 'Status' },\n { key: 'open', label: 'Actions', align: 'end' },\n ];\n readonly tasks: readonly Task[] = [\n {\n id: 'brief',\n title: 'Write the brief',\n project: 'Atlas',\n owner: 'Lena Meier',\n initials: 'LM',\n status: 'Review',\n },\n {\n id: 'deck',\n title: 'Pitch deck',\n project: 'Atlas',\n owner: 'Jonas Keller',\n initials: 'JK',\n status: 'Open',\n },\n {\n id: 'copy',\n title: 'Copy review',\n project: 'Cedar',\n owner: 'Lena Meier',\n initials: 'LM',\n status: 'Done',\n },\n ];\n readonly opened = signal('');\n}\n",
|
|
10680
10870
|
"preview": "inline"
|
|
10681
10871
|
},
|
|
10872
|
+
{
|
|
10873
|
+
"id": "table/toggle-matrix",
|
|
10874
|
+
"title": "A settings matrix of toggle cells",
|
|
10875
|
+
"file": "projects/docs/src/app/examples/table/toggle-matrix-example.component.ts",
|
|
10876
|
+
"source": "import { Component, signal } from '@angular/core';\nimport {\n BfTableCellDirective,\n BfTableComponent,\n BfToggleComponent,\n type BfTableColumn,\n} from '@bitforgehq/ui';\n\ntype Channel = 'email' | 'push' | 'slack';\n\ninterface AlertSetting {\n id: string;\n label: string;\n email: boolean;\n push: boolean;\n slack: boolean;\n}\n\nconst CHANNELS: readonly { key: Channel; label: string }[] = [\n { key: 'email', label: 'Email' },\n { key: 'push', label: 'Push' },\n { key: 'slack', label: 'Slack' },\n];\n\n/**\n * A settings matrix of toggle cells.\n *\n * Rows are alert kinds and columns are channels, each cell a `bf-toggle` named after its column\n * and row (\"Push, New CVE\") with `ariaLabel`, in centred columns (`align: 'center'`). The row label\n * stays the first column, and `stack=\"narrow\"` turns each row into a tile with the toggles at its\n * end in a narrow panel or on a phone.\n */\n@Component({\n selector: 'app-example-table-toggle-matrix',\n imports: [BfTableComponent, BfTableCellDirective, BfToggleComponent],\n template: `\n <div class=\"flex w-152 max-w-full flex-col gap-3\">\n <bf-table label=\"Alert channels\" stack=\"narrow\" [rows]=\"alerts()\" [columns]=\"columns\">\n @for (channel of channels; track channel.key) {\n <ng-template [bfTableCell]=\"channel.key\" [bfTableCellOf]=\"alerts()\" let-alert>\n <bf-toggle\n [ariaLabel]=\"channel.label + ', ' + alert.label\"\n [checked]=\"alert[channel.key]\"\n (changed)=\"set(alert, channel.key, $event)\"\n />\n </ng-template>\n }\n </bf-table>\n <p data-status aria-live=\"polite\" class=\"text-caption text-text-muted\">{{ status() }}</p>\n </div>\n `,\n})\nexport class TableToggleMatrixExampleComponent {\n readonly channels = CHANNELS;\n readonly columns: readonly BfTableColumn[] = [\n { key: 'label', label: 'Alert' },\n ...CHANNELS.map((channel): BfTableColumn => ({ ...channel, align: 'center' })),\n ];\n readonly alerts = signal<readonly AlertSetting[]>([\n { id: 'critical', label: 'Critical finding', email: true, push: true, slack: true },\n { id: 'cve', label: 'New CVE', email: true, push: false, slack: true },\n { id: 'ci', label: 'Failed CI run', email: false, push: false, slack: true },\n { id: 'digest', label: 'Weekly digest', email: true, push: false, slack: false },\n ]);\n readonly status = signal('No change yet.');\n\n /** Writes one cell back; a real app saves the setting right away. */\n set(alert: AlertSetting, channel: Channel, on: boolean): void {\n this.alerts.update((rows) =>\n rows.map((row) => (row.id === alert.id ? { ...row, [channel]: on } : row)),\n );\n const name = CHANNELS.find((c) => c.key === channel)?.label ?? channel;\n this.status.set(`${name} for ${alert.label} turned ${on ? 'on' : 'off'}.`);\n }\n}\n",
|
|
10877
|
+
"preview": "inline"
|
|
10878
|
+
},
|
|
10682
10879
|
{
|
|
10683
10880
|
"id": "table/empty",
|
|
10684
10881
|
"title": "An empty table with an empty state",
|
|
@@ -10711,7 +10908,7 @@
|
|
|
10711
10908
|
"usage": [
|
|
10712
10909
|
{
|
|
10713
10910
|
"heading": "Columns and rows",
|
|
10714
|
-
"body": "`columns` is an array of `BfTableColumn`: the row property `key`, the header `label`, `sortable` and `align`. A default cell shows the row's `key` property as text when it is a string, a number or a boolean, and is empty otherwise (`null`, `undefined`, an object): render a date or an object with a custom cell. `align: 'end'` lines the header and cells up at the inline end and sets tabular figures, for numbers and amounts
|
|
10911
|
+
"body": "`columns` is an array of `BfTableColumn`: the row property `key`, the header `label`, `sortable` and `align`. A default cell shows the row's `key` property as text when it is a string, a number or a boolean, and is empty otherwise (`null`, `undefined`, an object): render a date or an object with a custom cell. `align: 'end'` lines the header and cells up at the inline end and sets tabular figures, for numbers and amounts; `align: 'center'` centres the header and each cell's control, for a column of toggles or checkboxes (see Toggle cells). `BfTableColumn.align` is a `BfTableColumnAlign`, the released `BfTableAlign` (`start`, `end`) plus `center`.\n\n`rowKey` returns the string that identifies a row in `selection` and keeps each row's DOM when the rows change. By default it reads the row's `id` (a string or a number), and falls back to the row's index when a row has none; pass `[rowKey]=\"byEmail\"` when the key is another property.\n\n`label` names the table. It renders as a visually hidden `caption`, so a screen reader announces it when entering the table; the page's visible heading stays yours. Give every table a `label`."
|
|
10715
10912
|
},
|
|
10716
10913
|
{
|
|
10717
10914
|
"heading": "Sorting",
|
|
@@ -10725,6 +10922,10 @@
|
|
|
10725
10922
|
"heading": "Custom cells",
|
|
10726
10923
|
"body": "An `ng-template[bfTableCell]` replaces the default text of one column with your own markup: a title with a subtitle, an avatar, a badge, a link or a row action. `bfTableCell` names the column's `key`, `let-row` is the row and `let-i=\"index\"` its position. Bind `[bfTableCellOf]` to the same rows as the table so the template's row is typed (without it the row is `unknown`).\n\n<!-- example: table/custom-cells -->"
|
|
10727
10924
|
},
|
|
10925
|
+
{
|
|
10926
|
+
"heading": "Toggle cells",
|
|
10927
|
+
"body": "A settings matrix is a table of switches: rows are what the setting applies to (alert kinds), columns are the options (channels: Email, Push, Slack), and each cell is a `bf-toggle` from a `bfTableCell` template. Keep the row label as the first column, and give the toggle columns `align: 'center'`, which centres the header and the switch in each cell. A toggle in a cell shows no label of its own, so name each one after its column and row with `ariaLabel` (`[ariaLabel]=\"channel.label + ', ' + alert.label\"`, \"Push, New CVE\"); a screen reader also announces the column header as it moves across. Write the change back from `(changed)` and save it right away, as for any toggle. With `stack=\"narrow\"` (or `coarse` in the Capacitor shell) a phone shows each alert as a tile with the channel labels at the start and the toggles at the end, centred on their label line. In a test, `BfTableHarness.getCellHarness(row, 'Push', BfToggleHarness)` returns a cell's toggle.\n\n<!-- example: table/toggle-matrix -->\n\nUnder a coarse pointer every toggle is a 44 px target inside its own cell (a row grows to fit it), and a cell's horizontal padding keeps neighbouring toggles apart, so no two hit areas overlap. A `sm` toggle (40 px wide) takes a 44 px width under touch as well. ArrowUp and ArrowDown move between the toggles of one column."
|
|
10928
|
+
},
|
|
10728
10929
|
{
|
|
10729
10930
|
"heading": "Empty state",
|
|
10730
10931
|
"body": "While `rows` is empty the table shows one full-width row that holds the `empty` slot, such as a `bf-empty-state` with the action that creates the first record. The header stays, so the user still sees what the table holds.\n\n<!-- example: table/empty -->"
|
|
@@ -10890,6 +11091,10 @@
|
|
|
10890
11091
|
}
|
|
10891
11092
|
],
|
|
10892
11093
|
"changelog": [
|
|
11094
|
+
{
|
|
11095
|
+
"version": "0.4.0",
|
|
11096
|
+
"text": "New: toggle cells for a settings matrix (rows of alert kinds, columns of channels, a `bf-toggle`\nin each cell), documented with the `table/toggle-matrix` example. `BfTableColumn.align` takes\n`'center'`: the header and each cell's control are centred, and while stacked the control sits at\nthe tile's end, centred on its label line. New type `BfTableColumnAlign` (`BfTableAlign` plus\n`'center'`; `BfTableAlign` keeps its two members). A stacked cell of a start or end column keeps\nits baseline alignment. `@bitforgehq/ui/testing`: new `BfTableHarness.getCellHarness(row, column,\nquery)` returns the harness of a control in one cell, and `selectRow()`, `getSelectedCount()`\nread only the selection column's checkboxes, never a checkbox or toggle in a custom cell."
|
|
11097
|
+
},
|
|
10893
11098
|
{
|
|
10894
11099
|
"version": "0.3.3",
|
|
10895
11100
|
"text": "Fix: stacked rows (`stack=\"narrow\"` and `stack=\"coarse\"`) show their column labels again when the\npackage is installed from npm. 0.3.2 wrote the label as `content-[attr(data-label)_/_\"\"]`; the\nlibrary build escaped its quotes in the FESM (`\\\"\\\"`), so Tailwind built the rule for a class no\ncell carries and every label `::before` computed to `none`. The cells now carry\n`before:content-data-label`, a utility `theme.css` defines (below). Apps that added the\nworkaround `@source inline('@max-xl:before:content-[attr(data-label)_/_\"\"] pointer-coarse:before:content-[attr(data-label)_/_\"\"]')`\ncan remove it. consumer-smoke now opens a stacked table built from the packed package in\nChromium, under a fine and a coarse pointer, and fails if any FESM class string holds a\nbackslash escape.\n\nThe stacked Sort by select's first option is Not sorted (new label key `tableSortNone`, \"Not\nsorted\"): it shows while `sort` is null, so the select is no longer an empty box, and choosing it\nsets `sort` back to null. New `BfTableHarness.getSortSelect()` returns that select's\n`BfSelectHarness` (`null` when the table cannot stack or has no sortable column)."
|
|
@@ -10907,8 +11112,8 @@
|
|
|
10907
11112
|
"whenToUse": "Use `bf-table` on a desktop page for records the user scans and compares column by column: tasks, invoices, members. On a phone or in the Capacitor shell use [bf-list](https://bitforge-ui.web.app/components/list) rows instead, or let the table stack its rows into tiles with `stack` (see Stacked rows). Use a [bf-list](https://bitforge-ui.web.app/components/list) on desktop too when each record is one line of text with a status, and a description list for the fields of a single record.",
|
|
10908
11113
|
"desktop": "A selectable table tints a row `surface-muted` while hovered (a selected row `primary-soft-hover`); a table without selection does not, because its rows are not targets. Sortable headers darken from `text-muted` to `text` on hover and show the pointer. Hover styles apply only on devices that can hover. The focus ring shows on keyboard focus only, on the header button, the checkbox or the control in a custom cell. Desktop density is the default `md` step; `sm` suits a dense admin page ([density guide](https://bitforge-ui.web.app/density)).",
|
|
10909
11114
|
"theming": "- Header: `text-label-xs`, bold, `text-muted`; a sortable header's hover `text`. A `border` line under the header row.\n- Rows: `text` on the page's surface, divided by `border` lines; hover (selectable only) `surface-muted`; selected `primary-soft`, hover `primary-soft-hover`.\n- Sort arrow: a 1.5 px CSS chevron in the header's text colour.\n- Focus ring: `ring`. Header buttons: `rounded-field`.\n- Stacked rows: tiles on `surface-sunken` with `rounded-tile`, labels `text-label-xs` bold in `text-muted`.\n- Text: `text-body` (`text-label-sm` at `sm`), `tabular-nums` in `end` columns.\n\nPut the table on a `bf-card` (or another surface) yourself; it has no background or border of its own.",
|
|
10910
|
-
"accessibility": "A native `table` with a visually hidden `caption` from `label`, `th scope=\"col\"` headers and one `tr` per row. A sorted column's `th` carries `aria-sort=\"ascending\"` or `\"descending\"`; the others carry none. A sortable header is a native `button` inside the `th`, so its name is the column label; the arrow is `aria-hidden`. The header checkbox is named by `selectAllLabel`, else `provideBfLabels()`'s `tableSelectAll` (\"Select all rows\"), and is mixed (`indeterminate`) while some rows are selected. Each row checkbox is named by the row's first data cell through `aria-labelledby`, so it reads as the row's title. The empty row's cell spans every column.\n\nA table that can stack (`stack` other than `never`) sets explicit `table`, `rowgroup`, `row`, `columnheader` and `cell` roles and names the table from its caption with `aria-labelledby`, because Safari and some screen readers drop a table's semantics once CSS changes its display; so a stacked table still reads as a table with its column headers. Stacked, the header row is visually hidden but stays in the accessibility tree; its sort buttons and checkbox are hidden (a plain label takes a sortable header's place), so no control is focusable where nobody can see it, and the bar's Select all checkbox and Sort by select replace them. A cell's visible label is CSS generated content with empty alternative text (`content: attr(data-label) / \"\"`, the `content-data-label` utility of `theme.css`), so a screen reader reads the column header once, not the label as well.\n\n### Keyboard\n\n| Key | Action |\n| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves through the header buttons, checkboxes and the controls in custom cells in document order |\n| <kbd>ArrowDown</kbd> | On a button, link or checkbox in a row (or the header): moves focus to the control at the same place in the nearest row below that has one; nothing happens at the last row |\n| <kbd>ArrowUp</kbd> | The same, upwards, up to the header row |\n| <kbd>Space</kbd> | Toggles the focused checkbox, or sorts by the focused header |\n| <kbd>Enter</kbd> | Sorts by the focused header |\n\nA stacked table's Sort by select is a native select with its own keys.\n\nText fields, selects and composite widgets (radios, comboboxes, sliders) in a cell keep their own arrow keys.",
|
|
10911
|
-
"notes": "## Do\n\n- Give every table a `label`, and every sortable column a short header.\n- Sort and filter the rows in a `computed()` from `sort` and your filters.\n- Use `stack=\"narrow\"` for a table that may sit in a narrow column, or `stack=\"coarse\"` in the Capacitor shell; otherwise render the rows as `bf-list-item`s below `md`.\n\n## Don't\n\n- Don't put a table on a phone screen that scrolls sideways; stack it or use the list.\n- Don't put a stacking table in a parent that sizes it from its content: the container query needs a width from outside.\n- Don't make a whole row clickable; put a link or button in a custom cell."
|
|
11115
|
+
"accessibility": "A native `table` with a visually hidden `caption` from `label`, `th scope=\"col\"` headers and one `tr` per row. A sorted column's `th` carries `aria-sort=\"ascending\"` or `\"descending\"`; the others carry none. A sortable header is a native `button` inside the `th`, so its name is the column label; the arrow is `aria-hidden`. The header checkbox is named by `selectAllLabel`, else `provideBfLabels()`'s `tableSelectAll` (\"Select all rows\"), and is mixed (`indeterminate`) while some rows are selected. Each row checkbox is named by the row's first data cell through `aria-labelledby`, so it reads as the row's title. A control in a custom cell brings its own name: give a label-less one (a toggle in a settings matrix) `ariaLabel` from its column and row, such as \"Email, Critical finding\", so it is named the same in the table and in a stacked tile. The empty row's cell spans every column.\n\nA table that can stack (`stack` other than `never`) sets explicit `table`, `rowgroup`, `row`, `columnheader` and `cell` roles and names the table from its caption with `aria-labelledby`, because Safari and some screen readers drop a table's semantics once CSS changes its display; so a stacked table still reads as a table with its column headers. Stacked, the header row is visually hidden but stays in the accessibility tree; its sort buttons and checkbox are hidden (a plain label takes a sortable header's place), so no control is focusable where nobody can see it, and the bar's Select all checkbox and Sort by select replace them. A cell's visible label is CSS generated content with empty alternative text (`content: attr(data-label) / \"\"`, the `content-data-label` utility of `theme.css`), so a screen reader reads the column header once, not the label as well.\n\n### Keyboard\n\n| Key | Action |\n| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves through the header buttons, checkboxes and the controls in custom cells in document order |\n| <kbd>ArrowDown</kbd> | On a button, link or checkbox in a row (or the header): moves focus to the control at the same place in the nearest row below that has one; nothing happens at the last row |\n| <kbd>ArrowUp</kbd> | The same, upwards, up to the header row |\n| <kbd>Space</kbd> | Toggles the focused checkbox, or sorts by the focused header |\n| <kbd>Enter</kbd> | Sorts by the focused header |\n\nA stacked table's Sort by select is a native select with its own keys.\n\nText fields, selects and composite widgets (radios, comboboxes, sliders) in a cell keep their own arrow keys.",
|
|
11116
|
+
"notes": "## Do\n\n- Give every table a `label`, and every sortable column a short header.\n- In a settings matrix, keep the row label as the first column, centre the toggle columns and name every toggle after its column and row.\n- Sort and filter the rows in a `computed()` from `sort` and your filters.\n- Use `stack=\"narrow\"` for a table that may sit in a narrow column, or `stack=\"coarse\"` in the Capacitor shell; otherwise render the rows as `bf-list-item`s below `md`.\n\n## Don't\n\n- Don't put a table on a phone screen that scrolls sideways; stack it or use the list.\n- Don't put a stacking table in a parent that sizes it from its content: the container query needs a width from outside.\n- Don't make a whole row clickable; put a link or button in a custom cell."
|
|
10912
11117
|
},
|
|
10913
11118
|
{
|
|
10914
11119
|
"name": "tabs",
|
|
@@ -12003,6 +12208,197 @@
|
|
|
12003
12208
|
"accessibility": "The parts are a `role=\"group\"` named by `label` (else `ariaLabel` or `ariaLabelledby`); each part is a `role=\"spinbutton\"` named from the `timeInputHour`, `timeInputMinute`, `timeInputSecond` and `timeInputDayPeriod` labels, with `aria-valuemin`, `aria-valuemax`, `aria-valuenow` and `aria-valuetext` (the AM or PM name), and the field's `aria-invalid`, `aria-errormessage`, `aria-required`, `aria-readonly` and `aria-disabled`. The clock button is named by `timeInputOpenList`, with `aria-haspopup=\"dialog\"`, `aria-expanded` and `aria-controls`. The list is a `role=\"listbox\"` named by the same label, its times `role=\"option\"` with `aria-selected` and `aria-disabled`; focus stays on the list and `aria-activedescendant` names the active time.\n\n### Keyboard\n\n| Key | Where | Action |\n| ------------------------------------------------ | ----- | -------------------------------------------------------- |\n| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Field | Moves through the parts, then to the clock button |\n| <kbd>0</kbd>–<kbd>9</kbd> | Part | Types into the part; moves on once no further digit fits |\n| <kbd>A</kbd> / <kbd>P</kbd> | AM/PM | Sets the day period (the first letter of its name) |\n| <kbd>ArrowUp</kbd> / <kbd>ArrowDown</kbd> | Part | Next / previous value, wrapping |\n| <kbd>ArrowRight</kbd> / <kbd>ArrowLeft</kbd> | Part | Next / previous part (swapped under `dir=\"rtl\"`) |\n| <kbd>Backspace</kbd> / <kbd>Delete</kbd> | Part | Clears the part |\n| <kbd>:</kbd> | Part | Moves on to the next part |\n| <kbd>Alt</kbd>+<kbd>ArrowDown</kbd> | Part | Opens the list |\n| <kbd>ArrowDown</kbd> / <kbd>ArrowUp</kbd> | List | Next / previous time (skipping disabled ones) |\n| <kbd>Home</kbd> / <kbd>End</kbd> | List | First / last time |\n| <kbd>Enter</kbd> / <kbd>Space</kbd> | List | Picks the active time and closes the list |\n| <kbd>Escape</kbd> | List | Closes the list, focus back on the button |\n| <kbd>Tab</kbd> | List | Closes the list and moves on |",
|
|
12004
12209
|
"notes": "## Do\n\n- Give the field a `label`.\n- Store the value as the ISO time it is, and combine it with a date only where you know the time zone.\n- Pick a `step` that matches the slots you accept (15 for appointments).\n\n## Don't\n\n- Don't use it for a duration; a time input is a time of day.\n- Don't add `seconds` for times people pick by the quarter hour.\n- Don't format the value yourself for display in the field; it follows the locale's clock."
|
|
12005
12210
|
},
|
|
12211
|
+
{
|
|
12212
|
+
"name": "timeline",
|
|
12213
|
+
"selector": "bf-timeline",
|
|
12214
|
+
"selectors": ["bf-timeline"],
|
|
12215
|
+
"className": "BfTimelineComponent",
|
|
12216
|
+
"entryPoint": "@bitforgehq/ui",
|
|
12217
|
+
"file": "src/components/timeline/timeline.component.ts",
|
|
12218
|
+
"category": "Data display",
|
|
12219
|
+
"summary": "Dated list of events with who did what and when, on a vertical line",
|
|
12220
|
+
"description": "A dated history of events, newest first or oldest first as the consumer orders them: who did\nwhat and when, each event a `bf-timeline-event` with a marker on a vertical line.",
|
|
12221
|
+
"overview": "A timeline shows the history of one thing as dated events: who did what, and when. Each event is a [`bf-timeline-event`](https://bitforge-ui.web.app/components/timeline-event) with a coloured marker on a vertical line, its text (which may link the objects it names), then the actor and the time. The timeline shows the events in the order you give them, newest first or oldest first; it never sorts. It works the same in a desktop web app, a narrow side panel and the Capacitor shell.",
|
|
12222
|
+
"example": "<bf-timeline label=\"History\">\n <bf-timeline-event actor=\"Lena Meier\" time=\"2026-10-28T14:30:00Z\" tone=\"success\">\n Marked the finding as fixed\n </bf-timeline-event>\n</bf-timeline>",
|
|
12223
|
+
"primaryExample": "timeline/overview",
|
|
12224
|
+
"examples": [
|
|
12225
|
+
{
|
|
12226
|
+
"id": "timeline/overview",
|
|
12227
|
+
"title": "The bf-timeline overview",
|
|
12228
|
+
"file": "projects/docs/src/app/examples/timeline/overview-example.component.ts",
|
|
12229
|
+
"source": "import { Component } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { BfTimelineComponent, BfTimelineEventComponent } from '@bitforgehq/ui';\n\n/**\n * The bf-timeline overview.\n *\n * Live demo of `bf-timeline`: the history of a security finding, newest first. A system event has\n * no actor, the pull request is a link in the event text, the failed CI run is `danger` and the\n * fix `success`.\n */\n@Component({\n selector: 'app-example-timeline-overview',\n imports: [RouterLink, BfTimelineComponent, BfTimelineEventComponent],\n template: `\n <bf-timeline label=\"Finding history\" class=\"max-w-xl\">\n <bf-timeline-event actor=\"Lena Meier\" time=\"2026-10-28T15:40:00Z\" tone=\"success\">\n Marked the finding as fixed\n </bf-timeline-event>\n <bf-timeline-event actor=\"CI\" time=\"2026-10-28T13:05:00Z\" tone=\"danger\">\n CI failed on the security scan\n </bf-timeline-event>\n <bf-timeline-event actor=\"Jonas Keller\" time=\"2026-10-28T11:20:00Z\" tone=\"primary\">\n Linked\n <a routerLink=\".\" fragment=\"links-in-events\">PR #123</a>\n to the finding\n </bf-timeline-event>\n <bf-timeline-event actor=\"Jonas Keller\" time=\"2026-10-27T16:02:00Z\">\n Assigned the finding to Lena Meier\n </bf-timeline-event>\n <bf-timeline-event time=\"2026-10-27T08:14:00Z\">\n Created by the nightly dependency scan\n </bf-timeline-event>\n </bf-timeline>\n `,\n})\nexport class TimelineOverviewExampleComponent {}\n",
|
|
12230
|
+
"preview": "inline"
|
|
12231
|
+
},
|
|
12232
|
+
{
|
|
12233
|
+
"id": "timeline/audit",
|
|
12234
|
+
"title": "An audit trail with relative times and an action per event",
|
|
12235
|
+
"file": "projects/docs/src/app/examples/timeline/audit-example.component.ts",
|
|
12236
|
+
"source": "import { Component, signal } from '@angular/core';\nimport { BfButtonComponent, BfTimelineComponent, BfTimelineEventComponent } from '@bitforgehq/ui';\n\ninterface AuditEntry {\n id: string;\n actor: string;\n action: string;\n time: string;\n ago: string;\n diff: boolean;\n}\n\nconst ENTRIES: readonly AuditEntry[] = [\n {\n id: 'severity',\n actor: 'Lena Meier',\n action: 'Raised the severity from Medium to High',\n time: '2026-10-28T14:10:00Z',\n ago: '2 hours ago',\n diff: true,\n },\n {\n id: 'policy',\n actor: 'Policy bot',\n action: 'Applied the \"Critical CVEs block releases\" policy',\n time: '2026-10-28T09:30:00Z',\n ago: '7 hours ago',\n diff: true,\n },\n {\n id: 'owner',\n actor: 'Jonas Keller',\n action: 'Set the owner to the Platform team',\n time: '2026-10-27T16:45:00Z',\n ago: 'Yesterday',\n diff: false,\n },\n];\n\n/**\n * An audit trail with relative times and an action per event.\n *\n * Every event names its actor, `timeText` shows a relative time while `datetime` keeps the exact\n * one, and the `end` slot holds a Show diff button: at the inline end while the timeline is wide,\n * under the text in a narrow panel.\n */\n@Component({\n selector: 'app-example-timeline-audit',\n imports: [BfButtonComponent, BfTimelineComponent, BfTimelineEventComponent],\n template: `\n <div class=\"flex max-w-xl flex-col gap-3\">\n <bf-timeline label=\"Audit trail\">\n @for (entry of entries; track entry.id) {\n <bf-timeline-event [actor]=\"entry.actor\" [time]=\"entry.time\" [timeText]=\"entry.ago\">\n {{ entry.action }}\n @if (entry.diff) {\n <bf-button bfSlot=\"end\" variant=\"outline\" size=\"sm\" (clicked)=\"showDiff(entry)\">\n Show diff\n </bf-button>\n }\n </bf-timeline-event>\n }\n </bf-timeline>\n <p data-status aria-live=\"polite\" class=\"text-caption text-text-muted\">{{ status() }}</p>\n </div>\n `,\n})\nexport class TimelineAuditExampleComponent {\n readonly entries = ENTRIES;\n readonly status = signal('No diff open.');\n\n /** A real app would open the change in a side panel. */\n showDiff(entry: AuditEntry): void {\n this.status.set(`Diff of \"${entry.action}\" by ${entry.actor}.`);\n }\n}\n",
|
|
12237
|
+
"preview": "inline"
|
|
12238
|
+
}
|
|
12239
|
+
],
|
|
12240
|
+
"usage": [
|
|
12241
|
+
{
|
|
12242
|
+
"heading": "Events and tones",
|
|
12243
|
+
"body": "`actor` names who did it (leave it empty for a system event), `time` says when (a `Date` or an ISO 8601 string), and the content is the event text. `tone` colours the marker: `neutral` (the default) for routine events, `primary` for the current or most important one, `success` (fixed, merged), `warning`, `danger` (CI failed, a regression) and `info`. The colour only supports the text, so say what happened in words (\"CI failed\"), never by the colour alone. The line runs from each marker to the next and ends at the last event."
|
|
12244
|
+
},
|
|
12245
|
+
{
|
|
12246
|
+
"heading": "Links in events",
|
|
12247
|
+
"body": "The event text is the default slot, so the objects it names can be links: a pull request, a repository, a CVE. Put a plain `<a href>` (or `<a routerLink>`) inline in the text; the event styles every link in its text in `primary-text`, underlined, with a thicker underline on hover and the focus ring on keyboard focus. Keep the link text the object's name (\"PR #123\"), not \"here\"."
|
|
12248
|
+
},
|
|
12249
|
+
{
|
|
12250
|
+
"heading": "Time format",
|
|
12251
|
+
"body": "The time is formatted with `Intl.DateTimeFormat` in the app's `LOCALE_ID`: a medium date and a short time by default (`28 Oct 2026, 14:30` in `en-GB`, `28.10.2026, 14:30` in `de-CH`), in the browser's time zone. `timeFormat` on the timeline takes any `Intl.DateTimeFormatOptions` for all its events (`{ dateStyle: 'short' }`, or a `timeZone`), and `locale` overrides the language for one timeline. `timeText` on an event shows your own text in place of the formatted time, such as a relative \"vor 2 Std.\"; the `time` element's `datetime` keeps the exact value. An empty or invalid `time` shows no time, and a locale or options the browser refuses fall back to the defaults, so a bad value never throws."
|
|
12252
|
+
},
|
|
12253
|
+
{
|
|
12254
|
+
"heading": "Audit trail",
|
|
12255
|
+
"body": "An audit trail is actor-heavy: every event names a person or a bot. The `end` slot holds a small action for one event, such as a Show diff button: it sits at the inline end while the timeline is wide, and wraps under the text in a narrow side panel or on a phone (once the text would get narrower than 16rem). It takes no space while empty.\n\n<!-- example: timeline/audit -->"
|
|
12256
|
+
}
|
|
12257
|
+
],
|
|
12258
|
+
"inputs": [
|
|
12259
|
+
{
|
|
12260
|
+
"name": "label",
|
|
12261
|
+
"kind": "input",
|
|
12262
|
+
"type": "string",
|
|
12263
|
+
"required": false,
|
|
12264
|
+
"doc": "The list's accessible name, such as \"History\"; empty leaves the list unnamed (a visible heading above it already names the section).",
|
|
12265
|
+
"default": "''"
|
|
12266
|
+
},
|
|
12267
|
+
{
|
|
12268
|
+
"name": "timeFormat",
|
|
12269
|
+
"kind": "input",
|
|
12270
|
+
"type": "Intl.DateTimeFormatOptions",
|
|
12271
|
+
"required": false,
|
|
12272
|
+
"doc": "`Intl.DateTimeFormat` options of every event's time; by default a medium date and a short time (`dateStyle: 'medium'`, `timeStyle: 'short'`).",
|
|
12273
|
+
"default": "TIMELINE_TIME_FORMAT"
|
|
12274
|
+
},
|
|
12275
|
+
{
|
|
12276
|
+
"name": "locale",
|
|
12277
|
+
"kind": "input",
|
|
12278
|
+
"type": "string",
|
|
12279
|
+
"required": false,
|
|
12280
|
+
"doc": "BCP 47 locale of the times, such as `de-CH`; empty follows the app's `LOCALE_ID`.",
|
|
12281
|
+
"default": "''"
|
|
12282
|
+
}
|
|
12283
|
+
],
|
|
12284
|
+
"outputs": [],
|
|
12285
|
+
"models": [],
|
|
12286
|
+
"slots": [
|
|
12287
|
+
{
|
|
12288
|
+
"name": "default",
|
|
12289
|
+
"doc": "The events, each a `bf-timeline-event`, in the order to show them."
|
|
12290
|
+
}
|
|
12291
|
+
],
|
|
12292
|
+
"methods": [],
|
|
12293
|
+
"changelog": [
|
|
12294
|
+
{
|
|
12295
|
+
"version": "0.4.0",
|
|
12296
|
+
"text": "New: `bf-timeline`, a dated history of events (who did what, and when) for a record's history\nor an audit trail, in both shells. A `role=\"list\"` named by `label`, in the order the consumer\ngives (it never sorts); each event's time is formatted with `Intl.DateTimeFormat` in the app's\n`LOCALE_ID` (`timeFormat`, default a medium date and a short time; `locale` overrides it)."
|
|
12297
|
+
}
|
|
12298
|
+
],
|
|
12299
|
+
"since": "0.4.0",
|
|
12300
|
+
"whenToUse": "Use `bf-timeline` for a record's history or an audit trail: a finding that was created, assigned, linked to a pull request and fixed; the changes in a CVE side panel; who changed a setting. Use [bf-list](https://bitforge-ui.web.app/components/list) instead for items the user opens or acts on, and for navigation; a timeline is not a stepper through a process either, nor a feed the user scrolls for new items. For the details of one record, use a [bf-description-list](https://bitforge-ui.web.app/components/description-list).",
|
|
12301
|
+
"desktop": "The timeline itself is not interactive: no hover, no pointer cursor, no focus stop. Only the links in the event text and the controls in an `end` slot take focus, with their own hover and focus ring. Its spacing does not follow `provideBfDefaults()`: the events keep one rhythm at every density, and it has no `size`. Inside `provideBfShell()` it renders and behaves the same; the `end` slot's buttons bring their own 44 px touch height.",
|
|
12302
|
+
"theming": "- Marker: a 10 px dot (`rounded-control`) in `text-subtle` (neutral), `primary`, `success`, `warning`, `danger` or `info`; `CanvasText` under forced colours.\n- Line: a 1 px `border` line from marker to marker.\n- Text: `text-body` in `text`; links `primary-text`, underlined, `ring` on keyboard focus. Meta line: `text-caption` in `text-muted`.\n- Spacing: 12 px between the marker and the text, 20 px between events.",
|
|
12303
|
+
"accessibility": "The timeline is a list (`role=\"list\"`, named by `label` through `aria-label`) and each event inside it a `listitem`, so a screen reader announces the number of events and the position of each. An event outside a `bf-timeline` claims no role. The marker and the line are `aria-hidden`; the tone is decoration, so the text must say what happened. The time is a `time` element whose `datetime` is the ISO value, and the separator between actor and time is hidden from assistive technology. Links and `end` slot controls are native elements in reading order.",
|
|
12304
|
+
"notes": "## Do\n\n- Give the timeline a `label` when no visible heading names it, and keep one order (newest first) across the app.\n- Name the actor and the object in every event; link the object.\n\n## Don't\n\n- Don't use a timeline for navigation or for steps the user still has to take.\n- Don't carry meaning in the tone alone; say \"CI failed\", not just a red dot."
|
|
12305
|
+
},
|
|
12306
|
+
{
|
|
12307
|
+
"name": "timeline-event",
|
|
12308
|
+
"selector": "bf-timeline-event",
|
|
12309
|
+
"selectors": ["bf-timeline-event"],
|
|
12310
|
+
"className": "BfTimelineEventComponent",
|
|
12311
|
+
"entryPoint": "@bitforgehq/ui",
|
|
12312
|
+
"file": "src/components/timeline/timeline-event.component.ts",
|
|
12313
|
+
"category": "Data display",
|
|
12314
|
+
"summary": "One timeline event with a coloured marker, its text, actor and time",
|
|
12315
|
+
"description": "One event of a `bf-timeline`: a marker coloured by `tone`, the event text (the default slot,\nwhich may hold links), then who did it (`actor`) and when (`time`, in a `time` element).",
|
|
12316
|
+
"overview": "One event of a [`bf-timeline`](https://bitforge-ui.web.app/components/timeline): a marker coloured by `tone`, the event text from the default slot, then a meta line with `actor` and `time`. Place events in a `bf-timeline`, which gives them the list semantics and the time format; the line between the markers ends at the last event.",
|
|
12317
|
+
"example": "<bf-timeline-event actor=\"CI\" time=\"2026-10-28T09:12:00Z\" tone=\"danger\">\n CI failed on <a href=\"/pulls/123\">PR #123</a>\n</bf-timeline-event>",
|
|
12318
|
+
"primaryExample": "timeline-event/overview",
|
|
12319
|
+
"examples": [
|
|
12320
|
+
{
|
|
12321
|
+
"id": "timeline-event/overview",
|
|
12322
|
+
"title": "The bf-timeline-event overview",
|
|
12323
|
+
"file": "projects/docs/src/app/examples/timeline-event/overview-example.component.ts",
|
|
12324
|
+
"source": "import { Component } from '@angular/core';\nimport { RouterLink } from '@angular/router';\nimport { BfTimelineComponent, BfTimelineEventComponent } from '@bitforgehq/ui';\n\n/**\n * The bf-timeline-event overview.\n *\n * Live demo of `bf-timeline-event`: one event per tone in a `bf-timeline`, with and without an\n * actor, one with a link in its text and one with a relative `timeText`.\n */\n@Component({\n selector: 'app-example-timeline-event-overview',\n imports: [RouterLink, BfTimelineComponent, BfTimelineEventComponent],\n template: `\n <bf-timeline label=\"Event tones\" class=\"max-w-xl\">\n <bf-timeline-event actor=\"Lena Meier\" time=\"2026-10-28T15:40:00Z\" tone=\"primary\">\n Opened\n <a routerLink=\"/components/timeline\" fragment=\"links-in-events\">CVE-2026-1234</a>\n </bf-timeline-event>\n <bf-timeline-event actor=\"CI\" time=\"2026-10-28T15:10:00Z\" tone=\"success\">\n All checks passed\n </bf-timeline-event>\n <bf-timeline-event actor=\"Dependency bot\" time=\"2026-10-28T14:00:00Z\" tone=\"warning\">\n Found an outdated base image\n </bf-timeline-event>\n <bf-timeline-event actor=\"CI\" time=\"2026-10-28T13:05:00Z\" tone=\"danger\">\n CI failed on the security scan\n </bf-timeline-event>\n <bf-timeline-event time=\"2026-10-28T12:00:00Z\" timeText=\"4 hours ago\" tone=\"info\">\n A new advisory was published\n </bf-timeline-event>\n <bf-timeline-event actor=\"Jonas Keller\" time=\"2026-10-27T16:02:00Z\">\n Assigned the finding to Lena Meier\n </bf-timeline-event>\n </bf-timeline>\n `,\n})\nexport class TimelineEventOverviewExampleComponent {}\n",
|
|
12325
|
+
"preview": "inline"
|
|
12326
|
+
},
|
|
12327
|
+
{
|
|
12328
|
+
"id": "timeline-event/basic",
|
|
12329
|
+
"title": "An event's actor, time and tone",
|
|
12330
|
+
"file": "projects/docs/src/app/examples/timeline-event/basic-example.component.ts",
|
|
12331
|
+
"source": "import { Component } from '@angular/core';\nimport { BfTimelineComponent, BfTimelineEventComponent } from '@bitforgehq/ui';\n\n/**\n * An event's actor, time and tone.\n *\n * Two events of a CVE side panel: one by a person with an exact time, one by a bot with a relative\n * `timeText` whose `datetime` keeps the exact value.\n */\n@Component({\n selector: 'app-example-timeline-event-basic',\n imports: [BfTimelineComponent, BfTimelineEventComponent],\n template: `\n <bf-timeline label=\"CVE history\" class=\"max-w-sm\">\n <bf-timeline-event actor=\"Lena Meier\" time=\"2026-10-28T15:40:00Z\" tone=\"warning\">\n Accepted the risk until the next release\n </bf-timeline-event>\n <bf-timeline-event\n actor=\"Advisory bot\"\n time=\"2026-10-28T10:00:00Z\"\n timeText=\"6 hours ago\"\n tone=\"info\"\n >\n Imported the advisory\n </bf-timeline-event>\n </bf-timeline>\n `,\n})\nexport class TimelineEventBasicExampleComponent {}\n",
|
|
12332
|
+
"preview": "inline"
|
|
12333
|
+
}
|
|
12334
|
+
],
|
|
12335
|
+
"usage": [
|
|
12336
|
+
{
|
|
12337
|
+
"heading": "Basic usage",
|
|
12338
|
+
"body": "`actor`, `time` (a `Date` or an ISO 8601 string) and `tone` are inputs; the text is the content and may hold links, which take the link style. `timeText` replaces the formatted time with your own text while the `datetime` keeps the exact value. `bfSlot=\"end\"` holds a small action, at the inline end on a wide timeline and below the text on a narrow one.\n\n<!-- example: timeline-event/basic -->"
|
|
12339
|
+
}
|
|
12340
|
+
],
|
|
12341
|
+
"inputs": [
|
|
12342
|
+
{
|
|
12343
|
+
"name": "actor",
|
|
12344
|
+
"kind": "input",
|
|
12345
|
+
"type": "string",
|
|
12346
|
+
"required": false,
|
|
12347
|
+
"doc": "Who did it, such as a person or a bot; empty for a system event.",
|
|
12348
|
+
"default": "''"
|
|
12349
|
+
},
|
|
12350
|
+
{
|
|
12351
|
+
"name": "time",
|
|
12352
|
+
"kind": "input",
|
|
12353
|
+
"type": "Date | string | null",
|
|
12354
|
+
"required": false,
|
|
12355
|
+
"doc": "When it happened: a Date or an ISO 8601 string; empty or invalid shows no time.",
|
|
12356
|
+
"default": "null"
|
|
12357
|
+
},
|
|
12358
|
+
{
|
|
12359
|
+
"name": "timeText",
|
|
12360
|
+
"kind": "input",
|
|
12361
|
+
"type": "string",
|
|
12362
|
+
"required": false,
|
|
12363
|
+
"doc": "Shown in place of the formatted time, such as \"vor 2 Std.\"; the `time` element's `datetime` keeps the exact value.",
|
|
12364
|
+
"default": "''"
|
|
12365
|
+
},
|
|
12366
|
+
{
|
|
12367
|
+
"name": "tone",
|
|
12368
|
+
"kind": "input",
|
|
12369
|
+
"type": "BfTimelineTone",
|
|
12370
|
+
"required": false,
|
|
12371
|
+
"doc": "The marker's colour.",
|
|
12372
|
+
"default": "'neutral'",
|
|
12373
|
+
"expandedType": "'neutral' | 'primary' | 'success' | 'warning' | 'danger' | 'info'"
|
|
12374
|
+
}
|
|
12375
|
+
],
|
|
12376
|
+
"outputs": [],
|
|
12377
|
+
"models": [],
|
|
12378
|
+
"slots": [
|
|
12379
|
+
{
|
|
12380
|
+
"name": "default",
|
|
12381
|
+
"doc": "The event text; links in it take the link style."
|
|
12382
|
+
},
|
|
12383
|
+
{
|
|
12384
|
+
"name": "end",
|
|
12385
|
+
"doc": "Content at the inline end on a wide timeline and below the text on a narrow one, such as a button."
|
|
12386
|
+
}
|
|
12387
|
+
],
|
|
12388
|
+
"methods": [],
|
|
12389
|
+
"changelog": [
|
|
12390
|
+
{
|
|
12391
|
+
"version": "0.4.0",
|
|
12392
|
+
"text": "New: `bf-timeline-event`, one event of a `bf-timeline`: a 10 px marker coloured by `tone`\n(`neutral`, `primary`, `success`, `warning`, `danger`, `info`; new type `BfTimelineTone`) on a\n`border` line that ends at the last event, the event text in the default slot (links in it take\nthe link style), then `actor` and `time` (a `Date` or an ISO 8601 string, in a `time` element;\n`timeText` shows a relative text while `datetime` keeps the exact value; an invalid time shows\nnothing). A `bfSlot=\"end\"` action sits at the inline end, or under the text once the event is\nnarrow."
|
|
12393
|
+
}
|
|
12394
|
+
],
|
|
12395
|
+
"since": "0.4.0",
|
|
12396
|
+
"whenToUse": "Use `bf-timeline-event` for each dated entry of a `bf-timeline`: a change, a comment, a CI run. Use [bf-list-item](https://bitforge-ui.web.app/components/list-item) instead for a row the user opens or acts on.",
|
|
12397
|
+
"desktop": "An event has no hover and no focus stop of its own; only the links in its text and the controls in its `end` slot are interactive. It renders the same inside `provideBfShell()`.",
|
|
12398
|
+
"theming": "- Marker: `text-subtle`, `primary`, `success`, `warning`, `danger` or `info`; `CanvasText` under forced colours. Line: `border`.\n- Text `text-body` in `text`, links `primary-text`; meta line `text-caption` in `text-muted`.",
|
|
12399
|
+
"accessibility": "Inside a `bf-timeline` the event is a `listitem`; on its own it has no role. The marker is `aria-hidden`; the time is a `time` element with an ISO `datetime`, and the separator between actor and time is hidden from assistive technology.",
|
|
12400
|
+
"notes": "## Do\n\n- Write the text as what happened (\"Assigned to Lena Meier\"), and link the object it names.\n\n## Don't\n\n- Don't put more than one small action in the `end` slot; open the details elsewhere."
|
|
12401
|
+
},
|
|
12006
12402
|
{
|
|
12007
12403
|
"name": "toast",
|
|
12008
12404
|
"selector": "bf-toast",
|
|
@@ -12196,11 +12592,11 @@
|
|
|
12196
12592
|
},
|
|
12197
12593
|
{
|
|
12198
12594
|
"heading": "In a settings list",
|
|
12199
|
-
"body": "Put the label first so a column of toggles lines up on the right, as in the Meadow Sage profile screen.\n\n<!-- example: toggle/settings -->\n\nWhen the row itself already shows the title, such as a `bf-list-item`'s `title`/`subtitle`, don't repeat it as the toggle's own `label`; give it `ariaLabel` instead so the switch still has an accessible name, or `ariaLabelledby` with the id of the element that shows the title.\n\n<!-- example: toggle/list-row
|
|
12595
|
+
"body": "Put the label first so a column of toggles lines up on the right, as in the Meadow Sage profile screen.\n\n<!-- example: toggle/settings -->\n\nWhen the row itself already shows the title, such as a `bf-list-item`'s `title`/`subtitle`, don't repeat it as the toggle's own `label`; give it `ariaLabel` instead so the switch still has an accessible name, or `ariaLabelledby` with the id of the element that shows the title.\n\n<!-- example: toggle/list-row -->\n\nIn a table of switches (a settings matrix: alert kinds by channel), each cell's toggle is named after its column and row with `ariaLabel`; see [bf-table](https://bitforge-ui.web.app/components/table)'s Toggle cells."
|
|
12200
12596
|
},
|
|
12201
12597
|
{
|
|
12202
12598
|
"heading": "Sizes",
|
|
12203
|
-
"body": "`sm` (32 px row, 24×40 track), `md` (default, 40 px row, 28×48 track), `lg` (48 px row, 32×56 track). Unset, `size` follows `provideBfDefaults()`'s `controlSize`, `md` by default (`provideBfDefaults({ size: 'lg' })` enlarges every control of a Capacitor app; `{ size: 'sm' }` in a component's `providers` makes a dense sub-tree). Under a fine pointer the row is the hit area (32 px and up, above the 24 px of WCAG 2.5.8); under a coarse pointer (touch) `sm` and `md` rows grow to 44 px. The knob travels to the inline end, so it moves left under `dir=\"rtl\"`.\n\n<!-- example: toggle/sizes -->"
|
|
12599
|
+
"body": "`sm` (32 px row, 24×40 track), `md` (default, 40 px row, 28×48 track), `lg` (48 px row, 32×56 track). Unset, `size` follows `provideBfDefaults()`'s `controlSize`, `md` by default (`provideBfDefaults({ size: 'lg' })` enlarges every control of a Capacitor app; `{ size: 'sm' }` in a component's `providers` makes a dense sub-tree). Under a fine pointer the row is the hit area (32 px and up, above the 24 px of WCAG 2.5.8); under a coarse pointer (touch) `sm` and `md` rows grow to 44 px, and a label-less `sm` switch (40 px wide) also takes a 44 px width, its track centred. The knob travels to the inline end, so it moves left under `dir=\"rtl\"`.\n\n<!-- example: toggle/sizes -->"
|
|
12204
12600
|
},
|
|
12205
12601
|
{
|
|
12206
12602
|
"heading": "With Signal Forms",
|
|
@@ -12389,6 +12785,10 @@
|
|
|
12389
12785
|
}
|
|
12390
12786
|
],
|
|
12391
12787
|
"changelog": [
|
|
12788
|
+
{
|
|
12789
|
+
"version": "0.4.0",
|
|
12790
|
+
"text": "A label-less `sm` toggle (a table cell) takes a 44 px min-width under a coarse pointer, its track\ncentred, so its touch target is 44 × 44 px (it was 40 px wide). Nothing changes under a fine\npointer or for `md` and `lg`. `@bitforgehq/ui/testing`: new `BfToggleHarness` (`with({ label })`\nby the switch's computed name, `getLabel()`, `isChecked()`, `isDisabled()`, `isReadonly()`,\n`toggle()`, `check()`, `uncheck()`, `getErrorText()`)."
|
|
12791
|
+
},
|
|
12392
12792
|
{
|
|
12393
12793
|
"version": "0.3.0",
|
|
12394
12794
|
"text": "New `ariaLabelledby` input: the id of the element that names the switch when no visible `label`\nis shown, set as the native switch's `aria-labelledby`. Additive.\n\nNew exported `BfToggleLabelPosition` (`'end' | 'start'`), the type of `labelPosition` (was an\ninline union with the same values).\n\n**Behaviour:** new `size` (`BfSize`; unset follows `provideBfDefaults()`'s `controlSize`): rows\nare `sm` 32, `md` 40 (default), `lg` 48 px under a fine pointer, and at least 44 px under a coarse\npointer (was 44 px everywhere); the track is 24×40 / 28×48 / 32×56 px with a matching knob, and the knob\ntravels to the inline end (mirrored under `dir=\"rtl\"`).\n\n**Behaviour:** an error without a message shows its kind's default text (was a red border only):\nonce touched, the field shows the first error's own `message`, else `provideBfLabels()`'s\n`validation` text for its kind (Signal Forms and Reactive interop kinds), else\n`validation.fallback`. `errorMessage` still wins. See Migration 1.\n\n**Behaviour:** the track and the knob use `rounded-control` (`--radius-control`, `9999px` by\ndefault, so they render as before).\n\n**Behaviour:** the knob carries the state for any configured primary: `on-primary` on the on track\n(was `surface`), `primary-strong` when read-only and on (was `primary`); off and disabled knobs\nstay `surface`.\n\nJSDoc of `toggle.component.ts` for the API reference: `@slot` docs for `default`; `@param` docs;\ntemplate handlers and framework hooks marked `@internal`, so the reference leaves them out. No\nbehaviour change.\n\n**Behaviour:** a pressed state (F-045): the track takes a `text` border while the row is held down. None while disabled or read-only; under forced colours the system colours win.\n\n**Behaviour:** sets `aria-errormessage` to its error paragraph (`<id>-error`) while\n`aria-invalid=\"true\"`, and an error that appears while a pointer button is held down (a press\nelsewhere that blurred it) shows once the press ends, so the layout never moves under the pointer\n(F-020).\n\n**Behaviour:** the label wraps (`min-w-0 wrap-anywhere`) instead of overflowing its row at large\ntext sizes (F-051): a `labelPosition=\"start\"` label in a `bf-list-item` spilled past the row's\nstart edge at the iOS AX sizes."
|