@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
@@ -0,0 +1,23 @@
1
+ <script setup lang="ts">
2
+ import { SelectGroup, SelectLabel } from 'reka-ui'
3
+
4
+ defineProps<{
5
+ /** Heading read before the options of the group; leave it out for an untitled group. */
6
+ label?: string
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <SelectGroup
12
+ v-bind="$attrs"
13
+ class="flex flex-col gap-1"
14
+ >
15
+ <SelectLabel
16
+ v-if="label"
17
+ class="px-4 pt-2 pb-1 text-content-legends-bold-md text-text-neutral-tertiary"
18
+ >
19
+ {{ label }}
20
+ </SelectLabel>
21
+ <slot />
22
+ </SelectGroup>
23
+ </template>
@@ -1,25 +1,49 @@
1
1
  <script setup lang="ts">
2
- import type { SelectItemProps } from 'reka-ui'
2
+ import type { SelectItemEmits, SelectItemProps } from 'reka-ui'
3
3
  import { SelectItem, SelectItemText } from 'reka-ui'
4
- import { inject } from 'vue'
4
+ import { computed, inject } from 'vue'
5
5
  import { UI_DROPDOWN_MULTIPLE_KEY } from './context'
6
+ import DropdownItemCheckbox from './DropdownItemCheckbox.vue'
6
7
 
7
8
  interface Props extends SelectItemProps {
8
9
  title?: string
9
10
  description?: string
10
11
  icon?: string
12
+ /** `danger` marks an option that takes something away, such as removing access. */
13
+ variant?: 'default' | 'danger'
11
14
  }
12
15
 
13
- withDefaults(defineProps<Props>(), {
16
+ const props = withDefaults(defineProps<Props>(), {
14
17
  title: undefined,
15
18
  description: undefined,
16
19
  icon: undefined,
20
+ variant: 'default',
17
21
  })
18
22
 
23
+ const isDanger = computed(() => props.variant === 'danger')
24
+
25
+ const stateBackgroundClass = computed(() => isDanger.value
26
+ ? 'data-highlighted:bg-actions-danger-hover-overlay data-[state=checked]:bg-actions-danger-pressed-overlay'
27
+ : 'data-highlighted:bg-actions-neutral-hover-overlay data-[state=checked]:bg-actions-primary-active-overlay')
28
+
29
+ const titleColorClass = computed(() => isDanger.value
30
+ ? 'text-text-danger-body'
31
+ : 'text-actions-neutral-idle group-data-highlighted/item:text-actions-neutral-idle group-data-[state=checked]/item:text-actions-primary-idle')
32
+
33
+ const descriptionColorClass = computed(() => isDanger.value
34
+ ? 'text-text-neutral-secondary'
35
+ : 'text-text-neutral-secondary group-data-highlighted/item:text-text-neutral-secondary group-data-[state=checked]/item:text-text-primary-secondary')
36
+
19
37
  // `UiDropdown` provides this when `multiple` is set so items render a
20
38
  // leading checkbox automatically. Falls back to single-select behavior
21
39
  // when the item is used outside a `UiDropdown` (defensive default).
22
40
  const isMultiple = inject(UI_DROPDOWN_MULTIPLE_KEY, undefined)
41
+
42
+ // Reka picks on Enter keydown without cancelling it, so its keypress would activate whatever the pick focused.
43
+ function consumePickingKey (event: SelectItemEmits['select'][0]) {
44
+ const { originalEvent } = event.detail
45
+ if (originalEvent instanceof KeyboardEvent) originalEvent.preventDefault()
46
+ }
23
47
  </script>
24
48
 
25
49
  <template>
@@ -27,25 +51,17 @@ const isMultiple = inject(UI_DROPDOWN_MULTIPLE_KEY, undefined)
27
51
  v-bind="$attrs"
28
52
  :value="value"
29
53
  :disabled="disabled"
30
- class="focus-indicator-none group flex px-3 py-1 rounded-md cursor-pointer outline-hidden transition-all duration-150 ease-in-out data-highlighted:bg-actions-neutral-hover-overlay data-[state=checked]:bg-actions-primary-active-overlay data-disabled:opacity-50 data-disabled:cursor-not-allowed data-disabled:pointer-events-none"
54
+ class="focus-indicator-none group/item flex px-3 py-1 rounded-row cursor-pointer outline-hidden transition-all duration-150 ease-in-out data-disabled:opacity-50 data-disabled:cursor-not-allowed data-disabled:pointer-events-none"
55
+ :class="stateBackgroundClass"
56
+ @select="consumePickingKey"
31
57
  >
32
58
  <div class="flex flex-1 gap-2 items-center overflow-hidden">
33
- <!-- Multi-select indicator: visually mirrors `UiInputCheckbox` (same
34
- size, border radius, primary fill, and `i-ls-checkbox-check`
35
- icon) without dragging in the input's runtime/a11y plumbing.
36
- Driven entirely by the parent `SelectItem`'s `data-[state=checked]`
37
- attribute — no per-item state. -->
38
- <div
39
- v-if="isMultiple"
40
- class="shrink-0 size-4 rounded-sm border border-borders-neutral-strong bg-surface-neutral-bg-main grid place-content-center transition-colors group-data-[state=checked]:bg-actions-primary-idle group-data-[state=checked]:border-borders-primary-default"
41
- >
42
- <div class="i-ls-checkbox-check size-3 text-actions-neutral-invert-idle opacity-0 group-data-[state=checked]:opacity-100" />
43
- </div>
59
+ <DropdownItemCheckbox v-if="isMultiple" />
44
60
 
45
61
  <div
46
62
  v-if="icon"
47
63
  :class="[icon, 'size-4']"
48
- class="shrink-0 group-data-highlighted:text-actions-neutral-idle group-data-[state=checked]:text-actions-primary-idle"
64
+ class="shrink-0 group-data-highlighted/item:text-actions-neutral-idle group-data-[state=checked]/item:text-actions-primary-idle"
49
65
  />
50
66
 
51
67
  <div
@@ -56,14 +72,18 @@ const isMultiple = inject(UI_DROPDOWN_MULTIPLE_KEY, undefined)
56
72
  </div>
57
73
 
58
74
  <div class="flex flex-1 flex-col items-start justify-center min-w-0 py-1.5">
59
- <SelectItemText class="whitespace-nowrap text-content-text-md-medium text-actions-neutral-idle group-data-highlighted:text-actions-neutral-idle group-data-[state=checked]:text-actions-primary-idle">
75
+ <SelectItemText
76
+ class="max-w-full truncate text-content-text-md-medium"
77
+ :class="titleColorClass"
78
+ >
60
79
  <slot name="title">
61
80
  {{ title }}
62
81
  </slot>
63
82
  </SelectItemText>
64
83
  <div
65
84
  v-if="description || $slots.description"
66
- class="text-content-legends-medium-md text-text-neutral-secondary group-data-highlighted:text-text-neutral-secondary group-data-[state=checked]:text-text-primary-secondary"
85
+ class="max-w-full truncate text-content-legends-medium-md"
86
+ :class="descriptionColorClass"
67
87
  >
68
88
  <slot name="description">
69
89
  {{ description }}
@@ -3,5 +3,5 @@ import { SelectSeparator } from 'reka-ui'
3
3
  </script>
4
4
 
5
5
  <template>
6
- <SelectSeparator class="h-px bg-borders-neutral-light my-1 -mx-2 relative" />
6
+ <SelectSeparator class="h-px bg-borders-neutral-light my-1 -mx-1 relative" />
7
7
  </template>
@@ -3,7 +3,7 @@ import type { SelectTriggerProps } from 'reka-ui'
3
3
  import { SelectIcon, SelectTrigger, SelectValue } from 'reka-ui'
4
4
  import { computed, useSlots } from 'vue'
5
5
  import { useButtonStyles } from '../../composables/useButtonStyles'
6
- import { useIconSize } from '../../composables/useIconSize'
6
+ import { useControlShape } from '../../composables/useControlShape'
7
7
 
8
8
  type TSize = 'sm' | 'md' | 'lg' | 'xl'
9
9
  type TColor = 'neutral' | 'primary'
@@ -24,12 +24,21 @@ interface Props extends SelectTriggerProps {
24
24
  color?: TColor
25
25
  /**
26
26
  * Visual variant, named to match `UiButton`. `secondary` (default) is the
27
- * bordered field look. `tertiary` drops the border + background to read like
28
- * a `UiButton` tertiary — the trigger shows only colored text with a hover
29
- * overlay, so it sits flush next to tertiary buttons (e.g. a "View all" CTA).
27
+ * bordered field look. `tertiary` drops the border + background: the trigger
28
+ * shows only its value, coloured like a `UiButton` tertiary and set in the
29
+ * regular body text style, with a hover overlay. It suits a value picked
30
+ * inside a row, such as a role beside a person.
30
31
  */
31
32
  variant?: TVariant
32
33
  error?: boolean
34
+ /**
35
+ * Shows the value as static text in the box of an editable trigger: same size, padding and
36
+ * border width, no fill, and an empty slot where the chevron would be, so its text ends where an
37
+ * editable trigger's value ends. It is not focusable and opens nothing; it renders as a `group`, so pass
38
+ * `aria-labelledby` (for example the row's name) to label it like the editable trigger.
39
+ * The value reads in the secondary text colour. Render it from the `value` slot.
40
+ */
41
+ readonly?: boolean
33
42
  }
34
43
 
35
44
  const props = withDefaults(defineProps<Props>(), {
@@ -40,59 +49,112 @@ const props = withDefaults(defineProps<Props>(), {
40
49
  color: 'neutral',
41
50
  variant: 'secondary',
42
51
  error: false,
52
+ readonly: false,
43
53
  })
44
54
 
45
55
  const slots = useSlots()
46
56
 
47
- // Shared button styling — both variants pull their background / border / text /
57
+ // Shared button styling — both variants pull their background / text /
48
58
  // overlay tokens from the same source `UiButton` uses, so the trigger stays in
49
59
  // lockstep with the button palette. `error` maps onto the button `error` color;
50
60
  // otherwise the trigger's `primary`/`neutral` colors line up 1:1.
51
61
  const buttonColor = computed(() => (props.error ? 'error' as const : props.color))
52
- const { textClass, bgClass, borderClasses, beforeHoverOverlayClass } = useButtonStyles({
62
+ const { textClass, bgClass, beforeHoverOverlayClass } = useButtonStyles({
53
63
  variant: () => props.variant,
54
64
  color: buttonColor,
55
65
  disabled: () => props.disabled,
56
66
  })
57
67
 
68
+ const FIELD_BORDER: Record<'neutral' | 'primary' | 'error', string> = {
69
+ neutral: 'border-control-field-border',
70
+ primary: 'border-borders-primary-strong',
71
+ error: 'border-control-error-border',
72
+ }
73
+ const borderClass = computed(() => FIELD_BORDER[buttonColor.value])
74
+
75
+ const fieldBgClass = computed(() =>
76
+ buttonColor.value === 'neutral' ? 'bg-control-field-bg' : bgClass.value,
77
+ )
78
+
79
+ // With a `leading` slot the start padding shrinks so the leading box (24px on
80
+ // sm and md, 32px on lg and xl, enforced on its wrapper) sits as far from the
81
+ // start edge as from the top and bottom: (field height - box) / 2 - 1px border.
58
82
  const sizeClasses = computed(() => {
59
83
  const sizes = {
60
84
  sm: {
61
- field: 'h-8 px-2 gap-1',
62
- text: 'text-action-button-sm tracking-[0.05px]',
85
+ field: 'gap-1',
63
86
  padding: 'px-2',
87
+ leadingPadding: 'pl-[3px] pr-2',
88
+ leadingBox: 'size-6',
89
+ text: 'text-action-button-sm tracking-[0.05px]',
90
+ tertiaryText: 'text-content-legends-medium-md',
64
91
  },
65
92
  md: {
66
- field: 'h-10 px-3 gap-1.5',
67
- text: 'text-action-button-md leading-[20px] tracking-[-0.04px]',
93
+ field: 'gap-1.5',
68
94
  padding: 'px-3',
95
+ leadingPadding: 'pl-[5px] pr-3',
96
+ leadingBox: 'size-6',
97
+ text: 'text-action-button-md leading-[20px] tracking-[-0.04px]',
98
+ tertiaryText: 'text-content-text-md-regular',
69
99
  },
70
100
  lg: {
71
- field: 'h-12 px-4 gap-2',
72
- text: 'text-action-button-lg leading-[20px] tracking-[-0.04px]',
101
+ field: 'gap-2',
73
102
  padding: 'px-4',
103
+ leadingPadding: 'pl-[7px] pr-4',
104
+ leadingBox: 'size-8',
105
+ text: 'text-action-button-lg leading-[20px] tracking-[-0.04px]',
106
+ tertiaryText: 'text-content-text-lg-regular',
74
107
  },
75
108
  xl: {
76
- field: 'h-16 px-4 gap-2',
77
- text: 'text-action-button-lg leading-[24px] tracking-[-0.04px]',
109
+ field: 'gap-2',
78
110
  padding: 'px-4',
111
+ leadingPadding: 'pl-[15px] pr-4',
112
+ leadingBox: 'size-8',
113
+ text: 'text-action-button-lg leading-[24px] tracking-[-0.04px]',
114
+ tertiaryText: 'text-content-text-lg-regular',
79
115
  },
80
116
  }
81
117
 
82
118
  return sizes[props.size]
83
119
  })
84
120
 
85
- const iconSizeClass = useIconSize(() => props.size)
121
+ const shape = useControlShape('field', () => props.size)
122
+
123
+ const boxClasses = computed(() => [
124
+ 'flex',
125
+ 'items-center',
126
+ 'justify-between',
127
+ 'w-full',
128
+ shape.value.radius,
129
+ shape.value.height,
130
+ 'border',
131
+ 'border-solid',
132
+ 'border-transparent',
133
+ 'bg-transparent',
134
+ sizeClasses.value.field,
135
+ ])
136
+
137
+ const readonlyTextClasses = computed(() => [
138
+ 'flex-1',
139
+ 'overflow-hidden',
140
+ 'text-ellipsis',
141
+ 'whitespace-nowrap',
142
+ 'min-w-0',
143
+ 'text-left',
144
+ 'text-text-neutral-secondary',
145
+ props.variant === 'tertiary' ? sizeClasses.value.tertiaryText : sizeClasses.value.text,
146
+ ])
86
147
 
87
148
  const fieldClasses = computed(() => {
88
149
  const base = [
89
150
  'focus-indicator-none',
90
- 'group',
151
+ 'group/trigger',
91
152
  'flex',
92
153
  'items-center',
93
154
  'justify-between',
94
155
  'w-full',
95
- 'rounded-lg',
156
+ shape.value.radius,
157
+ shape.value.height,
96
158
  'border',
97
159
  'border-solid',
98
160
  'transition-all',
@@ -102,12 +164,11 @@ const fieldClasses = computed(() => {
102
164
  'cursor-pointer',
103
165
  'font-medium',
104
166
  'focus:outline-hidden',
105
- 'focus-visible:ring-1',
106
- 'focus-visible:ring-inset',
167
+ 'focus-visible:focus-ring',
107
168
  // Hover overlay via ::before
108
169
  "before:content-['']",
109
170
  'before:absolute',
110
- 'before:inset-0',
171
+ 'before:-inset-px',
111
172
  'before:rounded-[inherit]',
112
173
  'before:opacity-0',
113
174
  'before:transition-opacity',
@@ -128,33 +189,29 @@ const fieldClasses = computed(() => {
128
189
  state.push('opacity-50', '!cursor-not-allowed')
129
190
  }
130
191
 
131
- // Background / border / hover overlay all come from `useButtonStyles`, keyed
132
- // by `variant` × `buttonColor` — the exact tokens `UiButton` uses. Tertiary
192
+ // Background / hover overlay come from `useButtonStyles`, keyed by
193
+ // `variant` × `buttonColor` — the exact tokens `UiButton` uses. Tertiary
133
194
  // resolves to no border/bg (transparent), secondary to the bordered field.
134
195
  if (props.variant === 'tertiary') {
135
196
  state.push('border-transparent', 'bg-transparent')
136
197
  }
137
198
  else {
138
- state.push(bgClass.value, ...borderClasses.value)
199
+ state.push(fieldBgClass.value, borderClass.value)
139
200
  }
140
201
  if (beforeHoverOverlayClass.value) state.push(beforeHoverOverlayClass.value)
141
202
 
142
- // Focus ring + open-state border are trigger-specific (a button has no
143
- // open state). Error keeps its danger-colored focus ring; everything else
144
- // uses the standard primary focus highlight.
203
+ // The open-state border is trigger-specific: a button has no open state.
145
204
  if (props.error) {
146
- state.push(
147
- 'focus-visible:border-borders-danger-strong',
148
- 'focus-visible:ring-borders-danger-strong',
149
- 'data-[state=open]:border-borders-danger-strong',
150
- )
205
+ state.push('data-[state=open]:border-control-error-border')
151
206
  }
152
207
  else {
153
- state.push('focus-visible:ring-focus-ring')
154
208
  if (props.variant !== 'tertiary') {
209
+ state.push('data-[state=open]:border-control-field-border-focus')
210
+ }
211
+ if (props.variant !== 'tertiary' && buttonColor.value === 'neutral') {
155
212
  state.push(
156
- 'focus-visible:border-actions-primary-idle-alpha-strong',
157
- 'data-[state=open]:border-actions-primary-idle-alpha-strong',
213
+ 'focus-visible:bg-control-field-bg-focus',
214
+ 'data-[state=open]:bg-control-field-bg-focus',
158
215
  )
159
216
  }
160
217
  }
@@ -174,10 +231,12 @@ const textClasses = computed(() => {
174
231
  : isPrimary.value ? 'text-actions-primary-idle' : 'text-text-neutral-base'
175
232
  // Placeholder color: primary (incl. tertiary-primary) keeps the primary
176
233
  // token so an empty filter chip still reads as actionable; everything else
177
- // uses the muted placeholder token.
178
- const placeholderClass = (isPrimary.value || (isTertiary.value && props.color === 'primary'))
179
- ? 'group-data-placeholder:text-actions-primary-idle'
180
- : 'group-data-placeholder:text-text-neutral-tertiary'
234
+ // uses the muted placeholder token. A `value` slot renders a value, never the placeholder.
235
+ const placeholderClass = slots.value
236
+ ? ''
237
+ : (isPrimary.value || (isTertiary.value && props.color === 'primary'))
238
+ ? 'group-data-placeholder/trigger:text-actions-primary-idle'
239
+ : 'group-data-placeholder/trigger:text-control-placeholder'
181
240
  return [
182
241
  'flex-1',
183
242
  'overflow-hidden',
@@ -187,7 +246,7 @@ const textClasses = computed(() => {
187
246
  'text-left',
188
247
  colorClass,
189
248
  placeholderClass,
190
- sizeClasses.value.text,
249
+ isTertiary.value ? sizeClasses.value.tertiaryText : sizeClasses.value.text,
191
250
  ].join(' ')
192
251
  })
193
252
 
@@ -203,17 +262,53 @@ const iconContainerClasses = computed(() => {
203
262
  isTertiary.value
204
263
  ? textClass.value
205
264
  : isPrimary.value ? 'text-actions-primary-idle' : 'text-text-neutral-tertiary',
206
- iconSizeClass.value,
265
+ shape.value.chevron,
207
266
  ].join(' ')
208
267
  })
209
268
  </script>
210
269
 
211
270
  <template>
271
+ <span
272
+ v-if="readonly"
273
+ data-testid="ui-dropdown-trigger-readonly"
274
+ role="group"
275
+ v-bind="$attrs"
276
+ :class="[boxClasses, slots.leading ? sizeClasses.leadingPadding : sizeClasses.padding]"
277
+ >
278
+ <span
279
+ v-if="slots.leading"
280
+ data-testid="ui-dropdown-trigger-leading"
281
+ class="flex items-center justify-center shrink-0"
282
+ :class="sizeClasses.leadingBox"
283
+ >
284
+ <slot name="leading" />
285
+ </span>
286
+ <div :class="readonlyTextClasses">
287
+ <slot name="value">
288
+ {{ placeholder }}
289
+ </slot>
290
+ </div>
291
+ <span
292
+ v-if="showIcon"
293
+ class="shrink-0"
294
+ :class="shape.chevron"
295
+ aria-hidden="true"
296
+ />
297
+ </span>
212
298
  <SelectTrigger
299
+ v-else
213
300
  v-bind="$attrs"
214
- :class="fieldClasses"
301
+ :class="[fieldClasses, slots.leading ? sizeClasses.leadingPadding : sizeClasses.padding]"
215
302
  :disabled="disabled"
216
303
  >
304
+ <span
305
+ v-if="slots.leading"
306
+ data-testid="ui-dropdown-trigger-leading"
307
+ class="flex items-center justify-center shrink-0"
308
+ :class="sizeClasses.leadingBox"
309
+ >
310
+ <slot name="leading" />
311
+ </span>
217
312
  <div :class="textClasses">
218
313
  <SelectValue
219
314
  v-if="!slots.value"
@@ -230,8 +325,8 @@ const iconContainerClasses = computed(() => {
230
325
  :class="iconContainerClasses"
231
326
  >
232
327
  <div
233
- class="i-ph-caret-down transition-transform duration-200 ease-in-out group-data-[state=open]:rotate-180"
234
- :class="iconSizeClass"
328
+ class="i-ph-caret-down transition-transform duration-200 ease-in-out group-data-[state=open]/trigger:rotate-180"
329
+ :class="shape.chevron"
235
330
  />
236
331
  </SelectIcon>
237
332
  </SelectTrigger>
@@ -3,6 +3,7 @@ export { default as UiDropdown } from './UiDropdown.vue'
3
3
  export { default as UiDropdownTrigger } from './UiDropdownTrigger.vue'
4
4
  export { default as UiDropdownContent } from './UiDropdownContent.vue'
5
5
  export { default as UiDropdownItem } from './UiDropdownItem.vue'
6
+ export { default as UiDropdownGroup } from './UiDropdownGroup.vue'
6
7
  export { default as UiDropdownSeparator } from './UiDropdownSeparator.vue'
7
8
 
8
9
  // Re-export useful Reka UI types and components for advanced usage
@@ -0,0 +1,122 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+ import { useStorage } from '@vueuse/core'
4
+ import EmojiPicker from 'vue-frimousse'
5
+ import { useControlShape } from '../../composables/useControlShape'
6
+
7
+ interface UiEmojiPickerListProps {
8
+ locale?: string
9
+ searchPlaceholder?: string
10
+ loadingLabel?: string
11
+ emptyLabel?: string
12
+ previewPlaceholder?: string
13
+ skinToneLabel?: string
14
+ }
15
+
16
+ const props = withDefaults(defineProps<UiEmojiPickerListProps>(), {
17
+ locale: 'en',
18
+ searchPlaceholder: 'Search emoji...',
19
+ loadingLabel: 'Loading...',
20
+ emptyLabel: 'No emoji found',
21
+ previewPlaceholder: 'Change emoji skintone',
22
+ skinToneLabel: 'Change skin tone',
23
+ })
24
+
25
+ const emit = defineEmits<{ select: [emoji: string] }>()
26
+
27
+ type SkinTone = 'none' | 'light' | 'medium-light' | 'medium' | 'medium-dark' | 'dark'
28
+ const skinTones: SkinTone[] = ['none', 'light', 'medium-light', 'medium', 'medium-dark', 'dark']
29
+ const preferredSkinTone = useStorage<SkinTone>('livestorm:emoji-skin-tone', 'none')
30
+
31
+ const searchShape = useControlShape('field', 'sm')
32
+
33
+ const locale = computed(() => props.locale.toLowerCase().replace('_', '-'))
34
+
35
+ function selectEmoji (emoji: { emoji: string }): void {
36
+ emit('select', emoji.emoji)
37
+ }
38
+
39
+ function cycleSkinTone (): void {
40
+ const currentIndex = skinTones.indexOf(preferredSkinTone.value)
41
+ preferredSkinTone.value = skinTones[(currentIndex + 1) % skinTones.length] ?? 'none'
42
+ }
43
+ </script>
44
+
45
+ <template>
46
+ <EmojiPicker.Root
47
+ :locale="locale"
48
+ :columns="9"
49
+ :skin-tone="preferredSkinTone"
50
+ :on-emoji-select="selectEmoji"
51
+ class="flex flex-col gap-2"
52
+ >
53
+ <EmojiPicker.Search
54
+ class="w-full border border-borders-neutral-default bg-surface-neutral-bg-main px-2 text-content-legends-medium-md text-text-neutral-base outline-none placeholder:text-text-neutral-tertiary focus-visible:focus-ring"
55
+ :class="[searchShape.height, searchShape.radius]"
56
+ :placeholder="searchPlaceholder"
57
+ />
58
+
59
+ <EmojiPicker.Viewport class="box-border h-64 pr-2">
60
+ <EmojiPicker.Loading class="flex h-64 items-center justify-center text-content-legends-medium-md text-text-neutral-tertiary">
61
+ {{ loadingLabel }}
62
+ </EmojiPicker.Loading>
63
+ <EmojiPicker.Empty class="flex h-64 items-center justify-center text-content-legends-medium-md text-text-neutral-tertiary">
64
+ {{ emptyLabel }}
65
+ </EmojiPicker.Empty>
66
+ <EmojiPicker.List class="flex w-full flex-col gap-2">
67
+ <template #category-header="{ category }">
68
+ <div class="bg-surface-neutral-bg-main py-1 text-content-legends-bold-md text-text-neutral-secondary">
69
+ {{ category.label }}
70
+ </div>
71
+ </template>
72
+
73
+ <template #emoji="{ emoji }">
74
+ <button
75
+ type="button"
76
+ :aria-label="emoji.label"
77
+ class="flex size-button-sm w-full! items-center justify-center rounded-button-sm text-xl leading-none hover:bg-actions-neutral-hover-overlay data-[active]:bg-actions-neutral-hover-overlay"
78
+ >
79
+ {{ emoji.emoji }}
80
+ </button>
81
+ </template>
82
+ </EmojiPicker.List>
83
+ </EmojiPicker.Viewport>
84
+
85
+ <div class="flex h-10 items-center justify-between gap-2 border-t border-divider pt-2">
86
+ <EmojiPicker.ActiveEmoji>
87
+ <template #default="{ emoji }">
88
+ <span
89
+ v-if="emoji"
90
+ class="flex h-6 min-w-0 flex-1 items-center gap-2 text-content-legends-medium-md text-text-neutral-secondary"
91
+ >
92
+ <span class="flex size-6 shrink-0 items-center justify-center text-xl leading-none">{{ emoji.emoji }}</span>
93
+ <span class="min-w-0 truncate">{{ emoji.label }}</span>
94
+ </span>
95
+ <span
96
+ v-else
97
+ class="flex h-6 min-w-0 flex-1 items-center text-content-legends-medium-md text-text-neutral-secondary"
98
+ >{{ previewPlaceholder }}</span>
99
+ </template>
100
+ </EmojiPicker.ActiveEmoji>
101
+
102
+ <EmojiPicker.SkinToneSelector
103
+ class="flex size-button-sm shrink-0 items-center justify-center rounded-button-sm text-xl leading-none hover:bg-actions-neutral-hover-overlay"
104
+ :aria-label="skinToneLabel"
105
+ @click="cycleSkinTone"
106
+ />
107
+ </div>
108
+ </EmojiPicker.Root>
109
+ </template>
110
+
111
+ <style scoped>
112
+ :deep([frimousse-row]) {
113
+ display: grid !important;
114
+ width: 100%;
115
+ grid-template-columns: repeat(9, minmax(0, 1fr));
116
+ gap: 0.125rem;
117
+ }
118
+
119
+ :deep([frimousse-row] > [frimousse-emoji]) {
120
+ min-width: 0;
121
+ }
122
+ </style>
@@ -1,6 +1,7 @@
1
1
  <!-- UiEmptyState.vue - Empty state component for showing placeholder content with optional illustration and actions. -->
2
2
  <script setup lang="ts">
3
3
  import { computed, useSlots } from 'vue'
4
+ import { DotLottieVue } from '@lottiefiles/dotlottie-vue'
4
5
 
5
6
  export interface UiEmptyStateProps {
6
7
  /** Title shown as the primary heading */
@@ -9,6 +10,8 @@ export interface UiEmptyStateProps {
9
10
  description?: string
10
11
  /** Optional illustration image URL */
11
12
  image?: string
13
+ /** Optional .lottie animation URL, takes precedence over `image` */
14
+ lottieSrc?: string
12
15
  /** Layout orientation. `vertical` stacks the illustration above the text; `horizontal` places it to the right. */
13
16
  variant?: 'vertical' | 'horizontal'
14
17
  }
@@ -16,6 +19,7 @@ export interface UiEmptyStateProps {
16
19
  const props = withDefaults(defineProps<UiEmptyStateProps>(), {
17
20
  description: undefined,
18
21
  image: undefined,
22
+ lottieSrc: undefined,
19
23
  variant: 'vertical',
20
24
  })
21
25
 
@@ -32,8 +36,14 @@ const isHorizontal = computed(() => props.variant === 'horizontal')
32
36
  ? 'flex-row items-center max-w-[576px] min-w-[384px]'
33
37
  : 'flex-col items-center max-w-[320px] min-w-[256px]'"
34
38
  >
39
+ <DotLottieVue
40
+ v-if="lottieSrc && !isHorizontal"
41
+ :src="lottieSrc"
42
+ autoplay
43
+ class="w-[270px]"
44
+ />
35
45
  <img
36
- v-if="image && !isHorizontal"
46
+ v-else-if="image && !isHorizontal"
37
47
  :src="image"
38
48
  alt=""
39
49
  class="w-[270px]"
@@ -51,10 +61,12 @@ const isHorizontal = computed(() => props.variant === 'horizontal')
51
61
  {{ title }}
52
62
  </h3>
53
63
  <p
54
- v-if="description"
55
- class="text-content-text-md-regular text-text-neutral-tertiary"
64
+ v-if="description || $slots.description"
65
+ class="text-content-text-md-medium text-text-neutral-tertiary"
56
66
  >
57
- {{ description }}
67
+ <slot name="description">
68
+ {{ description }}
69
+ </slot>
58
70
  </p>
59
71
  </div>
60
72
 
@@ -45,7 +45,7 @@ defineExpose({ fieldId })
45
45
  >
46
46
  <Label
47
47
  :for="fieldId"
48
- class="text-content-legends-bold-md text-text-neutral-tertiary"
48
+ class="text-content-legends-bold-md text-text-neutral-secondary"
49
49
  >
50
50
  {{ label }}{{ required ? '*' : '' }}
51
51
  </Label>
@@ -70,9 +70,15 @@ defineExpose({ fieldId })
70
70
  <p
71
71
  v-if="displayMessage"
72
72
  :id="`${fieldId}-message`"
73
- class="text-content-legends-bold-md"
74
- :class="error ? 'text-text-danger-secondary' : 'text-text-neutral-secondary'"
73
+ class="flex items-center gap-1 text-content-legends-bold-md"
74
+ :class="error ? 'text-text-danger-body' : 'text-text-neutral-secondary'"
75
75
  >
76
+ <span
77
+ v-if="error"
78
+ class="i-ph-warning-circle size-4 shrink-0"
79
+ data-testid="ui-field-wrapper-error-icon"
80
+ aria-hidden="true"
81
+ />
76
82
  {{ displayMessage }}
77
83
  </p>
78
84
  <slot name="footer" />