@livestorm/ui-vue 3.0.0 → 4.0.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.
Files changed (291) hide show
  1. package/README.md +3 -3
  2. package/components-vite.js +2 -2
  3. package/dist/UiButton-uyW8EHRJ.mjs +602 -0
  4. package/dist/UiTimelineItem-EiOEi1yF.mjs +7293 -0
  5. package/dist/UiTooltip-DZcPD6_N.mjs +158 -0
  6. package/dist/a11y-CaZxRWzo.mjs +6 -0
  7. package/dist/breakpoints.mjs +4 -9
  8. package/dist/bus.mjs +5 -5
  9. package/dist/components.mjs +236 -231
  10. package/dist/i18n-B8QOMuFD.mjs +46 -0
  11. package/dist/index.mjs +4 -91
  12. package/dist/integrations.mjs +3 -14
  13. package/dist/layouts.mjs +228 -236
  14. package/dist/responsive.mjs +31 -36
  15. package/dist/slot.mjs +11 -17
  16. package/dist/toastDSV2-D3xDZPe5.mjs +105 -0
  17. package/dist/types/index.d.ts +19 -1
  18. package/dist/types/src/components/Toast/ToastMessage.vue.d.ts +17 -7
  19. package/dist/types/src/components/Toast/toastDSV2.d.ts +89 -64
  20. package/dist/types/src/components/UiActionBar/types.d.ts +2 -0
  21. package/dist/types/src/components/UiAvatar/UiAvatar.vue.d.ts +4 -2
  22. package/dist/types/src/components/UiAvatarList/UiAvatarList.vue.d.ts +3 -2
  23. package/dist/types/src/components/UiBanner/UiBanner.vue.d.ts +13 -1
  24. package/dist/types/src/components/UiButton/UiButton.vue.d.ts +95 -35
  25. package/dist/types/src/components/UiButtonGroup/UiButtonGroup.vue.d.ts +8 -2
  26. package/dist/types/src/components/UiButtonGroup/types.d.ts +4 -0
  27. package/dist/types/src/components/UiChoice/UiChoice.vue.d.ts +71 -0
  28. package/dist/types/src/components/UiChoice/index.d.ts +2 -0
  29. package/dist/types/src/components/UiChoice/types.d.ts +7 -0
  30. package/dist/types/src/components/UiColorField/UiColorField.vue.d.ts +1 -1
  31. package/dist/types/src/components/UiColorPicker/UiColorPicker.vue.d.ts +2 -2
  32. package/dist/types/src/components/UiCombobox/UiCombobox.types.d.ts +47 -0
  33. package/dist/types/src/components/UiCombobox/UiCombobox.vue.d.ts +40 -0
  34. package/dist/types/src/components/UiCombobox/index.d.ts +2 -0
  35. package/dist/types/src/components/UiDataTable/UiDataTable.types.d.ts +46 -0
  36. package/dist/types/src/components/UiDataTable/UiDataTable.vue.d.ts +136 -0
  37. package/dist/types/src/components/UiDataTable/index.d.ts +5 -0
  38. package/dist/types/src/components/UiDrawer/UiDrawer.vue.d.ts +28 -0
  39. package/dist/types/src/components/UiDrawer/UiDrawerContent.vue.d.ts +18 -0
  40. package/dist/types/src/components/UiDrawer/UiDrawerTrigger.vue.d.ts +14 -0
  41. package/dist/types/src/components/UiDropdown/DropdownItemCheckbox.vue.d.ts +3 -0
  42. package/dist/types/src/components/UiDropdown/UiDropdownContent.vue.d.ts +10 -0
  43. package/dist/types/src/components/UiDropdown/UiDropdownGroup.vue.d.ts +17 -0
  44. package/dist/types/src/components/UiDropdown/UiDropdownItem.vue.d.ts +8 -5
  45. package/dist/types/src/components/UiDropdown/UiDropdownTrigger.vue.d.ts +21 -5
  46. package/dist/types/src/components/UiDropdown/index.d.ts +1 -0
  47. package/dist/types/src/components/UiEmojiPickerList/UiEmojiPickerList.vue.d.ts +22 -0
  48. package/dist/types/src/components/UiEmptyState/UiEmptyState.vue.d.ts +8 -3
  49. package/dist/types/src/components/UiFieldWrapper/UiFieldWrapper.vue.d.ts +2 -2
  50. package/dist/types/src/components/UiInputCheckbox/CheckboxGlyph.vue.d.ts +17 -0
  51. package/dist/types/src/components/UiInputCheckbox/UiInputCheckbox.vue.d.ts +35 -4
  52. package/dist/types/src/components/UiInputRadio/RadioGlyph.vue.d.ts +19 -0
  53. package/dist/types/src/components/UiInputRadio/UiInputRadio.vue.d.ts +21 -8
  54. package/dist/types/src/components/UiInputRadio/UiInputRadioGroup.vue.d.ts +6 -1
  55. package/dist/types/src/components/UiInputRadio/types.d.ts +1 -1
  56. package/dist/types/src/components/UiInputSelect/UiInputSelect.vue.d.ts +5 -5
  57. package/dist/types/src/components/UiInputTextarea/UiInputTextarea.vue.d.ts +5 -5
  58. package/dist/types/src/components/UiListItem/UiListItem.vue.d.ts +18 -4
  59. package/dist/types/src/components/UiLoader/UiSpinnerIcon.vue.d.ts +3 -0
  60. package/dist/types/src/components/UiMenu/UiMenuContent.vue.d.ts +15 -2
  61. package/dist/types/src/components/UiMenu/UiMenuItem.vue.d.ts +9 -3
  62. package/dist/types/src/components/UiNestedOptions/UiNestedOptions.vue.d.ts +13 -0
  63. package/dist/types/src/components/UiPage/UiPage.vue.d.ts +79 -15
  64. package/dist/types/src/components/UiPagination/UiPagination.vue.d.ts +36 -0
  65. package/dist/types/src/components/UiPagination/index.d.ts +2 -0
  66. package/dist/types/src/components/UiPagination/types.d.ts +4 -0
  67. package/dist/types/src/components/UiPane/UiPane.vue.d.ts +22 -0
  68. package/dist/types/src/components/UiPane/UiPaneContent.vue.d.ts +75 -0
  69. package/dist/types/src/components/UiPane/UiPaneTrigger.vue.d.ts +14 -0
  70. package/dist/types/src/components/UiPane/index.d.ts +5 -0
  71. package/dist/types/src/components/UiRating/UiRating.vue.d.ts +62 -0
  72. package/dist/types/src/components/UiRating/types.d.ts +1 -0
  73. package/dist/types/src/components/UiRoomButton/UiRoomButton.vue.d.ts +5 -3
  74. package/dist/types/src/components/UiRoomCountdown/UiRoomCountdown.vue.d.ts +32 -0
  75. package/dist/types/src/components/UiShell/UiShell.vue.d.ts +3 -0
  76. package/dist/types/src/components/UiSideMenu/UiSideMenu.vue.d.ts +22 -1
  77. package/dist/types/src/components/UiSideMenu/UiSideMenuItemContent.vue.d.ts +2 -2
  78. package/dist/types/src/components/UiSwitch/UiSwitch.vue.d.ts +10 -5
  79. package/dist/types/src/components/UiTable/UiTable.vue.d.ts +13 -4
  80. package/dist/types/src/components/UiTable/UiTableActionsCell.vue.d.ts +49 -0
  81. package/dist/types/src/components/UiTable/UiTableActionsHeaderCell.vue.d.ts +3 -0
  82. package/dist/types/src/components/UiTable/UiTableBody.vue.d.ts +11 -1
  83. package/dist/types/src/components/UiTable/UiTableCell.vue.d.ts +16 -1
  84. package/dist/types/src/components/UiTable/UiTableHeaderCell.vue.d.ts +31 -2
  85. package/dist/types/src/components/UiTable/UiTableRow.vue.d.ts +32 -2
  86. package/dist/types/src/components/UiTable/context.d.ts +3 -0
  87. package/dist/types/src/components/UiTable/index.d.ts +4 -0
  88. package/dist/types/src/components/UiTabs/UiTabs.vue.d.ts +3 -0
  89. package/dist/types/src/components/UiTag/UiTag.types.d.ts +25 -0
  90. package/dist/types/src/components/UiTag/UiTag.vue.d.ts +5 -26
  91. package/dist/types/src/components/UiTag/index.d.ts +2 -1
  92. package/dist/types/src/components/UiTextField/UiTextField.vue.d.ts +10 -5
  93. package/dist/types/src/components/UiTooltip/UiTooltipProvider.vue.d.ts +1 -0
  94. package/dist/types/src/components/UiTooltip/UiTooltipTrigger.vue.d.ts +2 -2
  95. package/dist/types/src/composables/storedThemingApplies.d.ts +17 -0
  96. package/dist/types/src/composables/useButtonStyles.d.ts +1 -0
  97. package/dist/types/src/composables/useChoiceboxSurface.d.ts +11 -0
  98. package/dist/types/src/composables/useContentMode.d.ts +7 -9
  99. package/dist/types/src/composables/useControlModes.d.ts +7 -0
  100. package/dist/types/src/composables/useControlShape.d.ts +24 -0
  101. package/dist/types/src/composables/useFieldFocusEvents.d.ts +12 -0
  102. package/dist/types/src/composables/useIconSize.d.ts +2 -0
  103. package/dist/types/src/composables/usePullGesture.d.ts +42 -0
  104. package/dist/types/src/composables/useThemeAndModes.d.ts +4 -0
  105. package/dist/types/src/theming/colorScale.d.ts +165 -0
  106. package/dist/types/src/theming/legacyColorScale.d.ts +27 -0
  107. package/dist/types/src/theming/presetColors.d.ts +11 -0
  108. package/dist/types/src/theming/primitiveTokens.d.ts +2 -0
  109. package/dist/types/src/utils/effectiveOpacity.d.ts +2 -0
  110. package/dist/types/src/utils/numbers.d.ts +1 -2
  111. package/dist/types/src/utils/tokenColor.d.ts +2 -0
  112. package/dist/ui-vue.css +2 -1
  113. package/dist/utils.mjs +165 -194
  114. package/index.d.ts +18 -0
  115. package/index.js +36 -0
  116. package/package.json +26 -21
  117. package/src/components/Toast/LayoutToasts.vue +19 -11
  118. package/src/components/Toast/ToastMessage.vue +125 -68
  119. package/src/components/Toast/toastDSV2.ts +238 -67
  120. package/src/components/UiActionBar/UiActionBar.vue +4 -3
  121. package/src/components/UiActionBar/UiActionBarItem.vue +9 -6
  122. package/src/components/UiActionBar/types.ts +13 -0
  123. package/src/components/UiAvatar/UiAvatar.vue +8 -2
  124. package/src/components/UiAvatarList/UiAvatarList.vue +51 -20
  125. package/src/components/UiBadge/UiBadge.vue +1 -1
  126. package/src/components/UiBanner/UiBanner.vue +71 -21
  127. package/src/components/UiBreadcrumb/UiBreadcrumb.vue +1 -1
  128. package/src/components/UiButton/UiButton.vue +92 -25
  129. package/src/components/UiButtonGroup/UiButtonGroup.vue +21 -17
  130. package/src/components/UiButtonGroup/UiButtonGroupItem.vue +33 -15
  131. package/src/components/UiButtonGroup/types.ts +19 -0
  132. package/src/components/UiCard/UiCard.vue +20 -23
  133. package/src/components/UiChoice/UiChoice.vue +187 -0
  134. package/src/components/UiChoice/index.ts +2 -0
  135. package/src/components/UiChoice/types.ts +11 -0
  136. package/src/components/UiColorField/UiColorField.vue +2 -2
  137. package/src/components/UiColorPicker/UiColorPicker.vue +11 -8
  138. package/src/components/UiCombobox/UiCombobox.types.ts +52 -0
  139. package/src/components/UiCombobox/UiCombobox.vue +296 -0
  140. package/src/components/UiCombobox/index.ts +2 -0
  141. package/src/components/UiDataTable/UiDataTable.types.ts +47 -0
  142. package/src/components/UiDataTable/UiDataTable.vue +422 -0
  143. package/src/components/UiDataTable/index.ts +12 -0
  144. package/src/components/UiDialog/UiDialogContent.vue +22 -7
  145. package/src/components/UiDrawer/UiDrawer.vue +16 -0
  146. package/src/components/UiDrawer/UiDrawerContent.vue +42 -0
  147. package/src/components/UiDrawer/UiDrawerTrigger.vue +17 -0
  148. package/src/components/UiDropdown/DropdownItemCheckbox.vue +10 -0
  149. package/src/components/UiDropdown/UiDropdownContent.vue +32 -21
  150. package/src/components/UiDropdown/UiDropdownGroup.vue +23 -0
  151. package/src/components/UiDropdown/UiDropdownItem.vue +38 -18
  152. package/src/components/UiDropdown/UiDropdownSeparator.vue +1 -1
  153. package/src/components/UiDropdown/UiDropdownTrigger.vue +138 -43
  154. package/src/components/UiDropdown/index.ts +1 -0
  155. package/src/components/UiEmojiPickerList/UiEmojiPickerList.vue +122 -0
  156. package/src/components/UiEmptyState/UiEmptyState.vue +16 -4
  157. package/src/components/UiFieldWrapper/UiFieldWrapper.vue +9 -3
  158. package/src/components/UiHorizontalSeparator/UiHorizontalSeparator.vue +2 -2
  159. package/src/components/UiInputCheckbox/CheckboxGlyph.vue +78 -0
  160. package/src/components/UiInputCheckbox/UiInputCheckbox.vue +213 -177
  161. package/src/components/UiInputEmoji/UiInputEmoji.vue +18 -95
  162. package/src/components/UiInputRadio/RadioGlyph.vue +45 -0
  163. package/src/components/UiInputRadio/UiInputRadio.vue +177 -169
  164. package/src/components/UiInputRadio/UiInputRadioGroup.vue +15 -5
  165. package/src/components/UiInputRadio/types.ts +1 -2
  166. package/src/components/UiInputSelect/UiInputSelect.vue +9 -7
  167. package/src/components/UiInputTextarea/UiInputTextarea.vue +20 -35
  168. package/src/components/UiLabel/UiLabel.vue +1 -1
  169. package/src/components/UiListItem/UiListItem.vue +55 -9
  170. package/src/components/UiLoader/UiLoader.vue +4 -20
  171. package/src/components/UiLoader/UiSpinnerIcon.vue +21 -0
  172. package/src/components/UiMenu/UiMenuContent.vue +24 -3
  173. package/src/components/UiMenu/UiMenuGroup.vue +1 -1
  174. package/src/components/UiMenu/UiMenuItem.vue +14 -3
  175. package/src/components/UiMenu/UiMenuSubContent.vue +1 -1
  176. package/src/components/UiMenu/UiMenuSubTrigger.vue +1 -1
  177. package/src/components/UiNestedOptions/UiNestedOptions.vue +6 -0
  178. package/src/components/UiPage/UiPage.vue +190 -32
  179. package/src/components/UiPagination/UiPagination.vue +99 -0
  180. package/src/components/UiPagination/index.ts +2 -0
  181. package/src/components/UiPagination/types.ts +28 -0
  182. package/src/components/UiPane/UiPane.vue +20 -0
  183. package/src/components/UiPane/UiPaneContent.vue +232 -0
  184. package/src/components/UiPane/UiPaneTrigger.vue +14 -0
  185. package/src/components/UiPane/index.ts +12 -0
  186. package/src/components/UiPopover/UiPopoverContent.vue +21 -1
  187. package/src/components/UiProgress/UiProgress.vue +2 -2
  188. package/src/components/UiRating/UiRating.vue +258 -0
  189. package/src/components/UiRating/types.ts +1 -0
  190. package/src/components/UiRoomButton/UiRoomButton.vue +10 -41
  191. package/src/components/UiRoomCountdown/UiRoomCountdown.vue +376 -0
  192. package/src/components/UiShell/UiShell.vue +9 -3
  193. package/src/components/UiSideMenu/UiSideMenu.vue +155 -18
  194. package/src/components/UiSideMenu/UiSideMenuItemContent.vue +20 -16
  195. package/src/components/UiStepper/UiStepper.vue +3 -16
  196. package/src/components/UiStepper/UiStepperDescription.vue +1 -13
  197. package/src/components/UiStepper/UiStepperIndicator.vue +11 -48
  198. package/src/components/UiStepper/UiStepperItem.vue +1 -8
  199. package/src/components/UiStepper/UiStepperSeparator.vue +5 -16
  200. package/src/components/UiStepper/UiStepperTitle.vue +1 -10
  201. package/src/components/UiSwitch/UiSwitch.vue +44 -28
  202. package/src/components/UiTable/UiTable.vue +98 -8
  203. package/src/components/UiTable/UiTableActionsCell.vue +135 -0
  204. package/src/components/UiTable/UiTableActionsHeaderCell.vue +19 -0
  205. package/src/components/UiTable/UiTableBody.vue +16 -1
  206. package/src/components/UiTable/UiTableCell.vue +33 -16
  207. package/src/components/UiTable/UiTableHeader.vue +6 -5
  208. package/src/components/UiTable/UiTableHeaderCell.vue +61 -9
  209. package/src/components/UiTable/UiTableRow.vue +46 -10
  210. package/src/components/UiTable/context.ts +5 -0
  211. package/src/components/UiTable/index.ts +4 -0
  212. package/src/components/UiTabs/UiTabs.vue +56 -61
  213. package/src/components/UiTag/UiTag.types.ts +28 -0
  214. package/src/components/UiTag/UiTag.vue +24 -42
  215. package/src/components/UiTag/index.ts +2 -0
  216. package/src/components/UiTextField/UiTextField.vue +21 -36
  217. package/src/components/UiTooltip/UiTooltipContent.vue +1 -1
  218. package/src/components/UiTooltip/UiTooltipProvider.vue +3 -0
  219. package/src/components/UiTooltip/UiTooltipTrigger.vue +16 -1
  220. package/src/composables/storedThemingApplies.ts +19 -0
  221. package/src/composables/useButtonStyles.ts +9 -13
  222. package/src/composables/useChoiceboxSurface.ts +36 -0
  223. package/src/composables/useContentMode.ts +29 -16
  224. package/src/composables/useControlModes.ts +11 -0
  225. package/src/composables/useControlShape.ts +34 -0
  226. package/src/composables/useFieldFocusEvents.ts +30 -0
  227. package/src/composables/useIconSize.ts +15 -0
  228. package/src/composables/usePullGesture.ts +142 -0
  229. package/src/composables/useThemeAndModes.ts +28 -1
  230. package/src/css/body.css +16 -1
  231. package/src/css/fonts.css +39 -5
  232. package/src/global.d.ts +36 -0
  233. package/src/theming/colorScale.ts +173 -0
  234. package/src/theming/legacyColorScale.ts +58 -0
  235. package/src/theming/presetColors.ts +33 -0
  236. package/src/theming/primitiveTokens.ts +71 -0
  237. package/src/utils/effectiveOpacity.ts +8 -0
  238. package/src/utils/i18n.ts +6 -0
  239. package/src/utils/numbers.ts +1 -1
  240. package/src/utils/responsive.d.ts +14 -0
  241. package/src/utils/tokenColor.ts +9 -0
  242. package/dist/UiTimelineItem.vue_vue_type_style_index_0_lang-LpdvGf5X.mjs +0 -6093
  243. package/dist/a11y-BhF_nl7L.mjs +0 -159
  244. package/dist/i18n-Cktw9hIu.mjs +0 -49
  245. package/dist/toastDSV2-wMh-UMzW.mjs +0 -74
  246. package/dist/types/src/components/UiAvatar/index.d.ts +0 -1
  247. package/dist/types/src/components/UiAvatarList/index.d.ts +0 -1
  248. package/dist/types/src/components/UiBadge/index.d.ts +0 -1
  249. package/dist/types/src/components/UiBanner/index.d.ts +0 -2
  250. package/dist/types/src/components/UiButtonGroup/index.d.ts +0 -3
  251. package/dist/types/src/components/UiCarousel/index.d.ts +0 -2
  252. package/dist/types/src/components/UiColorField/index.d.ts +0 -2
  253. package/dist/types/src/components/UiDialog/MarketplaceHeaderExample.vue.d.ts +0 -28
  254. package/dist/types/src/components/UiDialog/index.d.ts +0 -7
  255. package/dist/types/src/components/UiFieldWrapper/index.d.ts +0 -1
  256. package/dist/types/src/components/UiInputCheckbox/index.d.ts +0 -2
  257. package/dist/types/src/components/UiInputEmoji/index.d.ts +0 -1
  258. package/dist/types/src/components/UiInputRadio/index.d.ts +0 -3
  259. package/dist/types/src/components/UiInputSelect/index.d.ts +0 -2
  260. package/dist/types/src/components/UiInputTextarea/index.d.ts +0 -2
  261. package/dist/types/src/components/UiMenu/index.d.ts +0 -10
  262. package/dist/types/src/components/UiProgress/index.d.ts +0 -2
  263. package/dist/types/src/components/UiSwitch/index.d.ts +0 -2
  264. package/dist/types/src/components/UiTabs/index.d.ts +0 -2
  265. package/dist/types/src/components/UiTimeline/index.d.ts +0 -2
  266. package/dist/types/src/components/UiTooltip/index.d.ts +0 -4
  267. package/dist/types/src/mixins/DynamicAction.d.ts +0 -30
  268. package/dist/useThemeAndModes-BzYwSOEv.mjs +0 -108
  269. package/src/components/UiAvatar/index.js +0 -1
  270. package/src/components/UiAvatarList/index.js +0 -1
  271. package/src/components/UiBadge/index.ts +0 -2
  272. package/src/components/UiBanner/index.ts +0 -2
  273. package/src/components/UiButtonGroup/index.ts +0 -3
  274. package/src/components/UiCarousel/index.ts +0 -2
  275. package/src/components/UiColorField/index.ts +0 -2
  276. package/src/components/UiDialog/MarketplaceHeaderExample.vue +0 -217
  277. package/src/components/UiDialog/index.ts +0 -18
  278. package/src/components/UiFieldWrapper/index.ts +0 -1
  279. package/src/components/UiInputCheckbox/index.ts +0 -2
  280. package/src/components/UiInputEmoji/index.ts +0 -1
  281. package/src/components/UiInputRadio/index.ts +0 -3
  282. package/src/components/UiInputSelect/index.ts +0 -2
  283. package/src/components/UiInputTextarea/index.ts +0 -2
  284. package/src/components/UiMenu/index.ts +0 -10
  285. package/src/components/UiProgress/index.ts +0 -2
  286. package/src/components/UiSwitch/index.ts +0 -2
  287. package/src/components/UiTabs/index.ts +0 -5
  288. package/src/components/UiTag/index.js +0 -1
  289. package/src/components/UiTimeline/index.ts +0 -2
  290. package/src/components/UiTooltip/index.js +0 -4
  291. package/src/mixins/DynamicAction.js +0 -61
@@ -5,7 +5,7 @@ const props = defineProps<{
5
5
  </script>
6
6
 
7
7
  <template>
8
- <div class="separator text-content-legends-medium-md">
8
+ <div class="separator text-content-legends-medium-md uppercase">
9
9
  <p
10
10
  class="w-full"
11
11
  v-text="props.label"
@@ -23,6 +23,6 @@ const props = defineProps<{
23
23
  .separator::before,
24
24
  .separator::after {
25
25
  content: '';
26
- @apply border-t border-borders-neutral-light h-0 w-full;
26
+ @apply border-t border-container-border h-0 w-full;
27
27
  }
28
28
  </style>
@@ -0,0 +1,78 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import type { CheckboxSize } from './types'
4
+
5
+ interface Props {
6
+ checked?: boolean
7
+ indeterminate?: boolean
8
+ error?: boolean
9
+ disabled?: boolean
10
+ size?: CheckboxSize
11
+ }
12
+
13
+ const props = withDefaults(defineProps<Props>(), {
14
+ checked: false,
15
+ indeterminate: false,
16
+ error: false,
17
+ disabled: false,
18
+ size: 'md',
19
+ })
20
+
21
+ const checkedFillClass = computed(() => {
22
+ if (props.error) {
23
+ return 'bg-actions-danger-idle'
24
+ }
25
+ return props.disabled ? 'bg-surface-neutral-alpha-600' : 'bg-actions-primary-idle'
26
+ })
27
+
28
+ const boxClasses = computed(() => {
29
+ const classes = [
30
+ 'relative shrink-0 grid place-content-center',
31
+ props.disabled ? 'cursor-not-allowed' : 'cursor-pointer',
32
+ ]
33
+
34
+ const sizeClasses = {
35
+ sm: 'size-unit-0-75rem rounded-4',
36
+ md: 'size-check-sm rounded-check',
37
+ lg: 'size-check-md rounded-check',
38
+ }
39
+ classes.push(sizeClasses[props.size])
40
+
41
+ // Checked is a plain fill, neutral once disabled; unchecked is a denser fill than the
42
+ // fields, which once disabled falls back to the field fill with an outline
43
+ if (props.checked || props.indeterminate) {
44
+ classes.push('border-none', checkedFillClass.value)
45
+ } else if (props.disabled && !props.error) {
46
+ classes.push('bg-control-field-bg border border-control-disabled-border')
47
+ } else {
48
+ classes.push('bg-control-check-bg border', props.error ? 'border-control-error-border' : 'border-control-field-border')
49
+ if (!props.disabled) {
50
+ classes.push('group-hover/option:bg-control-check-bg-hover')
51
+ }
52
+ }
53
+
54
+ return classes
55
+ })
56
+
57
+ const iconClasses = computed(() => {
58
+ const sizeClasses = {
59
+ sm: 'size-2',
60
+ md: 'size-2.5',
61
+ lg: 'size-3',
62
+ }
63
+ return ['block relative z-1 text-actions-neutral-invert-idle', sizeClasses[props.size], props.indeterminate ? 'i-ph-minus' : 'i-ls-checkbox-check']
64
+ })
65
+ </script>
66
+
67
+ <template>
68
+ <span
69
+ :class="boxClasses"
70
+ aria-hidden="true"
71
+ data-testid="ui-checkbox-box"
72
+ >
73
+ <span
74
+ v-if="checked || indeterminate"
75
+ :class="iconClasses"
76
+ />
77
+ </span>
78
+ </template>
@@ -1,8 +1,11 @@
1
1
  <script setup lang="ts">
2
- import { computed, ref, watch, nextTick } from 'vue'
2
+ import { computed, inject, ref, useSlots, watch, nextTick } from 'vue'
3
3
  import { Label, useId } from 'reka-ui'
4
4
  import type { CheckboxSize } from './types'
5
5
  import UiTooltip from '../UiTooltip/UiTooltip.vue'
6
+ import CheckboxGlyph from './CheckboxGlyph.vue'
7
+ import { CHOICE_ROW_KEY } from '../UiChoice/types'
8
+ import type { ControlPosition } from '../UiChoice/types'
6
9
 
7
10
  interface UiInputCheckboxProps {
8
11
  /**
@@ -45,6 +48,11 @@ interface UiInputCheckboxProps {
45
48
  * Info tooltip for the label
46
49
  */
47
50
  labelInfo?: string
51
+ /**
52
+ * Icon class rendered between the checkbox and the text (e.g. `i-ph-bell`).
53
+ * In vertical orientation it sits above the text instead.
54
+ */
55
+ icon?: string
48
56
  /**
49
57
  * Description text below the label
50
58
  */
@@ -66,6 +74,10 @@ interface UiInputCheckboxProps {
66
74
  * @default 'md'
67
75
  */
68
76
  size?: CheckboxSize
77
+ /**
78
+ * Accessible name for the input, for checkboxes rendered without a label
79
+ */
80
+ ariaLabel?: string
69
81
  /**
70
82
  * Layout orientation
71
83
  * @default 'horizontal'
@@ -76,6 +88,17 @@ interface UiInputCheckboxProps {
76
88
  * @default false
77
89
  */
78
90
  showBg?: boolean
91
+ /**
92
+ * Whether the checkbox stretches to fill its container instead of
93
+ * shrink-wrapping its content
94
+ * @default false
95
+ */
96
+ fullWidth?: boolean
97
+ /**
98
+ * Which side of the text the checkbox sits on, in horizontal orientation
99
+ * @default 'start'
100
+ */
101
+ controlPosition?: ControlPosition
79
102
  }
80
103
 
81
104
  const props = withDefaults(defineProps<UiInputCheckboxProps>(), {
@@ -83,9 +106,13 @@ const props = withDefaults(defineProps<UiInputCheckboxProps>(), {
83
106
  indeterminate: false,
84
107
  error: false,
85
108
  required: false,
109
+ icon: undefined,
86
110
  size: 'md',
87
111
  checked: undefined,
88
112
  orientation: 'horizontal',
113
+ ariaLabel: undefined,
114
+ fullWidth: false,
115
+ controlPosition: 'start',
89
116
  })
90
117
 
91
118
  const emit = defineEmits<{
@@ -93,17 +120,23 @@ const emit = defineEmits<{
93
120
  'change': [event: Event]
94
121
  'focus': [event: FocusEvent]
95
122
  'blur': [event: FocusEvent]
123
+ 'tooltip-clicked': []
96
124
  }>()
97
125
 
126
+ const slots = useSlots()
127
+ const inChoiceRow = inject(CHOICE_ROW_KEY, false)
128
+
98
129
  const checkboxRef = ref<HTMLInputElement | null>(null)
99
130
 
100
- function triggerCheckbox () {
101
- if (!props.disabled) {
131
+ function triggerCheckbox (event: Event) {
132
+ // A click on the input itself bubbles up to this same handler; without the target
133
+ // check we'd re-click it and immediately toggle it back.
134
+ if (!props.disabled && event.target !== checkboxRef.value) {
102
135
  checkboxRef.value?.click()
103
136
  }
104
137
  }
105
- const isFocused = ref(false)
106
- const inputId = useId()
138
+ const generatedId = useId()
139
+ const inputId = computed(() => props.id ?? generatedId)
107
140
 
108
141
  // Determine if checkbox is checked
109
142
  const isChecked = computed(() => {
@@ -130,82 +163,68 @@ const isChecked = computed(() => {
130
163
  return false
131
164
  })
132
165
 
133
- const checkboxClasses = computed(() => {
134
- const baseClasses = [
135
- 'appearance-none',
136
- 'relative shrink-0 grid place-content-center',
137
- 'border rounded-sm transition-colors',
138
- 'focus:outline-hidden',
139
- ]
140
-
141
- baseClasses.push(props.disabled ? 'cursor-not-allowed' : 'cursor-pointer')
142
-
143
- // Size classes
144
- const sizeClasses = {
145
- sm: 'w-3.5 h-3.5',
146
- md: 'w-4 h-4',
147
- lg: 'w-5 h-5',
148
- }
166
+ const hasText = computed(() => !!(props.label || slots.label || props.labelInfo || props.description))
167
+ const hasContent = computed(() => hasText.value || !!(props.icon || slots.icon))
168
+ const isOptionRow = computed(() => props.orientation === 'horizontal' && !props.showBg && hasContent.value)
149
169
 
150
- baseClasses.push(sizeClasses[props.size])
170
+ const controlAtEnd = computed(() => props.orientation === 'horizontal' && props.controlPosition === 'end')
151
171
 
152
- // Background and border based on state
172
+ // The showBg tile and the UiChoice row tint their text with the selection.
173
+ // Disabled, a UiChoice row recedes whole, while elsewhere the selection stays legible in neutral.
174
+ const tone = computed(() => {
153
175
  if (props.error) {
154
- baseClasses.push('border-borders-danger-strong')
155
- if (isChecked.value || props.indeterminate) {
156
- baseClasses.push('bg-actions-danger-idle')
157
- } else {
158
- baseClasses.push('bg-surface-neutral-bg-main')
159
- }
160
- } else if (isChecked.value || props.indeterminate) {
161
- baseClasses.push('bg-actions-primary-idle border-borders-primary-default')
162
- } else {
163
- baseClasses.push('bg-surface-neutral-bg-main border-borders-neutral-strong')
176
+ return 'danger'
164
177
  }
165
-
166
- if (isFocused.value && !props.disabled) {
167
- baseClasses.push('ring')
168
- if (isChecked.value && !props.error) {
169
- baseClasses.push('ring-borders-primary-strong')
170
- } else if (props.error) {
171
- baseClasses.push('ring-borders-danger-strong')
172
- } else {
173
- baseClasses.push('ring-2 ring-inset ring-focus-ring')
174
- }
175
- }
176
-
177
- return baseClasses
178
- })
179
-
180
- const iconClasses = computed(() => {
181
- const baseClasses = ['transition-opacity']
182
-
183
- // Size classes for icon
184
- const sizeClasses = {
185
- sm: 'size-2.5',
186
- md: 'size-3',
187
- lg: 'size-3.5',
178
+ if (props.disabled) {
179
+ return (isChecked.value || props.indeterminate) && !inChoiceRow ? 'neutral' : 'disabled'
188
180
  }
189
-
190
- baseClasses.push(sizeClasses[props.size])
191
-
192
- // Icon type and color based on state
193
- if (props.indeterminate) {
194
- baseClasses.push('i-ph-minus text-actions-neutral-invert-idle opacity-100')
195
- } else if (isChecked.value) {
196
- // Checked: always visible with light color on primary/error background
197
- baseClasses.push('i-ls-checkbox-check text-actions-neutral-invert-idle opacity-100')
198
- } else if (props.error) {
199
- // Unchecked + error: show on hover only with danger color
200
- baseClasses.push('i-ls-checkbox-check text-actions-danger-idle opacity-0 group-hover:opacity-50')
201
- } else {
202
- // Unchecked + no error: show on hover only with neutral color
203
- baseClasses.push('i-ls-checkbox-check text-actions-neutral-idle opacity-0 group-hover:opacity-50')
181
+ if (isChecked.value && inChoiceRow) {
182
+ return 'selected'
204
183
  }
205
-
206
- return baseClasses
184
+ return isChecked.value && props.showBg ? 'primary' : 'neutral'
207
185
  })
208
186
 
187
+ const glyphProps = computed(() => ({
188
+ checked: isChecked.value,
189
+ indeterminate: props.indeterminate,
190
+ error: props.error,
191
+ disabled: props.disabled,
192
+ size: props.size,
193
+ }))
194
+
195
+ const hoverOverlayClasses = computed(() => ({
196
+ 'bg-actions-neutral-hover-overlay': !isChecked.value && !props.indeterminate && !props.error,
197
+ 'bg-actions-danger-hover-overlay': props.error,
198
+ 'bg-actions-primary-hover-overlay': (isChecked.value || props.indeterminate) && !props.error,
199
+ }))
200
+
201
+ // Shared text-colour state used by the label and its icon
202
+ const stateTextClasses = computed(() => ({
203
+ 'text-text-neutral-base': tone.value === 'neutral',
204
+ 'text-text-neutral-tertiary': tone.value === 'disabled',
205
+ 'text-text-danger-base': tone.value === 'danger',
206
+ 'text-text-primary-base': tone.value === 'primary',
207
+ 'text-text-primary-title': tone.value === 'selected',
208
+ }))
209
+
210
+ // Same tertiary tones as the UiInputRadio info icon
211
+ const infoIconClasses = computed(() => ({
212
+ 'text-text-neutral-tertiary': tone.value === 'neutral' || tone.value === 'disabled',
213
+ 'text-text-danger-tertiary': tone.value === 'danger',
214
+ 'text-text-primary-tertiary': tone.value === 'primary' || tone.value === 'selected',
215
+ }))
216
+
217
+ const descriptionClasses = computed(() => ({
218
+ 'text-center': props.orientation === 'vertical',
219
+ 'text-text-neutral-secondary': tone.value === 'neutral',
220
+ 'text-text-neutral-tertiary': tone.value === 'disabled',
221
+ 'text-text-danger-tertiary': tone.value === 'danger',
222
+ 'text-text-primary-tertiary': tone.value === 'primary',
223
+ 'text-text-primary-base': tone.value === 'selected',
224
+ 'cursor-not-allowed': props.disabled,
225
+ 'cursor-pointer': !props.disabled,
226
+ }))
227
+
209
228
  const displayMessage = computed(() => {
210
229
  if (props.error && props.errorMessage) {
211
230
  return props.errorMessage
@@ -213,12 +232,19 @@ const displayMessage = computed(() => {
213
232
  return undefined
214
233
  })
215
234
 
235
+ const descriptionId = computed(() => props.description ? `${inputId.value}-description` : undefined)
236
+
237
+ const ariaDescribedby = computed(() => {
238
+ const ids = [descriptionId.value, displayMessage.value ? `${inputId.value}-message` : undefined].filter(Boolean)
239
+ return ids.length ? ids.join(' ') : undefined
240
+ })
241
+
216
242
  // for error/helper message
217
243
  const displayMessageClasses = computed(() => {
218
- const baseClasses = ['text-content-legends-bold-md mt-1']
244
+ const baseClasses = ['flex items-center gap-1 text-content-legends-bold-md mt-1']
219
245
 
220
246
  if (props.error) {
221
- baseClasses.push('text-text-danger-secondary')
247
+ baseClasses.push('text-text-danger-body')
222
248
  } else {
223
249
  baseClasses.push('text-text-neutral-secondary')
224
250
  }
@@ -227,29 +253,37 @@ const displayMessageClasses = computed(() => {
227
253
  })
228
254
 
229
255
  const innerWrapperClasses = computed(() => {
230
- const classes: string[] = ['group relative rounded-lg flex flex-col'];
256
+ const classes: string[] = ['group/option relative flex flex-col'];
231
257
 
232
258
  if (props.orientation === 'vertical') {
233
259
  classes.push('items-center');
234
260
  }
235
261
 
236
- if (props.disabled) {
237
- classes.push('hover:cursor-not-allowed opacity-50')
262
+ // The row bleeds around the option without taking layout space and carries the
263
+ // focus ring; hover shows on the glyph only. Inside UiChoice the choice draws the row.
264
+ if (inChoiceRow) {
265
+ classes.push('rounded-field-md')
266
+ } else if (isOptionRow.value && !props.showBg) {
267
+ classes.push('isolate before:absolute before:-inset-x-3 before:-inset-y-2 before:-z-10 before:rounded-field-md before:pointer-events-none has-[input:focus-visible]:before:focus-ring')
238
268
  } else {
239
- classes.push('hover:cursor-pointer')
269
+ classes.push('rounded-field-md has-[input:focus-visible]:focus-ring')
240
270
  }
241
271
 
272
+ classes.push(props.disabled ? 'hover:cursor-not-allowed' : 'hover:cursor-pointer')
273
+
242
274
  if (props.showBg) {
243
275
  classes.push('p-2 ring-inset');
244
- if (isChecked.value && !props.error) {
276
+ if (isChecked.value && !props.error && props.disabled) {
277
+ classes.push('bg-control-field-bg ring-1 ring-borders-neutral-strong');
278
+ } else if (isChecked.value && !props.error) {
245
279
  // checked
246
- classes.push('bg-actions-primary-idle-alpha-light ring-2 ring-borders-primary-strong');
280
+ classes.push('bg-actions-primary-idle-alpha-light ring-2 ring-actions-primary-idle');
247
281
  } else if (!isChecked.value && !props.error) {
248
282
  // idle
249
283
  classes.push('bg-surface-neutral-alpha-100 ring-transparent');
250
284
  } else if (isChecked.value && props.error) {
251
285
  // error + checked
252
- classes.push('bg-actions-danger-idle-alpha-light ring-2 ring-borders-danger-strong');
286
+ classes.push('bg-actions-danger-idle-alpha-light ring-2 ring-actions-danger-idle');
253
287
  } else {
254
288
  // error + unchecked
255
289
  classes.push('bg-actions-danger-idle-alpha-light')
@@ -297,12 +331,10 @@ function handleChange (event: Event) {
297
331
  }
298
332
 
299
333
  function handleFocus(event: FocusEvent) {
300
- isFocused.value = true
301
334
  emit('focus', event)
302
335
  }
303
336
 
304
337
  function handleBlur (event: FocusEvent) {
305
- isFocused.value = false
306
338
  emit('blur', event)
307
339
  }
308
340
 
@@ -322,9 +354,14 @@ defineExpose({
322
354
  </script>
323
355
 
324
356
  <template>
325
- <div class="ui-input-checkbox-wrapper w-fit">
357
+ <div
358
+ class="ui-input-checkbox-wrapper"
359
+ data-testid="ui-input-checkbox"
360
+ :class="fullWidth ? 'w-full' : 'w-fit'"
361
+ >
326
362
  <div
327
363
  :class="innerWrapperClasses"
364
+ data-testid="ui-input-checkbox-row"
328
365
  @click="triggerCheckbox"
329
366
  >
330
367
  <input
@@ -335,94 +372,108 @@ defineExpose({
335
372
  :name="name"
336
373
  type="checkbox"
337
374
  class="absolute opacity-0 pointer-events-none"
375
+ :aria-label="ariaLabel"
338
376
  :aria-invalid="error"
339
- :aria-describedby="displayMessage ? `${inputId}-message` : undefined"
377
+ :aria-describedby="ariaDescribedby"
340
378
  @change="handleChange"
341
379
  @focus="handleFocus"
342
380
  @blur="handleBlur"
343
381
  >
344
382
 
383
+ <!-- Icon - vertical only, stacked above the text -->
384
+ <span
385
+ v-if="(icon || $slots.icon) && orientation === 'vertical'"
386
+ class="flex items-center justify-center shrink-0 mb-2"
387
+ data-testid="ui-input-checkbox-icon"
388
+ >
389
+ <slot name="icon">
390
+ <span
391
+ class="size-4"
392
+ :class="[icon, stateTextClasses]"
393
+ />
394
+ </slot>
395
+ </span>
396
+
345
397
  <!-- First row (contains checkbox for horizontal orientation) -->
346
398
  <div
347
- class="flex items-center"
348
- :class="{ 'justify-center': orientation === 'vertical' }"
399
+ class="flex"
400
+ :class="orientation === 'vertical' ? 'items-center justify-center' : 'items-start'"
349
401
  >
350
402
  <!-- Checkbox - horizontal only -->
351
403
  <label
352
404
  v-if="orientation === 'horizontal'"
353
- class="flex w-5 items-center justify-center shrink-0 mr-2"
354
- :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
405
+ class="flex size-5 items-center shrink-0"
406
+ :class="[
407
+ disabled ? 'cursor-not-allowed' : 'cursor-pointer',
408
+ controlAtEnd
409
+ ? ['justify-end', { 'order-last': hasContent, 'ms-3': hasContent && !inChoiceRow, 'ms-5': hasContent && inChoiceRow }]
410
+ : ['justify-start', { 'me-2': hasContent && showBg, 'me-1': hasContent && !showBg && !inChoiceRow, 'me-3': hasContent && !showBg && inChoiceRow }],
411
+ ]"
355
412
  >
356
- <span
357
- :class="checkboxClasses"
358
- aria-hidden="true"
359
- >
360
- <span
361
- :class="iconClasses"
362
- class="block relative z-1"
363
- >
364
- <div
365
- class="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none"
366
- :class="{
367
- 'bg-actions-neutral-hover-overlay': !isChecked && !error,
368
- 'bg-actions-danger-hover-overlay': error,
369
- 'bg-actions-primary-hover-overlay': isChecked && !error,
370
- }"
371
- />
372
- </span>
373
- </span>
413
+ <CheckboxGlyph v-bind="glyphProps" />
374
414
  </label>
375
415
 
376
- <Label
377
- :for="inputId"
378
- class="text-content-text-md-regular"
379
- :class="{
380
- 'text-text-neutral-base': !error && !isChecked,
381
- 'text-text-danger-base': error,
382
- 'text-text-primary-base': isChecked && !error,
383
- 'cursor-not-allowed': disabled,
384
- 'cursor-pointer': !disabled,
385
- }"
386
- @click.stop
416
+ <!-- Icon - horizontal only, between the checkbox and the text -->
417
+ <span
418
+ v-if="(icon || $slots.icon) && orientation === 'horizontal'"
419
+ class="flex items-center justify-center shrink-0 me-2 min-h-5"
420
+ data-testid="ui-input-checkbox-icon"
387
421
  >
388
- <slot name="label">
389
- {{ label }}{{ required ? '*' : '' }}
422
+ <slot name="icon">
423
+ <span
424
+ class="size-4"
425
+ :class="[icon, stateTextClasses]"
426
+ />
390
427
  </slot>
391
- </Label>
428
+ </span>
392
429
 
393
- <!-- icon -->
394
- <UiTooltip
395
- v-if="labelInfo"
396
- :content="labelInfo"
430
+ <div
431
+ v-if="hasText"
432
+ class="flex flex-col min-w-0"
433
+ :class="{ 'items-center': orientation === 'vertical', 'gap-0.5': !showBg && !inChoiceRow, 'gap-1': inChoiceRow, 'flex-1': controlAtEnd }"
434
+ data-testid="ui-input-checkbox-text"
397
435
  >
398
- <div
399
- class="flex items-center justify-center size-4 text-text-neutral-primary cursor-help shrink-0"
400
- :class="{
401
- 'text-text-neutral-base': !error && !isChecked,
402
- 'text-text-danger-base': error,
403
- 'text-text-primary-base': isChecked && !error,
404
- }"
436
+ <Label
437
+ v-if="label || $slots.label || labelInfo"
438
+ :for="inputId"
439
+ :class="[stateTextClasses, inChoiceRow ? 'text-content-text-md-medium' : 'text-content-text-md-regular', {
440
+ 'cursor-not-allowed': disabled,
441
+ 'cursor-pointer': !disabled,
442
+ }]"
443
+ @click.stop
405
444
  >
406
- <span class="i-ph-info-fill size-4" />
407
- </div>
408
- </UiTooltip>
409
- </div>
445
+ <slot name="label">
446
+ {{ label }}{{ required ? '*' : '' }}
447
+ </slot>
410
448
 
411
- <span
412
- v-if="description"
413
- class="text-content-text-md-regular"
414
- :class="{
415
- 'ml-7': orientation === 'horizontal',
416
- 'text-center': orientation === 'vertical',
417
- 'text-text-neutral-tertiary': !error && !isChecked,
418
- 'text-text-danger-tertiary': error,
419
- 'text-text-primary-tertiary': isChecked && !error,
420
- 'cursor-not-allowed': disabled,
421
- 'cursor-pointer': !disabled,
422
- }"
423
- >
424
- {{ description }}
425
- </span>
449
+ <!-- icon: inline so it wraps with the text instead of centering against the whole label block,
450
+ and the word joiner keeps it on the line of the last word.
451
+ -translate-y-px because the icon aligns with the text so that its visually centered -->
452
+ <span
453
+ v-if="labelInfo"
454
+ class="whitespace-nowrap before:content-['\2060']"
455
+ ><UiTooltip :content="labelInfo">
456
+ <span
457
+ class="inline-flex items-center justify-center size-4 ms-2 align-middle -translate-y-px cursor-help shrink-0"
458
+ :class="infoIconClasses"
459
+ data-testid="open-tooltip"
460
+ @click.stop.prevent="emit('tooltip-clicked')"
461
+ >
462
+ <span class="i-ph-info-fill size-4" />
463
+ </span>
464
+ </UiTooltip></span>
465
+ </Label>
466
+
467
+ <span
468
+ v-if="description"
469
+ :id="descriptionId"
470
+ class="text-content-text-md-regular"
471
+ :class="descriptionClasses"
472
+ >
473
+ {{ description }}
474
+ </span>
475
+ </div>
476
+ </div>
426
477
 
427
478
  <!-- Checkbox - vertical only -->
428
479
  <label
@@ -430,34 +481,13 @@ defineExpose({
430
481
  class="flex justify-center mt-2"
431
482
  :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
432
483
  >
433
- <span
434
- :class="checkboxClasses"
435
- aria-hidden="true"
436
- >
437
- <span
438
- :class="iconClasses"
439
- class="block relative z-1"
440
- >
441
- <div
442
- class="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none"
443
- :class="{
444
- 'bg-actions-neutral-hover-overlay': !isChecked && !error,
445
- 'bg-actions-danger-hover-overlay': error,
446
- 'bg-actions-primary-hover-overlay': isChecked && !error,
447
- }"
448
- />
449
- </span>
450
- </span>
484
+ <CheckboxGlyph v-bind="glyphProps" />
451
485
  </label>
452
486
 
453
487
  <div
454
- v-if="showBg"
455
- class="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none rounded-lg"
456
- :class="{
457
- 'bg-actions-neutral-hover-overlay': !isChecked && !error,
458
- 'bg-actions-danger-hover-overlay': error,
459
- 'bg-actions-primary-hover-overlay': isChecked && !error,
460
- }"
488
+ v-if="showBg && !disabled"
489
+ class="absolute inset-0 opacity-0 group-hover/option:opacity-100 transition-opacity pointer-events-none rounded-[inherit]"
490
+ :class="hoverOverlayClasses"
461
491
  />
462
492
  </div>
463
493
 
@@ -467,6 +497,12 @@ defineExpose({
467
497
  :id="`${inputId}-message`"
468
498
  :class="displayMessageClasses"
469
499
  >
500
+ <span
501
+ v-if="error"
502
+ class="i-ph-warning-circle size-4 shrink-0"
503
+ data-testid="ui-input-checkbox-error-icon"
504
+ aria-hidden="true"
505
+ />
470
506
  {{ displayMessage }}
471
507
  </p>
472
508
  </div>