bitboss-ui 3.0.0-beta.1 → 3.0.0-beta.12

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.
Files changed (106) hide show
  1. package/README.md +36 -29
  2. package/dist/ai/BbBadge.md +10 -5
  3. package/dist/ai/BbBaseSelect.md +1 -0
  4. package/dist/ai/BbSelect.md +95 -4
  5. package/dist/ai/BbSelectPopover.md +9 -17
  6. package/dist/ai/BbTable.md +771 -71
  7. package/dist/ai/changelog.json +92 -2
  8. package/dist/ai/components.json +76 -18
  9. package/dist/ai/guides/component-picker.md +2 -2
  10. package/dist/ai/guides/design-language.md +14 -14
  11. package/dist/ai/guides/design-tokens.md +25 -18
  12. package/dist/ai/guides/installation-and-plugin-setup.md +1 -1
  13. package/dist/ai/guides/migration/components/bb-dialog.md +35 -29
  14. package/dist/ai/guides/migration/components/bb-table.md +446 -28
  15. package/dist/ai/guides/migration/v2-to-v3.md +175 -75
  16. package/dist/ai/index.md +1 -1
  17. package/dist/ai/recipes/inertia/layout-scaffold.md +5 -1
  18. package/dist/ai/recipes/inertia/ownership-atlas.md +41 -20
  19. package/dist/ai/recipes/inertia/records-workspace.md +26 -7
  20. package/dist/ai/recipes/nuxt/layout-scaffold.md +5 -1
  21. package/dist/ai/recipes/nuxt/records-workspace.md +26 -7
  22. package/dist/ai/recipes/vue/layout-scaffold.md +5 -1
  23. package/dist/ai/recipes/vue/records-workspace.md +26 -7
  24. package/dist/ai/source/BbBadge.md +52 -13
  25. package/dist/ai/source/BbBaseInputContainer.md +26 -0
  26. package/dist/ai/source/BbBaseSelect.md +71 -16
  27. package/dist/ai/source/BbBreadcrumbs.md +31 -0
  28. package/dist/ai/source/BbButton.md +51 -24
  29. package/dist/ai/source/BbDialog.md +39 -10
  30. package/dist/ai/source/BbDropdown.md +58 -11
  31. package/dist/ai/source/BbDropdownGroup.md +53 -10
  32. package/dist/ai/source/BbIcon.md +43 -34
  33. package/dist/ai/source/BbOffCanvas.md +42 -12
  34. package/dist/ai/source/BbProgress.md +2 -2
  35. package/dist/ai/source/BbSelect.md +28 -0
  36. package/dist/ai/source/BbSelectPopover.md +23 -3
  37. package/dist/ai/source/BbTable.md +4458 -1362
  38. package/dist/ai/source/CommonInputWrapper.md +4 -0
  39. package/dist/ai/source/CommonPopover.md +6 -0
  40. package/dist/ai/source/FlatListBox.md +11 -5
  41. package/dist/ai/source/GroupedListBox.md +7 -5
  42. package/dist/components/BbBadge/BadgeBodyContent.vue_vue_type_script_setup_true_lang.js +22 -16
  43. package/dist/components/BbBadge/BbBadge.vue_vue_type_script_setup_true_lang.js +33 -12
  44. package/dist/components/BbBadge/badgeButtonContext.d.ts +21 -8
  45. package/dist/components/BbBaseSelect/BbBaseSelect.vue_vue_type_script_setup_true_lang.js +74 -65
  46. package/dist/components/BbBaseSelect/types.d.ts +22 -0
  47. package/dist/components/BbButton/BbButton.vue_vue_type_script_setup_true_lang.js +86 -82
  48. package/dist/components/BbDropdown/BbDropdown.vue_vue_type_script_setup_true_lang.js +1 -1
  49. package/dist/components/BbIcon/BbIcon.vue_vue_type_script_setup_true_lang.js +40 -40
  50. package/dist/components/BbIcon/iconCache.d.ts +33 -0
  51. package/dist/components/BbIcon/iconCache.js +17 -0
  52. package/dist/components/BbPagination/BbPagination.vue.d.ts +1 -1
  53. package/dist/components/BbSelect/BbSelect.vue_vue_type_script_setup_true_lang.js +1 -0
  54. package/dist/components/BbSelect/types.d.ts +24 -0
  55. package/dist/components/BbTable/BbTable.vue.d.ts +8 -4
  56. package/dist/components/BbTable/BbTable.vue_vue_type_script_setup_true_lang.js +1304 -587
  57. package/dist/components/BbTable/BbTableDataRow.d.ts +151 -0
  58. package/dist/components/BbTable/BbTableDataRow.js +138 -0
  59. package/dist/components/BbTable/types.d.ts +247 -86
  60. package/dist/components/BbTable/utils.d.ts +87 -4
  61. package/dist/components/BbTable/utils.js +72 -36
  62. package/dist/components/CommonPopover/CommonPopover.vue_vue_type_script_setup_true_lang.js +2 -1
  63. package/dist/components/FlatListBox/FlatListBox.vue_vue_type_script_setup_true_lang.js +2 -2
  64. package/dist/components/GroupedListBox/GroupedListBox.vue_vue_type_script_setup_true_lang.js +2 -2
  65. package/dist/composables/useBaseOptions.js +20 -10
  66. package/dist/directives/bbDropdown.d.ts +5 -0
  67. package/dist/directives/bbDropdown.js +32 -10
  68. package/dist/directives/createPopoverDirective.d.ts +62 -0
  69. package/dist/directives/createPopoverDirective.js +63 -23
  70. package/dist/i18n/locales/cs.json +4 -1
  71. package/dist/i18n/locales/da.json +4 -1
  72. package/dist/i18n/locales/de.json +4 -1
  73. package/dist/i18n/locales/el.json +4 -1
  74. package/dist/i18n/locales/en.json +4 -1
  75. package/dist/i18n/locales/es.json +4 -1
  76. package/dist/i18n/locales/fi.json +4 -1
  77. package/dist/i18n/locales/fr.json +4 -1
  78. package/dist/i18n/locales/hu.json +4 -1
  79. package/dist/i18n/locales/it.json +4 -1
  80. package/dist/i18n/locales/ja.json +4 -1
  81. package/dist/i18n/locales/ko.json +4 -1
  82. package/dist/i18n/locales/nb.json +4 -1
  83. package/dist/i18n/locales/nl.json +4 -1
  84. package/dist/i18n/locales/pl.json +4 -1
  85. package/dist/i18n/locales/pt.json +4 -1
  86. package/dist/i18n/locales/ro.json +4 -1
  87. package/dist/i18n/locales/ru.json +4 -1
  88. package/dist/i18n/locales/sv.json +4 -1
  89. package/dist/i18n/locales/tr.json +4 -1
  90. package/dist/i18n/locales/uk.json +4 -1
  91. package/dist/i18n/locales/zh-cn.json +4 -1
  92. package/dist/index.d.ts +5 -1
  93. package/dist/index.js +68 -67
  94. package/dist/llms-full.txt +1718 -308
  95. package/dist/llms-medium.txt +17 -17
  96. package/dist/llms.txt +1 -1
  97. package/dist/locale-blueprint.json +4 -1
  98. package/dist/styles.css +1 -1
  99. package/dist/validated/BbSelect.vue.d.ts +2 -0
  100. package/dist/validated/BbSelect.vue_vue_type_script_setup_true_lang.js +1 -0
  101. package/llms.txt +1 -1
  102. package/package.json +1 -1
  103. package/dist/assets/svgs/sort.svg_raw.js +0 -4
  104. package/dist/components/BbBadge/BadgeAppendIcon.vue.d.ts +0 -3
  105. package/dist/components/BbBadge/BadgeAppendIcon.vue.js +0 -5
  106. package/dist/components/BbBadge/BadgeAppendIcon.vue_vue_type_script_setup_true_lang.js +0 -22
package/README.md CHANGED
@@ -36,46 +36,53 @@ Full detail: `ai/guides/installation-and-plugin-setup.md`.
36
36
 
37
37
  ### Composables
38
38
 
39
- | Composable | Role |
40
- | ----------------------------- | ---------------------------------------------------------- |
41
- | `useBbConfig` | Global options for the kit (locale, defaults, etc.). |
42
- | `useBroadcastChannelInstance` | Shared `BroadcastChannel` helper for cross-tab messaging. |
43
- | `useConfirm` | Programmatic confirm / modal flows. |
44
- | `useCountdown` | Countdown timer state. |
45
- | `useMobile` | Mobile / viewport-oriented behaviour. |
46
- | `useQueue` | Simple async task queue. |
47
- | `useSafeArea` | Claim a screen edge for a docked panel so toasts avoid it. |
48
- | `useToast` | Toast notifications API. |
49
-
50
- ### Base\* components (primitives) _(reference-only - prefer `Bb*` equivalents for consumer code)_
51
-
52
- Headless or low-level pieces used inside **Bb\*** wrappers or your own compositions:
53
-
54
- - **Actions:** `BbBaseButton`
55
- - **Text & numbers:** `BaseTextInput`, `BaseTextarea`, `BaseNumberInput`, `BaseTag`
56
- - **Choice:** `BaseCheckbox`, `BaseCheckboxGroup`, `BaseRadio`, `BaseRadioGroup`, `BaseSwitch`, `BaseSwitchGroup`, `BaseSlider`, `BaseRating`
57
- - **Choice glyphs (presentational SVGs):** `BbBaseCheckboxIcon`, `BbBaseRadioIcon`, `BbBaseSwitchIcon`
58
- - **Rich inputs:** `BaseSelect`, `BaseColorInput`, `BaseDatePicker`, `BaseDatePickerInput`
59
- - **Layout / chrome:** `BaseInputContainer`, `BaseDialog`
39
+ | Composable | Role |
40
+ | ---------------------- | --------------------------------------------------------------------- |
41
+ | `useBbConfig` | Global options for the kit (locale, defaults, etc.). |
42
+ | `useBbDropdownContext` | Read a `BbDropdown`'s resolved groups/selections from anywhere by id. |
43
+ | `useBbTableContext` | Imperative handle onto a `BbTable` by id. |
44
+ | `useBbTabsContext` | Programmatic tab selection for a `BbTabs` by id. |
45
+ | `useConfirm` | Programmatic confirm / modal flows. |
46
+ | `useCountdown` | Countdown timer state. |
47
+ | `useId` | SSR-safe unique ids. |
48
+ | `useMobile` | Mobile / viewport-oriented behaviour. |
49
+ | `useSafeArea` | Claim a screen edge for a docked panel so toasts avoid it. |
50
+ | `useToast` | Toast notifications API. |
51
+
52
+ ### Utilities
53
+
54
+ - **`slotKey`** — the normalizer behind every dynamic per-item named slot (`BbDropdown`, `BbTable`, `BbTabs`, `BbTree`, `BbBreadcrumbs`). Import it when building slot names programmatically instead of restating the rule.
55
+
56
+ ### Directives
57
+
58
+ `vBbTooltip`, `vBbDropdown`, `vBbColor`, `vBbDate`, `vBbTime` — plus a
59
+ `Bb*DirectivePlugin` for each, for app-level registration. Prefer the directive
60
+ over the component for simple no-slot cases.
61
+
62
+ ### Base\* primitives
63
+
64
+ The styled `Base*` input controls and the input chrome (`BbBaseInputContainer`,
65
+ `CommonInputWrapper` — label, prepend/append, prefix/suffix, clear button,
66
+ spacing) are **internal composition layers and are not exported**. Use the full
67
+ public inputs (`BbTextInput`, `BbSelect`, …); pass `hideLabel` for a
68
+ bare/inline control. The exported primitives are **`BbBaseButton`** and the
69
+ presentational glyphs **`BbBaseCheckboxIcon`**, **`BbBaseRadioIcon`**,
70
+ **`BbBaseSwitchIcon`**.
60
71
 
61
72
  ### Bb\* components (composed)
62
73
 
63
74
  Product-ready components with labels, hints, errors, and consistent styling:
64
75
 
65
- - **Forms & actions:** `BbTextInput`, `BbTextarea`, `BbNumberInput`, `BbSelect`, `BbCheckbox`, `BbCheckboxGroup`, `BbRadio`, `BbRadioGroup`, `BbSwitch`, `BbSwitchGroup`, `BbSlider`, `BbRating`, `BbColorInput`, `BbDatePickerInput`, `BbButton`, `BbBadge`, `BbIndicator`, `BbTag`, `BbDropdown`, `BbDropdownButton`
66
- - **Feedback:** `BbAlert`, `BbProgress`, `BbSpinner`, `BbToast`, `BbTooltip`
67
- - **Layout & navigation:** `BbAccordion`, `BbCollapsible`, `BbTab`, `BbBreadcrumbs`, `BbPagination`,`BbSmoothHeight`
76
+ - **Forms & actions:** `BbTextInput`, `BbTextarea`, `BbNumberInput`, `BbSelect`, `BbSelectPopover`, `BbCheckbox`, `BbCheckboxGroup`, `BbRadio`, `BbRadioGroup`, `BbSwitch`, `BbSwitchGroup`, `BbSlider`, `BbRating`, `BbColorInput`, `BbColorPalette`, `BbDatePicker`, `BbDatePickerInput`, `BbTimePicker`, `BbTimePickerInput`, `BbButton`, `BbBadge`, `BbBadgeButton`, `BbIndicator`, `BbTag`, `BbDropdown`, `BbDropdownGroup`, `BbDropdownButton`, `BbAsterisk`
77
+ - **Feedback:** `BbAlert`, `BbProgress`, `BbSpinner`, `BbToast`, `BbToastPortal`, `BbTooltip`
78
+ - **Layout & navigation:** `BbAccordion`, `BbCollapsible`, `BbTabs` (+ `BbTabsRoot`, `BbTabsList`, `BbTabsPanes`), `BbBreadcrumbs`, `BbPagination`, `BbSmoothHeight`
68
79
  - **Overlays & panels:** `BbDialog`, `BbConfirm`, `BbConfirmPortal`, `BbOffCanvas`, `BbPopover`
69
80
  - **Data:** `BbTable`, `BbTree`
70
81
  - **Media & files:** `BbAvatar`, `BbDropzone`, `BbIcon`
71
82
 
72
- ### Shared input shells _(internal layout primitives - not for direct consumer use)_
73
-
74
- - **`CommonInputWrapper`** — the internal input-chrome primitive (label, prepend/append, prefix/suffix, clear button, spacing) that every `Bb*` input composes. Not exported; use the full public inputs (`BbTextInput`, `BbSelect`, …).
75
-
76
83
  ### Exported component types
77
84
 
78
- For most **Base\*** and **Bb\*** components, the package exports matching **props**, **events**, and (where relevant) **slots** types (for example `BbSelectProps`, `BaseSelectEvents`). Use them for typed wrappers and design-system layers; the docs list the full set per component.
85
+ For the public **Bb\*** components, the package exports matching **props**, **events**, and (where relevant) **slots** types (for example `BbSelectProps`, `BbTableEvents`), plus supporting shapes like `BbDropdownItem` and `DropdownGroupItemsGetter`. Use them for typed wrappers and design-system layers; the docs list the full set per component.
79
86
 
80
87
  ---
81
88
 
@@ -309,9 +309,12 @@ state in the handler. Prefer `secondary` / `outline` for filter chips; reserve
309
309
  ```
310
310
 
311
311
  The clear button inherits the variant's foreground, so it reads on every surface
312
- — light on a filled `primary`, dark on a `secondary`/`outline`. Set
313
- `clearable-label` for the button's accessible name (defaults to the localized
314
- `badge.clearLabel`).
312
+ — light on a filled `primary`, dark on a `secondary`/`outline`. By default its
313
+ accessible name composes the localized verb with the badge's own text a chip
314
+ reading "Italy" announces "Remove Italy", with no per-chip wiring. Set
315
+ `clearable-label` only when that composition is wrong for the context (it
316
+ replaces the whole name). With a `BbBadgeButton` body the composition is
317
+ skipped and the button falls back to the bare localized `badge.clearLabel`.
315
318
 
316
319
  Icons combine with `clearable`: a **`prepend:icon`** stays (it leads the label);
317
320
  an **`append:icon`** shares the trailing slot with the clear button — the icon
@@ -695,8 +698,10 @@ selectors like `.bb-badge > .bb-badge__content`, which no longer match.
695
698
  - An icon-only badge (`icon`) has no visible text; the default slot is rendered
696
699
  as a screen-reader-only label — never leave it empty. In button mode that
697
700
  label is also the **button's accessible name**, so it is doubly required.
698
- - The `clearable` button has an accessible name from `clearable-label` (or the
699
- localized `badge.clearLabel`).
701
+ - The `clearable` button's accessible name is composed by default: the
702
+ localized `badge.clearLabel` verb plus the badge body's text ("Remove
703
+ Italy"). `clearable-label` replaces the whole name; with a `BbBadgeButton`
704
+ body the composition is skipped (bare verb only).
700
705
  - A clickable **and** clearable badge is two tab stops (body, then clear). That
701
706
  is intended: two actions, two controls.
702
707
  - Keyboard focus on either control rings the whole pill, so the focused target
@@ -76,6 +76,7 @@
76
76
 
77
77
  - `append` — scope: `BaseSelectActivatorSlotProps`
78
78
  - `append-outer` — scope: `BaseSelectOuterSlotProps`
79
+ - `chip` — scope: `BaseSelectChipSlotProps<Item>` — Replaces the default badge rendered for each selection in `multiple` mode.
79
80
  - `footer` — scope: `BaseSelectPanelSlotProps` — Content pinned to the bottom of the panel, below the options list.
80
81
  - `group` — scope: `BaseSelectGroupSlotProps<Item>`
81
82
  - `header` — scope: `BaseSelectPanelSlotProps` — Content pinned to the top of the panel, above the search field.
@@ -1068,10 +1068,12 @@ const role = ref<string | null>(null);
1068
1068
 
1069
1069
  ### Custom option rows
1070
1070
 
1071
- The `#option` slot replaces the default row rendering — use it for avatars,
1072
- secondary text or metadata. It exposes the raw `item`, the resolved `text` and
1073
- `value`, plus `selected` / `focused` / `disabled` / `loading` / `hasErrors`
1074
- flags and the row `index`. Rows are fixed-height (the list is virtualized): if
1071
+ The `#option` slot replaces the row's **text** — use it for avatars, secondary
1072
+ text or metadata. It exposes the raw `item`, the resolved `text` and `value`,
1073
+ plus `selected` / `focused` / `disabled` / `loading` / `hasErrors` flags and the
1074
+ row `index`. The selection check is not yours to redraw: it renders outside the
1075
+ slot, so a custom row keeps it (`selected` is there for rows that want to show
1076
+ the state some _other_ way, not for drawing a second check). Rows are fixed-height (the list is virtualized): if
1075
1077
  your markup is taller than the default 28px (24px compact; 44px / 36px inside
1076
1078
  the adaptive sheet), set `item-height` to match — and `header-height` for
1077
1079
  grouped headers (defaults 32px / 24px compact; 36px / 32px in the sheet). The
@@ -1148,6 +1150,86 @@ const assignee = ref<string | null>(null);
1148
1150
  </script>
1149
1151
  ```
1150
1152
 
1153
+ ### Custom chips
1154
+
1155
+ In `multiple` mode each selection renders as a badge. The `#chip` slot replaces
1156
+ that badge outright — a colored label, an avatar plus a name, anything — and
1157
+ receives `{ item, text, value, disabled, deselect }`. `item` is the raw entry
1158
+ from `items` (where a per-selection color or avatar lives), `disabled` is `true`
1159
+ while the control is `disabled` or `readonly` (render no remove affordance —
1160
+ `deselect` refuses anyway), and `deselect` drops just this value from the model.
1161
+
1162
+ Custom chips need none of the library's class names: the chips row supplies the
1163
+ spacing and keeps them from shrinking. The slot is not called in `comma` mode
1164
+ (there are no chips to replace) nor for the collapsed "+N" summary that appears
1165
+ once the chips outgrow the field — keep chips compact, or raise
1166
+ `max-selected-labels`.
1167
+
1168
+ **Labels as colored chips**
1169
+
1170
+ ```vue
1171
+ <template>
1172
+ <div class="grid max-w-sm gap-3 rounded-[var(--bb-radius)] border p-3">
1173
+ <BbSelect
1174
+ v-model="tags"
1175
+ clearable
1176
+ item-text="label"
1177
+ item-value="value"
1178
+ :items="tagItems"
1179
+ label="Labels"
1180
+ multiple
1181
+ placeholder="Add labels"
1182
+ >
1183
+ <template #option="{ item, text }">
1184
+ <span class="flex w-full items-center gap-2">
1185
+ <span
1186
+ class="size-2 shrink-0 rounded-full"
1187
+ :style="{ background: item.color }"
1188
+ ></span>
1189
+ <span class="min-w-0 flex-1 truncate">{{ text }}</span>
1190
+ </span>
1191
+ </template>
1192
+ <!-- The chip slot replaces the default badge for each selection. It
1193
+ hands over the raw `item` (here: the label's own color), the resolved
1194
+ `text` / `value`, a `disabled` flag that is true while the control is
1195
+ disabled or readonly, and `deselect` — which drops just this value. -->
1196
+ <template #chip="{ item, text, disabled, deselect }">
1197
+ <BbBadge
1198
+ :clearable="!disabled"
1199
+ size="lg"
1200
+ :style="{
1201
+ '--bg': `color-mix(in oklab, ${item.color} 15%, transparent)`,
1202
+ '--color': 'var(--bb-text)',
1203
+ }"
1204
+ @click:clear="deselect"
1205
+ >
1206
+ <span
1207
+ class="size-2 rounded-full"
1208
+ :style="{ background: item.color }"
1209
+ ></span>
1210
+ {{ text }}
1211
+ </BbBadge>
1212
+ </template>
1213
+ </BbSelect>
1214
+ </div>
1215
+ </template>
1216
+ <script setup lang="ts">
1217
+ import { ref } from 'vue';
1218
+ import { BbBadge, BbSelect } from 'bitboss-ui';
1219
+
1220
+ type Label = { label: string; value: string; color: string };
1221
+
1222
+ const tagItems: Label[] = [
1223
+ { label: 'Bug', value: 'bug', color: '#ef4444' },
1224
+ { label: 'Enhancement', value: 'enhancement', color: '#0ea5e9' },
1225
+ { label: 'Documentation', value: 'documentation', color: '#8b5cf6' },
1226
+ { label: 'Good first issue', value: 'good-first-issue', color: '#22c55e' },
1227
+ ];
1228
+
1229
+ const tags = ref<string[]>(['bug']);
1230
+ </script>
1231
+ ```
1232
+
1151
1233
  ### Slots around the input
1152
1234
 
1153
1235
  Beyond the panel slots, the field itself is extensible:
@@ -1212,6 +1294,14 @@ Shipped behavior — never reimplement it:
1212
1294
  library default) — `direction="horizontal"` gives a label-beside-field row.
1213
1295
  Only `floating`/`inside` label modes force the vertical layout, since they
1214
1296
  embed the label in the field itself.
1297
+ - **A long `placeholder` or a long selected label never widens the control's
1298
+ track.** The trigger is a button, not an `<input>`, so the placeholder is
1299
+ real text rather than a paint-only `::placeholder` — but the field's minimum
1300
+ size is capped so that text cannot become the floor of a grid or flex track.
1301
+ Put a select with a paragraph-length placeholder in
1302
+ `grid-template-columns: repeat(3, 1fr)` and you still get three equal
1303
+ columns, with the text ellipsised inside its field. You do **not** need
1304
+ `min-w-0` on the item or `minmax(0, 1fr)` on the track to get this.
1215
1305
 
1216
1306
  ### Adaptive mobile sheet
1217
1307
 
@@ -1416,6 +1506,7 @@ Platform notes:
1416
1506
 
1417
1507
  - `append` — scope: `{ query: string; focus: () => void; close: () => Promise<void>; }` — Content appended inside the input field, after the value area.
1418
1508
  - `append-outer` — scope: `{ query: string; close: () => Promise<void>; }` — Content appended outside the input field wrapper.
1509
+ - `chip` — scope: `BbSelectChipSlotProps<T>` — Replaces the default badge rendered for each selection in `multiple` mode. Not called in `comma` mode, nor for the collapsed "+N" summary.
1419
1510
  - `footer` — scope: `BbSelectPanelSlotProps` — Content pinned to the bottom of the options panel, below the options list. Rendered in both the desktop popover and the adaptive sheet.
1420
1511
  - `group` — scope: `BbSelectGroupSlotProps<T>` — Replaces the default group header rendered above grouped options.
1421
1512
  - `header` — scope: `BbSelectPanelSlotProps` — Content pinned to the top of the options panel, above the search field. Rendered in both the desktop popover and the adaptive sheet.
@@ -216,9 +216,11 @@ const reviewers = ref<string[]>(['ada', 'grace']);
216
216
 
217
217
  ### Custom option rows
218
218
 
219
- The `#option` slot replaces each row — exposing the raw `item`, resolved `text` /
220
- `value`, the row `index`, and `selected` / `focused` / `disabled` / `loading`
221
- flags — so options can carry avatars, badges or secondary metadata. Rows are
219
+ The `#option` slot replaces each row's **text** — exposing the raw `item`,
220
+ resolved `text` / `value`, the row `index`, and `selected` / `focused` /
221
+ `disabled` / `loading` flags — so options can carry avatars, badges or secondary
222
+ metadata. The selection check renders outside the slot and survives any custom
223
+ markup, so there is no need to redraw it from `selected`. Rows are
222
224
  fixed-height and virtualized: if your markup is taller than the default 28px
223
225
  (24px compact; 44px / 36px inside the adaptive sheet), set `item-height` to
224
226
  match — and `header-height` for grouped headers (32px / 24px compact; 36px /
@@ -258,16 +260,12 @@ height wins everywhere.
258
260
  }}</BbBadgeButton>
259
261
  </BbBadge>
260
262
  </template>
261
- <template #option="{ item, text, selected }">
263
+ <!-- The slot replaces the row's TEXT only: the selection check keeps
264
+ rendering outside it, so custom rows never have to redraw it. -->
265
+ <template #option="{ item, text }">
262
266
  <span class="flex w-full items-center gap-2">
263
267
  <BbAvatar :alt="text" size="32">{{ item.initials }}</BbAvatar>
264
268
  <span class="min-w-0 flex-1 truncate">{{ text }}</span>
265
- <BbIcon
266
- v-if="selected"
267
- class="shrink-0 text-[color:var(--bb-text-muted)]"
268
- icon="lucide:check"
269
- :size="14"
270
- />
271
269
  </span>
272
270
  </template>
273
271
  </BbSelectPopover>
@@ -275,13 +273,7 @@ height wins everywhere.
275
273
  </template>
276
274
  <script setup lang="ts">
277
275
  import { computed, ref } from 'vue';
278
- import {
279
- BbAvatar,
280
- BbBadge,
281
- BbBadgeButton,
282
- BbIcon,
283
- BbSelectPopover,
284
- } from 'bitboss-ui';
276
+ import { BbAvatar, BbBadge, BbBadgeButton, BbSelectPopover } from 'bitboss-ui';
285
277
 
286
278
  type Person = { label: string; value: string; initials: string };
287
279