@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,12 +1,20 @@
1
- import { ref } from 'vue'
2
- import type { Component } from 'vue'
1
+ import { markRaw, ref } from 'vue'
2
+ import type { Component, Ref } from 'vue'
3
3
  import type { RouteLocationRaw } from 'vue-router'
4
4
  import { createEventHook, type EventHook } from '@vueuse/core'
5
5
 
6
6
  import type { ThemeColorType } from '../../theme'
7
+ import type { Size } from '../../constants'
7
8
 
8
9
  let uid = 1
9
10
 
11
+ /**
12
+ * How many toasts render at once. It lives here rather than in `LayoutToasts`
13
+ * because the store schedules the close timers, and only the toasts actually
14
+ * on screen may run one.
15
+ */
16
+ export const MAX_ITEMS_DISPLAYED = 3
17
+
10
18
  export type ToastType = 'alert'|'warning'|'info'|'success'
11
19
 
12
20
  const DEFAULT_TIMER_DURATION_PER_TYPE: Record<Extract<ToastType, 'success'|'info'>, number> = {
@@ -14,14 +22,39 @@ const DEFAULT_TIMER_DURATION_PER_TYPE: Record<Extract<ToastType, 'success'|'info
14
22
  info: 7 * 1000,
15
23
  }
16
24
 
17
- export interface ToastOptions {
18
- message: string
25
+ /**
26
+ * Identity shown in place of the status icon. Size and variant are fixed by
27
+ * the design, so only the identity-bearing `UiAvatar` props are exposed.
28
+ */
29
+ export interface ToastAvatar {
30
+ src?: string | null
31
+ firstName?: string | null
32
+ lastName?: string | null
33
+ customText?: string | null
34
+ color?: string | null
35
+ alt?: string
36
+ }
37
+
38
+ /**
39
+ * Every field a toast understands, all optional. `openToast` narrows this to
40
+ * `ToastOptions`, which additionally demands something to display.
41
+ */
42
+ export interface ToastBaseOptions {
43
+ message?: string
44
+ /**
45
+ * Rendered in place of `message`, inside the default layout — `component`
46
+ * replaces the whole body instead. Markup, and sanitizing it, stays with
47
+ * the caller.
48
+ */
49
+ messageComponent?: Component
50
+ messageComponentProps?: Record<string, unknown>
19
51
  id?: PropertyKey
20
52
  title?: string
21
53
  info?: string
22
54
  type?: ThemeColorType
55
+ avatar?: ToastAvatar
23
56
  time?: number | boolean // if time is explicitly false, the toast will not auto-close
24
- progessIndeterminate?: boolean
57
+ progressIndeterminate?: boolean
25
58
  customIcon?: string
26
59
  confirmLabel?: string
27
60
  confirmHref?: string
@@ -29,6 +62,7 @@ export interface ToastOptions {
29
62
  confirmTo?: RouteLocationRaw
30
63
  confirmIcon?: string
31
64
  confirmColor?: ThemeColorType
65
+ actionsSize?: Size
32
66
  cancelLabel?: string
33
67
  locked?: boolean
34
68
  closable?: boolean
@@ -36,116 +70,253 @@ export interface ToastOptions {
36
70
  componentProps?: Record<string, unknown>
37
71
  }
38
72
 
73
+ /**
74
+ * A toast has to show something. Requiring one of the three content fields
75
+ * here means an empty toast is a type error rather than an empty white box.
76
+ */
77
+ export type ToastContent =
78
+ | { message: string }
79
+ | { messageComponent: Component }
80
+ | { title: string }
81
+ | { component: Component }
82
+
83
+ export type ToastOptions = ToastBaseOptions & ToastContent
84
+
85
+ /**
86
+ * The caller's options as the store keeps them: `time` has been resolved to
87
+ * milliseconds, so nothing downstream has to handle the `true` shorthand.
88
+ */
89
+ type StoredToastOptions = Omit<ToastBaseOptions, 'time'> & { time?: number }
90
+
91
+ /**
92
+ * A toast on screen: what the caller asked for, kept in `options`, plus the
93
+ * bookkeeping the store needs alongside it. They stay apart so `LayoutToasts`
94
+ * can hand the component the options without the timer handle and the event
95
+ * hooks falling through onto the DOM.
96
+ */
39
97
  interface ToastItem {
40
98
  id: PropertyKey
41
- message: string
42
- title?: string
43
- info?: string
44
- type?: ThemeColorType
45
- icon?: string
46
- time?: number
47
- locked?: boolean
48
- progessIndeterminate?: boolean
99
+ options: StoredToastOptions
49
100
  closeTimer: any
101
+ // The pointer is resting on this toast, so it must not be given a timer
102
+ // back when the visible window is recomputed.
103
+ paused: boolean
104
+ // When the current run of the close timer started. The progress bar animation
105
+ // is component state, so without this it has no way to observe a restart.
106
+ timerStartedAt: number
50
107
  confirmEventHook: EventHook
51
108
  cancelEventHook: EventHook
52
109
  }
53
110
 
54
- const toasts = ref<ToastItem[]>([])
111
+ /**
112
+ * `true` picks the per-type default; a number is used as-is; `false` (or
113
+ * unset) means no auto-close — normalized to undefined so the timer is never
114
+ * started, and so nothing downstream has to divide a boolean by 1000.
115
+ */
116
+ function resolveTime(time: ToastBaseOptions['time'], type: ThemeColorType): number | undefined {
117
+ const typeWithTimer = type === 'success' || type === 'info'
118
+
119
+ return time === true
120
+ ? (typeWithTimer ? DEFAULT_TIMER_DURATION_PER_TYPE[type] : undefined)
121
+ : (typeof time === 'number' ? time : undefined)
122
+ }
123
+
124
+ // Cast past `UnwrapRef`, which mangles the `Component` in `options` into a
125
+ // shape that no longer matches ToastItem. Nothing here holds a nested ref, so
126
+ // the unwrapped type and the declared one are the same at runtime.
127
+ const toasts = ref<ToastItem[]>([]) as Ref<ToastItem[]>
55
128
 
56
- export function useToasts () {
129
+ export function useToasts() {
57
130
  return {
58
131
  toasts,
59
- closeToast,
60
132
  openToast,
133
+ updateToast,
134
+ restartToast,
135
+ closeToast,
61
136
  cancelCloseTimer,
62
137
  }
63
138
  }
64
139
 
65
- export function openToast (options: ToastOptions) {
66
- let id: PropertyKey
67
- let item = toasts.value.find(item => item.id === options.id)
68
- const confirmEventHook = createEventHook<void>()
69
- const cancelEventHook = createEventHook<void>()
70
-
71
- if (item) {
72
- id = item.id
73
- Object.assign(item, {
74
- ...options,
75
- })
76
-
77
- const restartTimer = options.id && Object.keys(options).length === 1
78
- if (restartTimer) {
79
- startTimer(item)
80
- }
81
- } else {
82
- id = options.id ?? uid++
83
-
84
- const type = options.type || 'info'
85
- const typeWithTimer = type === 'success' || type === 'info'
86
- // `true` picks the per-type default; a number is used as-is; `false`
87
- // (or unset) means no auto-close — normalized to undefined so the timer
88
- // is never started.
89
- const time = options.time === true
90
- ? (typeWithTimer ? DEFAULT_TIMER_DURATION_PER_TYPE[type] : undefined)
91
- : (typeof options.time === 'number' ? options.time : undefined)
92
- item = {
93
- ...options,
94
- id,
95
- closeTimer: null,
96
- confirmEventHook,
97
- cancelEventHook,
98
- time,
99
- type,
100
- }
101
-
102
- toasts.value.push(item)
140
+ /** What a caller gets back for the toast it just opened or patched. */
141
+ interface ToastHandle {
142
+ id: PropertyKey
143
+ close: () => void
144
+ onConfirm: EventHook['on']
145
+ onCancel: EventHook['on']
146
+ }
103
147
 
104
- startTimer(item)
105
- }
148
+ function toastHandle(item: ToastItem): ToastHandle {
149
+ const { id } = item
106
150
 
107
151
  return {
108
152
  id,
109
153
  close: () => {
110
154
  closeToast(id)
111
155
  },
112
- onConfirm: confirmEventHook.on,
113
- onCancel: cancelEventHook.on,
156
+ // Read off the item, so a patched toast hands back the hooks it was
157
+ // opened with rather than a fresh pair nothing would ever trigger.
158
+ onConfirm: item.confirmEventHook.on,
159
+ onCancel: item.cancelEventHook.on,
160
+ }
161
+ }
162
+
163
+ /**
164
+ * The store is a deep ref, so a component definition kept in it would be
165
+ * proxied on every render — Vue warns about exactly that.
166
+ */
167
+ function withRawComponents<T extends ToastBaseOptions>(options: T): T {
168
+ const { component, messageComponent } = options
169
+
170
+ return {
171
+ ...options,
172
+ ...(component && { component: markRaw(component) }),
173
+ ...(messageComponent && { messageComponent: markRaw(messageComponent) }),
114
174
  }
115
175
  }
116
176
 
117
- export function closeToast (id: PropertyKey) {
177
+ function applyPatch(item: ToastItem, patch: Omit<ToastBaseOptions, 'id'>) {
178
+ const { time, ...rest } = patch
179
+ Object.assign(item.options, withRawComponents(rest))
180
+
181
+ // Patching goes through the same normalization as opening, so the store only
182
+ // ever holds milliseconds and the progress bar never divides a boolean.
183
+ if (time !== undefined) {
184
+ item.options.time = resolveTime(time, item.options.type ?? 'info')
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Opens a toast. An `id` is an identity, so opening one that is already on
190
+ * screen patches it in place instead of stacking a second copy — that is what
191
+ * the room's per-user and per-link toasts rely on. It does not restart the
192
+ * close timer; call `restartToast` for that.
193
+ */
194
+ export function openToast(options: ToastOptions): ToastHandle {
195
+ const open = toasts.value.find(item => item.id === options.id)
196
+ if (open) {
197
+ // Re-opening is a fresh occurrence of the same identity, and the caller is
198
+ // about to bind the handlers for it. Without dropping the previous ones,
199
+ // every re-open leaves another subscriber on hooks nothing ever clears.
200
+ open.confirmEventHook.clear()
201
+ open.cancelEventHook.clear()
202
+ applyPatch(open, options)
203
+
204
+ return toastHandle(open)
205
+ }
206
+
207
+ const type = options.type || 'info'
208
+ const id = options.id ?? uid++
209
+ const item: ToastItem = {
210
+ id,
211
+ options: {
212
+ ...withRawComponents(options),
213
+ id,
214
+ type,
215
+ time: resolveTime(options.time, type),
216
+ },
217
+ closeTimer: null,
218
+ paused: false,
219
+ timerStartedAt: 0,
220
+ // Created with the toast, not on every call, so the hooks outlive a patch.
221
+ confirmEventHook: createEventHook<void>(),
222
+ cancelEventHook: createEventHook<void>(),
223
+ }
224
+
225
+ toasts.value.push(item)
226
+ syncTimers()
227
+
228
+ return toastHandle(item)
229
+ }
230
+
231
+ /**
232
+ * Patches an open toast. Unlike `openToast` it needs no content, so a caller
233
+ * can change one field. Returns undefined if the toast has already closed.
234
+ */
235
+ export function updateToast(id: PropertyKey, patch: Omit<ToastBaseOptions, 'id'>): ToastHandle | undefined {
236
+ const item = toasts.value.find(item => item.id === id)
237
+ if (!item) return
238
+
239
+ applyPatch(item, patch)
240
+
241
+ return toastHandle(item)
242
+ }
243
+
244
+ /** Runs the close timer again from zero, e.g. when the pointer leaves. */
245
+ export function restartToast(id: PropertyKey) {
246
+ const item = toasts.value.find(item => item.id === id)
247
+ if (!item) return
248
+
249
+ item.paused = false
250
+ startTimer(item)
251
+ }
252
+
253
+ export function closeToast(id: PropertyKey) {
118
254
  const item = toasts.value.find(item => item.id === id)
119
255
  if (item) {
120
- clearTimeout(item.closeTimer)
256
+ stopTimer(item)
121
257
  removeToast(id)
258
+ // Closing one frees a slot, so whatever was queued behind it can start
259
+ // counting down now that it is on screen.
260
+ syncTimers()
122
261
  }
123
262
  }
124
263
 
125
- export function cancelCloseTimer (id: PropertyKey) {
264
+ export function cancelCloseTimer(id: PropertyKey) {
126
265
  const item = toasts.value.find(item => item.id === id)
127
266
  if (item) {
128
- clearTimeout(item.closeTimer)
267
+ item.paused = true
268
+ stopTimer(item)
129
269
  }
130
270
  }
131
271
 
132
- export function resetToasts () {
272
+ export function resetToasts() {
273
+ toasts.value.forEach(stopTimer)
133
274
  toasts.value = []
134
275
  }
135
276
 
136
- function removeToast (id: PropertyKey) {
277
+ function removeToast(id: PropertyKey) {
137
278
  const index = toasts.value.findIndex(item => item.id === id)
138
279
  if (index !== -1) {
139
280
  toasts.value.splice(index, 1)
140
281
  }
141
282
  }
142
283
 
143
- function startTimer (item: ToastItem): void {
144
- if (item.time) {
284
+ /**
285
+ * Gives a close timer to every toast on screen and takes it away from the ones
286
+ * queued behind them. Without this a queued toast counts down while nothing is
287
+ * rendered and can be removed before it is ever seen.
288
+ */
289
+ function syncTimers(): void {
290
+ toasts.value.forEach((item, index) => {
291
+ const onScreen = index < MAX_ITEMS_DISPLAYED
292
+
293
+ if (!onScreen || item.paused) {
294
+ stopTimer(item)
295
+ return
296
+ }
297
+
298
+ // Guarded so resettling the window never restarts a countdown that is
299
+ // already running, which would also replay the progress bar.
300
+ if (!item.closeTimer) startTimer(item)
301
+ })
302
+ }
303
+
304
+ function stopTimer(item: ToastItem): void {
305
+ clearTimeout(item.closeTimer)
306
+ item.closeTimer = null
307
+ }
308
+
309
+ function startTimer(item: ToastItem): void {
310
+ // Idempotent: without this a restart would orphan the previous timeout,
311
+ // which would go on to close the toast early.
312
+ stopTimer(item)
313
+
314
+ if (item.options.time) {
315
+ item.timerStartedAt = performance.now()
145
316
  item.closeTimer = setTimeout(
146
317
  () => {
147
318
  closeToast(item.id)
148
- }, item.time
319
+ }, item.options.time
149
320
  )
150
321
  }
151
322
  }
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, provide } from 'vue'
3
3
  import type { ActionBarOrientation, ActionBarPosition, ActionBarSize, ActionBarTooltipSide } from './types'
4
- import { ACTION_BAR_CONTEXT_KEY } from './types'
4
+ import { ACTION_BAR_CONTEXT_KEY, ACTION_BAR_RAIL_RADIUS } from './types'
5
5
 
6
6
  interface UiActionBarProps {
7
7
  /**
@@ -54,6 +54,7 @@ const SIZE_CLASSES: Record<ActionBarSize, string> = {
54
54
  }
55
55
 
56
56
  const sizeClasses = computed(() => SIZE_CLASSES[props.size])
57
+ const railRadiusClass = computed(() => ACTION_BAR_RAIL_RADIUS[props.size])
57
58
 
58
59
  // When `position` is set, anchor the bar to an edge of its positioned
59
60
  // ancestor. Offsets are inline styles (rem) so any `offset` value works
@@ -86,8 +87,8 @@ const positionStyle = computed<Record<string, string>>(() => {
86
87
  :aria-label="ariaLabel"
87
88
  :aria-orientation="orientation"
88
89
  data-testid="ui-action-bar"
89
- class="inline-flex items-center bg-surface-neutral-bg-main rounded-xl border border-borders-neutral-light shadow-lg"
90
- :class="[sizeClasses, { 'flex-col': isVertical, 'absolute z-10': position }]"
90
+ class="inline-flex items-center bg-surface-neutral-bg-main border border-container-border shadow-lg"
91
+ :class="[sizeClasses, railRadiusClass, { 'flex-col': isVertical, 'absolute z-10': position }]"
91
92
  :style="positionStyle"
92
93
  >
93
94
  <slot />
@@ -11,7 +11,7 @@ import {
11
11
  import type { RouteLocationRaw } from 'vue-router'
12
12
  import { useIconSize } from '../../composables/useIconSize'
13
13
  import type { ActionBarSize } from './types'
14
- import { ACTION_BAR_CONTEXT_KEY } from './types'
14
+ import { ACTION_BAR_CONTEXT_KEY, ACTION_BAR_ITEM_RADIUS } from './types'
15
15
 
16
16
  interface UiActionBarItemProps {
17
17
  /**
@@ -101,6 +101,9 @@ const iconSizeClass = useIconSize(size)
101
101
  const itemClasses = computed(() => [
102
102
  props.showLabel ? sizeConfig.value.padded : sizeConfig.value.square,
103
103
  sizeConfig.value.text,
104
+ ACTION_BAR_ITEM_RADIUS[size.value],
105
+ // Labeled items in a vertical rail stretch so their icons align.
106
+ props.showLabel && orientation.value === 'vertical' ? 'w-full justify-start' : 'justify-center',
104
107
  {
105
108
  'bg-actions-neutral-active-overlay': props.active === true,
106
109
  'cursor-pointer': !props.disabled,
@@ -134,10 +137,10 @@ function onClick (event: MouseEvent) {
134
137
  :aria-label="label"
135
138
  :aria-pressed="active"
136
139
  :data-testid="testId"
137
- class="group inline-flex items-center justify-center rounded-lg shrink-0 outline-none transition-colors
140
+ class="group inline-flex items-center shrink-0 outline-none transition-colors
138
141
  text-actions-neutral-idle
139
142
  hover:bg-actions-neutral-hover-overlay
140
- focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus-ring
143
+ focus-visible:focus-ring
141
144
  disabled:opacity-50 disabled:cursor-not-allowed"
142
145
  :class="itemClasses"
143
146
  @click="onClick"
@@ -156,7 +159,7 @@ function onClick (event: MouseEvent) {
156
159
  :side="tooltipSide"
157
160
  :side-offset="8"
158
161
  data-colors-semantic="light"
159
- class="inline-flex items-center justify-center gap-1.5 py-1.5 px-3 bg-surface-neutral-invert-bg-alpha text-text-neutral-invert-base text-content-text-md-regular rounded-md text-center z-50 shadow-shadows-drop-shadow-lg
162
+ class="inline-flex items-center justify-center gap-1.5 py-1.5 px-3 bg-surface-neutral-invert-bg-alpha text-text-neutral-invert-base text-content-text-md-regular rounded-row text-center z-overlay shadow-shadows-drop-shadow-lg
160
163
  data-[state=delayed-open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=delayed-open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=delayed-open]:zoom-in-95"
161
164
  >
162
165
  {{ label }}
@@ -183,10 +186,10 @@ function onClick (event: MouseEvent) {
183
186
  :aria-label="label"
184
187
  :aria-pressed="active"
185
188
  :data-testid="testId"
186
- class="group inline-flex items-center justify-center rounded-lg shrink-0 outline-none transition-colors
189
+ class="group inline-flex items-center shrink-0 outline-none transition-colors
187
190
  text-actions-neutral-idle
188
191
  hover:bg-actions-neutral-hover-overlay
189
- focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-focus-ring
192
+ focus-visible:focus-ring
190
193
  disabled:opacity-50 disabled:cursor-not-allowed"
191
194
  :class="itemClasses"
192
195
  @click="onClick"
@@ -24,3 +24,16 @@ export interface ActionBarContext {
24
24
  */
25
25
  export const ACTION_BAR_CONTEXT_KEY: InjectionKey<ActionBarContext> =
26
26
  Symbol('ui-action-bar-context')
27
+
28
+ export const ACTION_BAR_RAIL_RADIUS: Record<ActionBarSize, string> = {
29
+ xs: 'rounded-button-sm',
30
+ sm: 'rounded-button-sm',
31
+ md: 'rounded-field-lg',
32
+ }
33
+
34
+ // Item radius stays concentric with the rail: rail radius minus rail padding and border.
35
+ export const ACTION_BAR_ITEM_RADIUS: Record<ActionBarSize, string> = {
36
+ xs: 'rounded-[calc(var(--radius-button-sm)_-_3px)]',
37
+ sm: 'rounded-[calc(var(--radius-button-sm)_-_3px)]',
38
+ md: 'rounded-[calc(var(--radius-field-lg)_-_5px)]',
39
+ }
@@ -4,6 +4,7 @@
4
4
  :style="hasBadge ? undefined : customStyle"
5
5
  :data-size="size"
6
6
  :data-variant="variant"
7
+ data-testid="ui-avatar"
7
8
  >
8
9
  <span
9
10
  :class="surfaceClasses"
@@ -38,6 +39,9 @@
38
39
  <script setup lang="ts">
39
40
  import { computed, ref, useSlots, watch } from 'vue'
40
41
 
42
+ /** `xs` 24 · `sm` 32 · `md` 40 · `lg` 48 · `xl` 64 · `10xl` 128 */
43
+ export type UiAvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '10xl'
44
+
41
45
  interface Props {
42
46
  /** Image URL. Initials are shown when missing or when the image fails to load. */
43
47
  src?: string | null
@@ -49,7 +53,7 @@ interface Props {
49
53
  * Avatar size. Maps to the `--spacing-component-*` tokens:
50
54
  * `xs` 24, `sm` 32, `md` 40, `lg` 48, `xl` 64, `10xl` 128.
51
55
  */
52
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '10xl'
56
+ size?: UiAvatarSize
53
57
  /**
54
58
  * `default` is light-on-dark (used standalone or inside an avatar list);
55
59
  * `more` is the light-grey `+N` chip used by `UiAvatarList` overflow.
@@ -90,7 +94,9 @@ const customStyle = computed(() => {
90
94
  if (props.variant === 'more') return undefined
91
95
  if (props.src && !imageFailed.value) return undefined
92
96
  if (!props.color) return undefined
93
- return { backgroundColor: props.color }
97
+ // A fixed custom color doesn't follow the semantic scope, so the ink must
98
+ // not either: the invert token would go dark-on-dark in the dark scope.
99
+ return { backgroundColor: props.color, color: 'white' }
94
100
  })
95
101
 
96
102
  const imageFailed = ref(false)
@@ -1,12 +1,12 @@
1
1
  <template>
2
2
  <div
3
- :class="['ui-avatar-list inline-flex items-center', containerPaddingClass]"
3
+ :class="['ui-avatar-list inline-flex items-center', cutoutClasses]"
4
4
  :data-size="size"
5
5
  >
6
6
  <div
7
7
  v-for="(item, idx) in visibleItems"
8
8
  :key="item.id ?? idx"
9
- :class="['flex border border-actions-neutral-invert-idle-alpha-strong rounded-full overflow-hidden', overlapClass]"
9
+ :class="[...ITEM_CLASSES, idx > 0 && overlapClass]"
10
10
  >
11
11
  <UiAvatar
12
12
  :src="item.src"
@@ -18,7 +18,8 @@
18
18
  </div>
19
19
  <div
20
20
  v-if="overflowCount > 0"
21
- :class="['flex border border-actions-neutral-invert-idle-alpha-strong rounded-full overflow-hidden', overlapClass]"
21
+ data-testid="ui-avatar-list-overflow"
22
+ :class="[...ITEM_CLASSES, visibleItems.length > 0 && overlapClass]"
22
23
  >
23
24
  <UiAvatar
24
25
  :custom-text="`+${overflowCount}`"
@@ -32,6 +33,7 @@
32
33
  <script setup lang="ts">
33
34
  import { computed } from 'vue'
34
35
  import UiAvatar from '../UiAvatar/UiAvatar.vue'
36
+ import type { UiAvatarSize } from '../UiAvatar/UiAvatar.vue'
35
37
 
36
38
  export interface AvatarListItem {
37
39
  id?: string | number
@@ -53,7 +55,7 @@ interface Props {
53
55
  /** Maximum visible avatars before showing the `+N` overflow chip. */
54
56
  max?: number
55
57
  /** Mirrors `UiAvatar`'s size scale. The overlap scales with the size. */
56
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '10xl'
58
+ size?: UiAvatarSize
57
59
  }
58
60
 
59
61
  const props = withDefaults(defineProps<Props>(), {
@@ -68,42 +70,71 @@ const visibleItems = computed(() => props.items.slice(0, props.max))
68
70
 
69
71
  const overflowCount = computed(() => Math.max(0, effectiveTotal.value - props.max))
70
72
 
71
- // Overlap scales with the avatar diameter so larger sizes don't look
72
- // disproportionately stacked. The container reserves matching right padding so
73
- // the rightmost avatar is fully visible.
73
+ const ITEM_CLASSES = ['ui-avatar-list__item', 'flex']
74
+
75
+ // Each avatar after the first is pulled back over its predecessor with a
76
+ // negative inline-start margin. The overlap scales with the diameter so larger
77
+ // sizes don't look disproportionately stacked.
78
+ //
79
+ // The margin deliberately sits on the items after the first, never on the last
80
+ // one. A trailing negative margin would make the root's content box narrower
81
+ // than what it paints, and any host rule that sizes the wrappers against it
82
+ // (DataView tables apply `max-width: 100%` to every `div` in a cell) would then
83
+ // shrink the wrapper and clip the avatar. With the overlap on the leading edge
84
+ // the root is exactly as wide as its content, so such rules are a no-op.
74
85
  const overlapClass = computed(() => {
75
86
  switch (props.size) {
76
87
  case 'sm':
77
- return '-mr-2'
88
+ return '-ms-2'
78
89
  case 'md':
79
- return '-mr-2.5'
90
+ return '-ms-2.5'
80
91
  case 'lg':
81
- return '-mr-3'
92
+ return '-ms-3'
82
93
  case 'xl':
83
- return '-mr-4'
94
+ return '-ms-4'
84
95
  case '10xl':
85
- return '-mr-8'
96
+ return '-ms-8'
86
97
  case 'xs':
87
98
  default:
88
- return '-mr-1.5'
99
+ return '-ms-1.5'
89
100
  }
90
101
  })
91
102
 
92
- const containerPaddingClass = computed(() => {
103
+ // Every avatar but the last is cut by the next one: a transparent hole the size
104
+ // of the next avatar plus a gap, so the stack reads on any background.
105
+ // `--avatar-step` is the distance between two avatar centres (diameter minus
106
+ // overlap), `--avatar-cutout` the hole radius (half diameter plus a gap of
107
+ // 0.5 spacing unit), both in `--spacing` units like the classes above.
108
+ const cutoutClasses = computed(() => {
93
109
  switch (props.size) {
94
110
  case 'sm':
95
- return 'pr-2'
111
+ return '[--avatar-step:calc(var(--spacing)*6)] [--avatar-cutout:calc(var(--spacing)*4.5)]'
96
112
  case 'md':
97
- return 'pr-2.5'
113
+ return '[--avatar-step:calc(var(--spacing)*7.5)] [--avatar-cutout:calc(var(--spacing)*5.5)]'
98
114
  case 'lg':
99
- return 'pr-3'
115
+ return '[--avatar-step:calc(var(--spacing)*9)] [--avatar-cutout:calc(var(--spacing)*6.5)]'
100
116
  case 'xl':
101
- return 'pr-4'
117
+ return '[--avatar-step:calc(var(--spacing)*12)] [--avatar-cutout:calc(var(--spacing)*8.5)]'
102
118
  case '10xl':
103
- return 'pr-8'
119
+ return '[--avatar-step:calc(var(--spacing)*24)] [--avatar-cutout:calc(var(--spacing)*16.5)]'
104
120
  case 'xs':
105
121
  default:
106
- return 'pr-1.5'
122
+ return '[--avatar-step:calc(var(--spacing)*4.5)] [--avatar-cutout:calc(var(--spacing)*3.5)]'
107
123
  }
108
124
  })
109
125
  </script>
126
+
127
+ <style scoped>
128
+ .ui-avatar-list__item:not(:last-child) {
129
+ --avatar-next: 1;
130
+ mask-image: radial-gradient(
131
+ circle at calc(50% + var(--avatar-next) * var(--avatar-step)) 50%,
132
+ transparent var(--avatar-cutout),
133
+ var(--color-surface-neutral-bg-main) calc(var(--avatar-cutout) + 0.5px)
134
+ );
135
+ }
136
+
137
+ .ui-avatar-list__item:dir(rtl) {
138
+ --avatar-next: -1;
139
+ }
140
+ </style>
@@ -80,7 +80,7 @@ const badgeClasses = computed(() => {
80
80
  'inline-flex',
81
81
  'items-center',
82
82
  'justify-center',
83
- 'rounded-2xl',
83
+ 'rounded-full',
84
84
  'shrink-0',
85
85
  // Only hint interactivity when a tooltip is attached by the consumer.
86
86
  props.hasTooltip ? 'cursor-help' : 'cursor-default',