@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
@@ -1,9 +1,12 @@
1
1
  <script setup lang="ts">
2
- import { computed, inject, ref } from 'vue'
3
- import { Label, RadioGroupItem, RadioGroupIndicator, useId } from 'reka-ui'
4
- import type { RadioValue, RadioVariant } from './types'
2
+ import { computed, inject, provide, ref } from 'vue'
3
+ import { Label, useId } from 'reka-ui'
4
+ import type { RadioValue } from './types'
5
5
  import { RADIO_GROUP_KEY } from './types'
6
+ import { CHOICE_ROW_KEY } from '../UiChoice/types'
7
+ import type { ControlPosition } from '../UiChoice/types'
6
8
  import UiTooltip from '../UiTooltip/UiTooltip.vue'
9
+ import RadioGlyph from './RadioGlyph.vue'
7
10
 
8
11
  interface UiInputRadioProps {
9
12
  /**
@@ -23,6 +26,11 @@ interface UiInputRadioProps {
23
26
  * Info tooltip for the label
24
27
  */
25
28
  labelInfo?: string
29
+ /**
30
+ * Icon class rendered between the radio and the text (e.g. `i-ph-bell`).
31
+ * In vertical orientation it sits above the text instead.
32
+ */
33
+ icon?: string
26
34
  /**
27
35
  * Whether this option is disabled
28
36
  * @default false
@@ -34,10 +42,10 @@ interface UiInputRadioProps {
34
42
  */
35
43
  orientation?: 'horizontal' | 'vertical'
36
44
  /**
37
- * Visual variant: 'card' renders a selectable tile
38
- * @default 'default'
45
+ * Which side of the text the radio sits on, in horizontal orientation
46
+ * @default 'start'
39
47
  */
40
- variant?: RadioVariant
48
+ controlPosition?: ControlPosition
41
49
  /**
42
50
  * Radio id attribute
43
51
  */
@@ -48,10 +56,11 @@ const props = withDefaults(defineProps<UiInputRadioProps>(), {
48
56
  label: undefined,
49
57
  description: undefined,
50
58
  labelInfo: undefined,
59
+ icon: undefined,
51
60
  id: undefined,
61
+ controlPosition: 'start',
52
62
  disabled: false,
53
63
  orientation: 'horizontal',
54
- variant: 'default',
55
64
  })
56
65
 
57
66
  const emit = defineEmits<{
@@ -61,6 +70,9 @@ const emit = defineEmits<{
61
70
  // Group context: UiInputRadio is only usable inside UiInputRadioGroup
62
71
  // (reka's RadioGroupItem enforces this too)
63
72
  const group = inject(RADIO_GROUP_KEY, undefined)
73
+ const inChoiceRow = inject(CHOICE_ROW_KEY, false)
74
+ // Controls in the content slot are not the row's control
75
+ provide(CHOICE_ROW_KEY, false)
64
76
 
65
77
  const generatedId = useId()
66
78
  const inputId = computed(() => props.id ?? generatedId)
@@ -77,6 +89,11 @@ const isChecked = computed(() => {
77
89
  const isDisabled = computed(() => props.disabled || (group?.disabled.value ?? false))
78
90
  const error = computed(() => group?.error.value ?? false)
79
91
  const size = computed(() => group?.size.value ?? 'md')
92
+ const isOptionRow = computed(() => props.orientation === 'horizontal')
93
+ // Side by side, a choice keeps its title on one line: the choice widens or the row
94
+ // wraps instead. A title wider than the whole row truncates.
95
+ const titleOnOneLine = computed(() => inChoiceRow && group?.orientation.value === 'horizontal')
96
+ const controlAtEnd = computed(() => props.orientation === 'horizontal' && props.controlPosition === 'end')
80
97
 
81
98
  const descriptionId = computed(() => props.description ? `${inputId.value}-description` : undefined)
82
99
 
@@ -87,145 +104,129 @@ const ariaDescribedby = computed(() => {
87
104
 
88
105
  const radioClasses = computed(() => {
89
106
  const baseClasses = [
90
- 'relative shrink-0 grid place-content-center',
91
- 'rounded-full',
92
- 'focus:outline-hidden',
107
+ 'relative shrink-0 rounded-full border',
108
+ 'focus:outline-hidden focus-indicator-none',
93
109
  'cursor-pointer disabled:cursor-not-allowed',
94
110
  ]
95
111
 
96
- // Size classes
97
112
  const sizeClasses = {
98
- sm: 'w-3.5 h-3.5',
99
- md: 'w-4 h-4',
100
- lg: 'w-5 h-5',
113
+ sm: 'size-unit-0-75rem',
114
+ md: 'size-check-sm',
115
+ lg: 'size-check-md',
101
116
  }
102
117
 
103
118
  baseClasses.push(sizeClasses[size.value])
104
119
 
105
- // Selected is a full fill (the light inner circle turns it into a thick
106
- // ring); unselected keeps a border that thickens on hover
120
+ // Checked is a thin ring around a dot on the light surface; unchecked is a denser
121
+ // fill than the fields, which once disabled falls back to the field fill with an outline
107
122
  if (isChecked.value) {
108
- baseClasses.push('border-none')
109
- baseClasses.push(error.value ? 'bg-actions-danger-idle' : 'bg-actions-primary-idle')
123
+ baseClasses.push('bg-surface-neutral-bg-main', checkedRingClass.value)
124
+ } else if (isDisabled.value && !error.value) {
125
+ baseClasses.push('bg-control-field-bg border-control-disabled-border')
110
126
  } else {
111
- baseClasses.push('bg-surface-neutral-bg-main border')
112
- baseClasses.push(error.value ? 'border-borders-danger-strong' : 'border-borders-neutral-strong')
127
+ baseClasses.push('bg-control-check-bg', error.value ? 'border-control-error-border' : 'border-control-field-border')
113
128
  if (!isDisabled.value) {
114
- baseClasses.push('group-hover:border-2')
129
+ baseClasses.push('group-hover/option:bg-control-check-bg-hover')
115
130
  }
116
131
  }
117
132
 
118
- // Focus ring
119
- if (error.value) {
120
- baseClasses.push('focus-visible:ring focus-visible:ring-borders-danger-strong')
121
- } else if (isChecked.value) {
122
- baseClasses.push('focus-visible:ring focus-visible:ring-borders-primary-strong')
123
- } else {
124
- baseClasses.push('focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus-ring')
125
- }
126
-
127
133
  return baseClasses
128
134
  })
129
135
 
130
- // Light inner circle of the selected state; grows on hover (ring thins) and
131
- // stays grown when disabled or errored — an errored selected radio is always
132
- // the thin ring. Fraction sizes scale with the sm/md/lg glyph.
133
- const indicatorClasses = computed(() => {
134
- // Transform centering (not inset/margins): layout boxes get snapped to
135
- // device pixels, which renders fractional gaps visibly off-center
136
- const baseClasses = [
137
- 'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full z-1',
138
- 'bg-surface-neutral-bg-main transition-all',
139
- ]
140
-
141
- if (isDisabled.value || error.value) {
142
- baseClasses.push('size-3/4')
143
- } else {
144
- baseClasses.push('size-1/2 group-hover:size-3/4')
136
+ // Transform centering (not inset/margins): layout boxes get snapped to
137
+ // device pixels, which renders fractional gaps visibly off-center
138
+ const dotClasses = computed(() => {
139
+ const sizeClasses = {
140
+ sm: 'size-unit-0-375rem',
141
+ md: 'size-unit-0-5rem',
142
+ lg: 'size-unit-0-625rem',
145
143
  }
146
-
147
- return baseClasses
144
+ return ['absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full', sizeClasses[size.value]]
148
145
  })
149
146
 
150
- // Preview dot shown while hovering an unselected, enabled radio
151
- const previewDotClasses = computed(() => {
152
- const baseClasses = [
153
- 'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 size-1/2 rounded-full z-1',
154
- 'opacity-0 group-hover:opacity-50 transition-opacity pointer-events-none',
155
- ]
156
-
157
- baseClasses.push(error.value ? 'bg-actions-danger-idle' : 'bg-actions-neutral-idle')
147
+ const checkedRingClass = computed(() => {
148
+ if (error.value) {
149
+ return 'border-actions-danger-idle'
150
+ }
151
+ return isDisabled.value ? 'border-borders-neutral-strong' : 'border-actions-primary-idle'
152
+ })
158
153
 
159
- return baseClasses
154
+ const indicatorClasses = computed(() => {
155
+ if (error.value) {
156
+ return [...dotClasses.value, 'bg-actions-danger-idle']
157
+ }
158
+ return [...dotClasses.value, isDisabled.value ? 'bg-text-neutral-secondary' : 'bg-actions-primary-idle']
160
159
  })
161
160
 
162
- const hoverOverlayClasses = computed(() => ({
163
- 'bg-actions-neutral-hover-overlay': !isChecked.value && !error.value,
164
- 'bg-actions-danger-hover-overlay': error.value,
165
- 'bg-actions-primary-hover-overlay': isChecked.value && !error.value,
166
- }))
161
+ // A choicebox tints its text with the selection. Disabled, a choicebox recedes
162
+ // whole, while a plain option keeps its selection legible in neutral.
163
+ const tone = computed(() => {
164
+ if (error.value) {
165
+ return 'danger'
166
+ }
167
+ if (isDisabled.value) {
168
+ return isChecked.value && !inChoiceRow ? 'neutral' : 'disabled'
169
+ }
170
+ return isChecked.value && inChoiceRow ? 'selected' : 'neutral'
171
+ })
167
172
 
168
173
  const labelClasses = computed(() => ({
169
- 'text-text-neutral-base': !error.value && !isChecked.value,
170
- 'text-text-danger-base': error.value,
171
- 'text-text-primary-base': isChecked.value && !error.value,
174
+ 'text-content-text-md-medium': inChoiceRow,
175
+ 'text-content-text-md-regular': !inChoiceRow,
176
+ 'text-text-neutral-base': tone.value === 'neutral',
177
+ 'text-text-neutral-tertiary': tone.value === 'disabled',
178
+ 'text-text-danger-base': tone.value === 'danger',
179
+ 'text-text-primary-title': tone.value === 'selected',
180
+ 'flex items-center min-w-0': titleOnOneLine.value,
172
181
  'cursor-not-allowed': isDisabled.value,
173
182
  'cursor-pointer': !isDisabled.value,
174
183
  }))
175
184
 
176
185
  const descriptionClasses = computed(() => ({
177
186
  'text-center': props.orientation === 'vertical',
178
- 'text-text-neutral-tertiary': !error.value && !isChecked.value,
179
- 'text-text-danger-tertiary': error.value,
180
- 'text-text-primary-tertiary': isChecked.value && !error.value,
187
+ 'text-text-neutral-secondary': tone.value === 'neutral',
188
+ 'text-text-neutral-tertiary': tone.value === 'disabled',
189
+ 'text-text-danger-tertiary': tone.value === 'danger',
190
+ 'text-text-primary-base': tone.value === 'selected',
181
191
  'cursor-not-allowed': isDisabled.value,
182
192
  'cursor-pointer': !isDisabled.value,
183
193
  }))
184
194
 
185
195
  const innerWrapperClasses = computed(() => {
186
- // h-full + items-center let a consumer-sized card (e.g. h-[76px])
187
- // center the glyph and text block vertically
188
- const classes: string[] = ['group relative rounded-md flex h-full']
196
+ const classes: string[] = ['group/option relative flex h-full']
189
197
 
190
- if (props.orientation === 'vertical') {
191
- classes.push('flex-col items-center')
192
- } else {
193
- classes.push('items-center')
194
- }
198
+ classes.push(props.orientation === 'vertical' ? 'flex-col items-center' : 'items-start')
195
199
 
196
- if (isDisabled.value) {
197
- classes.push('opacity-50 cursor-not-allowed')
198
- } else {
199
- classes.push('cursor-pointer')
200
+ if (isOptionRow.value && !inChoiceRow) {
201
+ // The row bleeds around the option without taking layout space, so a one-line
202
+ // option is a 32px hit target; hover shows on the glyph only. Inside UiChoice the choice draws the row.
203
+ classes.push('isolate before:absolute before:-inset-x-3 before:-inset-y-1.5 before:-z-10 before:rounded-field-md has-[[role=radio]:focus-visible]:before:focus-ring')
204
+ } else if (props.orientation === 'vertical') {
205
+ classes.push(inChoiceRow ? 'rounded-field-md' : 'rounded-field-md has-[[role=radio]:focus-visible]:focus-ring')
200
206
  }
201
207
 
202
- if (props.variant === 'card') {
203
- classes.push('p-2 ring-inset')
204
- if (isChecked.value && !error.value) {
205
- // checked
206
- classes.push('bg-actions-primary-idle-alpha-light ring-2 ring-actions-primary-idle')
207
- } else if (!isChecked.value && !error.value) {
208
- // idle
209
- classes.push('bg-actions-neutral-idle-alpha-light ring-transparent')
210
- } else if (isChecked.value && error.value) {
211
- // error + checked
212
- classes.push('bg-actions-danger-idle-alpha-light ring-2 ring-actions-danger-idle')
213
- } else {
214
- // error + unchecked
215
- classes.push('bg-actions-danger-idle-alpha-light')
216
- }
217
- }
208
+ classes.push(isDisabled.value ? 'cursor-not-allowed' : 'cursor-pointer')
218
209
 
219
210
  return classes
220
211
  })
221
212
 
222
- const itemRef = ref<InstanceType<typeof RadioGroupItem> | null>(null)
213
+ const glyphProps = computed(() => ({
214
+ inputId: inputId.value,
215
+ value: props.value,
216
+ disabled: props.disabled,
217
+ error: error.value,
218
+ ariaDescribedby: ariaDescribedby.value,
219
+ radioClasses: radioClasses.value,
220
+ indicatorClasses: indicatorClasses.value,
221
+ }))
222
+
223
+ const glyphRef = ref<InstanceType<typeof RadioGlyph> | null>(null)
223
224
 
224
225
  function radioElement (): HTMLElement | undefined {
225
- return (itemRef.value?.$el as HTMLElement | undefined) ?? undefined
226
+ return glyphRef.value?.el()
226
227
  }
227
228
 
228
- // Make the whole wrapper (description, padding, card surface) select the radio.
229
+ // Make the whole wrapper (description, padding) select the radio.
229
230
  // Clicks on the button itself or on the label are skipped: the button handles
230
231
  // its own clicks and the label forwards to the button natively via `for`.
231
232
  function onWrapperClick (event: MouseEvent) {
@@ -242,6 +243,10 @@ function onWrapperClick (event: MouseEvent) {
242
243
  if (target.closest('label')) {
243
244
  return
244
245
  }
246
+ // Controls in the content slot handle their own clicks
247
+ if (target.closest('input, textarea, select, button, a')) {
248
+ return
249
+ }
245
250
 
246
251
  radioEl.click()
247
252
  }
@@ -253,86 +258,112 @@ defineExpose({
253
258
  </script>
254
259
 
255
260
  <template>
256
- <!-- Cards are edge-to-edge interactive surfaces, so they fill their slot;
257
- the default variant shrink-wraps to keep the click zone on content -->
261
+ <!-- An option that hosts a field or pushes its radio to the end fills its
262
+ slot; otherwise it shrink-wraps to keep the click zone on content -->
258
263
  <div
259
264
  class="ui-input-radio-wrapper"
260
- :class="variant === 'card' ? 'w-full' : 'w-fit'"
265
+ data-testid="ui-input-radio"
266
+ :class="[$slots.content || controlAtEnd ? 'w-full' : 'w-fit', { 'max-w-full': titleOnOneLine }]"
261
267
  >
262
268
  <div
263
269
  :class="innerWrapperClasses"
270
+ data-testid="ui-input-radio-row"
264
271
  @click="onWrapperClick"
265
272
  >
266
- <!-- Radio - horizontal only, centered against the whole text block -->
273
+ <!-- Radio - horizontal only. The box matches the label's line-height so
274
+ the glyph centers on the label line, and the checkbox's width so
275
+ labels line up; the glyph hugs the outer edge like a checkbox does -->
267
276
  <span
268
277
  v-if="orientation === 'horizontal'"
269
- class="flex w-5 items-center justify-center shrink-0 mr-2"
278
+ class="flex size-5 items-center shrink-0"
279
+ :class="controlAtEnd ? ['order-last justify-end', inChoiceRow ? 'ms-5' : 'ms-3'] : ['justify-start', inChoiceRow ? 'me-3' : 'me-1']"
270
280
  >
271
- <RadioGroupItem
272
- :id="inputId"
273
- ref="itemRef"
274
- :value="value"
275
- :disabled="disabled"
276
- :class="radioClasses"
277
- :aria-invalid="error || undefined"
278
- :aria-describedby="ariaDescribedby"
281
+ <RadioGlyph
282
+ ref="glyphRef"
283
+ v-bind="glyphProps"
279
284
  @select="emit('select', $event)"
280
- >
281
- <RadioGroupIndicator :class="indicatorClasses" />
285
+ />
286
+ </span>
287
+
288
+ <!-- Optional icon: between the radio and the text, or above the text in
289
+ vertical orientation. min-h-5 keeps a small icon on the label line -->
290
+ <span
291
+ v-if="icon || $slots.icon"
292
+ class="flex items-center justify-center shrink-0"
293
+ data-testid="ui-input-radio-icon"
294
+ :class="orientation === 'vertical' ? 'mb-2' : 'mr-2 min-h-5'"
295
+ >
296
+ <slot name="icon">
282
297
  <span
283
- v-if="!isChecked && !isDisabled"
284
- :class="previewDotClasses"
285
- />
286
- <div
287
- v-if="!isDisabled"
288
- class="absolute inset-0 rounded-full opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none"
289
- :class="hoverOverlayClasses"
298
+ class="size-4"
299
+ :class="[icon, {
300
+ 'text-text-neutral-base': tone === 'neutral',
301
+ 'text-text-neutral-tertiary': tone === 'disabled',
302
+ 'text-text-danger-base': tone === 'danger',
303
+ 'text-text-primary-title': tone === 'selected',
304
+ }]"
290
305
  />
291
- </RadioGroupItem>
306
+ </slot>
292
307
  </span>
293
308
 
294
309
  <div
295
310
  class="flex flex-col min-w-0"
296
- :class="orientation === 'vertical' ? 'items-center' : 'flex-1'"
311
+ :class="[orientation === 'vertical' ? 'items-center' : 'flex-1', inChoiceRow ? 'gap-1' : 'gap-0.5']"
312
+ data-testid="ui-input-radio-text"
297
313
  >
298
- <div class="flex items-center">
299
- <Label
300
- :for="inputId"
301
- class="text-content-text-md-medium"
302
- :class="labelClasses"
314
+ <Label
315
+ :for="inputId"
316
+ :class="labelClasses"
317
+ >
318
+ <span
319
+ v-if="titleOnOneLine"
320
+ class="truncate"
303
321
  >
304
322
  <slot name="label">
305
323
  {{ label }}
306
324
  </slot>
307
- </Label>
325
+ </span>
326
+ <slot
327
+ v-else
328
+ name="label"
329
+ >
330
+ {{ label }}
331
+ </slot>
308
332
 
309
- <!-- info tooltip -->
310
- <UiTooltip
333
+ <!-- icon: inline so it wraps with the text instead of centering against the whole label block,
334
+ and the word joiner keeps it on the line of the last word.
335
+ -translate-y-px because the icon aligns with the text so that its visually centered -->
336
+ <span
311
337
  v-if="labelInfo"
312
- :content="labelInfo"
313
- >
314
- <div
315
- class="flex items-center justify-center size-4 cursor-help shrink-0 ml-1"
338
+ class="whitespace-nowrap before:content-['\2060']"
339
+ ><UiTooltip :content="labelInfo">
340
+ <span
341
+ class="inline-flex items-center justify-center size-4 ms-2 align-middle -translate-y-px cursor-help shrink-0"
316
342
  :class="{
317
- 'text-text-neutral-secondary': !error && !isChecked,
318
- 'text-text-danger-secondary': error,
319
- 'text-text-primary-secondary': isChecked && !error,
343
+ 'text-text-neutral-tertiary': tone === 'neutral' || tone === 'disabled',
344
+ 'text-text-danger-tertiary': tone === 'danger',
345
+ 'text-text-primary-tertiary': tone === 'selected',
320
346
  }"
321
- @click.stop
347
+ data-testid="ui-input-radio-label-info"
348
+ @click.stop.prevent
322
349
  >
323
350
  <span class="i-ph-info-fill size-4" />
324
- </div>
325
- </UiTooltip>
326
- </div>
351
+ </span>
352
+ </UiTooltip></span>
353
+ </Label>
327
354
 
328
355
  <span
329
356
  v-if="description"
330
357
  :id="descriptionId"
331
- class="text-content-text-md-medium"
358
+ class="text-content-text-md-regular"
332
359
  :class="descriptionClasses"
333
360
  >
334
361
  {{ description }}
335
362
  </span>
363
+
364
+ <!-- Extra content under the text, e.g. a field the option configures.
365
+ Sits outside the label so it stays out of the accessible name -->
366
+ <slot name="content" />
336
367
  </div>
337
368
 
338
369
  <!-- Radio - vertical only -->
@@ -340,35 +371,12 @@ defineExpose({
340
371
  v-if="orientation === 'vertical'"
341
372
  class="flex justify-center mt-2"
342
373
  >
343
- <RadioGroupItem
344
- :id="inputId"
345
- ref="itemRef"
346
- :value="value"
347
- :disabled="disabled"
348
- :class="radioClasses"
349
- :aria-invalid="error || undefined"
350
- :aria-describedby="ariaDescribedby"
374
+ <RadioGlyph
375
+ ref="glyphRef"
376
+ v-bind="glyphProps"
351
377
  @select="emit('select', $event)"
352
- >
353
- <RadioGroupIndicator :class="indicatorClasses" />
354
- <span
355
- v-if="!isChecked && !isDisabled"
356
- :class="previewDotClasses"
357
- />
358
- <div
359
- v-if="!isDisabled"
360
- class="absolute inset-0 rounded-full opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none"
361
- :class="hoverOverlayClasses"
362
- />
363
- </RadioGroupItem>
378
+ />
364
379
  </span>
365
-
366
- <!-- Card hover overlay -->
367
- <div
368
- v-if="variant === 'card' && !isDisabled"
369
- class="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none rounded-md"
370
- :class="hoverOverlayClasses"
371
- />
372
380
  </div>
373
381
  </div>
374
382
  </template>
@@ -55,6 +55,10 @@ interface UiInputRadioGroupProps {
55
55
  * @default false
56
56
  */
57
57
  fullWidth?: boolean
58
+ /**
59
+ * Id of the element naming the group, set on the radiogroup itself
60
+ */
61
+ ariaLabelledby?: string
58
62
  }
59
63
 
60
64
  const props = withDefaults(defineProps<UiInputRadioGroupProps>(), {
@@ -68,6 +72,7 @@ const props = withDefaults(defineProps<UiInputRadioGroupProps>(), {
68
72
  size: 'md',
69
73
  orientation: 'vertical',
70
74
  fullWidth: false,
75
+ ariaLabelledby: undefined,
71
76
  })
72
77
 
73
78
  const emit = defineEmits<{
@@ -98,18 +103,21 @@ const displayMessage = computed(() => {
98
103
 
99
104
  const messageId = computed(() => displayMessage.value ? `${groupId}-message` : undefined)
100
105
 
101
- // items-stretch keeps card tiles equal-height when they share a row
106
+ // items-stretch keeps card tiles equal-height when they share a row. The 12px
107
+ // gap holds the 6px bleed of each option row, so one-line rows stay 32px hit
108
+ // targets that never overlap.
102
109
  const rootClasses = computed(() =>
103
110
  props.orientation === 'horizontal'
104
- ? 'flex flex-row flex-wrap items-stretch gap-2'
105
- : 'flex flex-col gap-2',
111
+ ? 'flex flex-row flex-wrap items-stretch gap-3'
112
+ : 'flex flex-col gap-3',
106
113
  )
107
114
 
115
+ // mt-1.5 clears the 6px bleed of the last option row
108
116
  const displayMessageClasses = computed(() => {
109
- const baseClasses = ['text-content-legends-bold-md mt-1']
117
+ const baseClasses = ['text-content-legends-bold-md mt-1.5']
110
118
 
111
119
  if (props.error) {
112
- baseClasses.push('text-text-danger-secondary')
120
+ baseClasses.push('text-text-danger-body')
113
121
  } else {
114
122
  baseClasses.push('text-text-neutral-secondary')
115
123
  }
@@ -121,6 +129,7 @@ provide(RADIO_GROUP_KEY, {
121
129
  modelValue: currentValue,
122
130
  error: toRef(props, 'error'),
123
131
  size: toRef(props, 'size'),
132
+ orientation: toRef(props, 'orientation'),
124
133
  disabled: toRef(props, 'disabled'),
125
134
  messageId,
126
135
  })
@@ -138,6 +147,7 @@ provide(RADIO_GROUP_KEY, {
138
147
  :loop="loop"
139
148
  :required="required"
140
149
  :orientation="orientation"
150
+ :aria-labelledby="ariaLabelledby"
141
151
  :class="rootClasses"
142
152
  @update:model-value="onUpdateModelValue"
143
153
  >
@@ -3,8 +3,6 @@ import type { AcceptableValue } from 'reka-ui'
3
3
 
4
4
  export type RadioSize = 'sm' | 'md' | 'lg'
5
5
 
6
- export type RadioVariant = 'default' | 'card'
7
-
8
6
  /**
9
7
  * Valid radio option values. `null` is a supported option value;
10
8
  * `undefined` means "no selection". Object values must be reference-stable.
@@ -16,6 +14,7 @@ export interface RadioGroupContext {
16
14
  modelValue: Readonly<Ref<RadioValue | undefined>>
17
15
  error: Readonly<Ref<boolean>>
18
16
  size: Readonly<Ref<RadioSize>>
17
+ orientation: Readonly<Ref<'vertical' | 'horizontal'>>
19
18
  disabled: Readonly<Ref<boolean>>
20
19
  /** id of the group error message, for aria-describedby on items */
21
20
  messageId: Readonly<Ref<string | undefined>>
@@ -7,6 +7,7 @@ import UiDropdownContent from '../UiDropdown/UiDropdownContent.vue'
7
7
  import UiDropdownItem from '../UiDropdown/UiDropdownItem.vue'
8
8
  import type { AcceptableValue } from 'reka-ui'
9
9
  import type { SelectSize } from './types'
10
+ import { useControlShape } from '../../composables/useControlShape'
10
11
 
11
12
  interface UiInputSelectProps {
12
13
  size?: SelectSize
@@ -57,6 +58,8 @@ const props = withDefaults(defineProps<UiInputSelectProps>(), {
57
58
  multiple: false,
58
59
  })
59
60
 
61
+ const shape = useControlShape('field', () => props.size)
62
+
60
63
  // Sentinel value for the "clear" option — intentionally verbose to avoid collisions with real values
61
64
  const CLEAR_VALUE = '__ui-input-select-clear__'
62
65
 
@@ -110,13 +113,12 @@ function handleUpdate (value: AcceptableValue) {
110
113
  <!-- Loading State -->
111
114
  <div
112
115
  v-if="loading"
113
- class="flex items-center gap-2 w-full rounded-lg border border-solid border-borders-neutral-strong bg-actions-neutral-idle-alpha-light"
114
- :class="{
115
- 'h-8 px-2': size === 'sm',
116
- 'h-10 px-3': size === 'md',
117
- 'h-12 px-4': size === 'lg',
118
- 'h-16 px-4': size === 'xl',
119
- }"
116
+ class="flex items-center gap-2 w-full border border-solid border-control-field-border bg-control-field-bg"
117
+ :class="[shape.radius, shape.height, {
118
+ 'px-2': size === 'sm',
119
+ 'px-3': size === 'md',
120
+ 'px-4': size === 'lg' || size === 'xl',
121
+ }]"
120
122
  >
121
123
  <div class="h-3 w-24 bg-surface-neutral-alpha-400 rounded-md animate-pulse" />
122
124
  </div>