@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
@@ -16,7 +16,10 @@
16
16
  </span>
17
17
 
18
18
  <!-- Label content -->
19
- <span class="ui-tag-label">
19
+ <span
20
+ v-if="$slots.default || label"
21
+ class="ui-tag-label"
22
+ >
20
23
  <slot>{{ label }}</slot>
21
24
  </span>
22
25
  </span>
@@ -25,30 +28,9 @@
25
28
  <script setup lang="ts">
26
29
  import { computed } from 'vue'
27
30
 
28
- interface Props {
29
- /**
30
- * Size of the tag
31
- */
32
- size?: 'xxs' | 'xs' | 'sm' | 'md'
33
- /**
34
- * Color variant of the tag
35
- */
36
- color?: 'primary' | 'neutral' | 'danger' | 'warning' | 'success' | 'neutral-invert'
37
- /**
38
- * Variant of the tag
39
- */
40
- variant?: 'light' | 'strong'
41
- /**
42
- * Label text for the tag
43
- */
44
- label?: string
45
- /**
46
- * Whether to show hover effects
47
- */
48
- withHoverEffect?: boolean
49
- }
31
+ import type { UiTagProps } from './UiTag.types'
50
32
 
51
- const props = withDefaults(defineProps<Props>(), {
33
+ const props = withDefaults(defineProps<UiTagProps>(), {
52
34
  size: 'md',
53
35
  color: 'neutral',
54
36
  variant: 'light',
@@ -61,7 +43,7 @@ const tagClasses = computed(() => {
61
43
  'ui-tag',
62
44
  'inline-flex',
63
45
  'items-center',
64
- 'rounded-3xl',
46
+ 'rounded-full',
65
47
  'transition-colors',
66
48
  'duration-150',
67
49
  'ease-in-out',
@@ -71,36 +53,36 @@ const tagClasses = computed(() => {
71
53
  // Size classes
72
54
  const sizeClasses = {
73
55
  xxs: ['px-1.5', 'gap-1', 'text-content-badges-bold-md', 'h-4'],
74
- xs: ['px-1.5', 'py-1', 'gap-1', 'text-content-badges-bold-md', 'h-6'],
75
- sm: ['px-2', 'py-1', 'gap-1', 'text-content-text-md-bold', 'h-8'],
76
- md: ['px-3', 'py-1', 'gap-2', 'text-content-text-md-bold', 'h-10'],
56
+ xs: ['px-2', 'py-1', 'gap-1', 'text-content-badges-bold-md', 'h-6'],
57
+ sm: ['px-3', 'py-1', 'gap-1', 'text-content-text-md-bold', 'h-8'],
58
+ md: ['px-4', 'py-1', 'gap-2', 'text-content-text-md-bold', 'h-10'],
77
59
  }
78
60
 
79
61
  // Color and variant classes
80
62
  const colorVariantClasses = {
81
63
  primary: {
82
- light: ['bg-actions-primary-active-overlay', 'text-actions-primary-idle'],
83
- strong: ['bg-actions-primary-idle-alpha-strong', 'text-actions-neutral-invert-idle'],
64
+ light: ['bg-actions-primary-active-overlay', 'text-text-primary-title'],
65
+ strong: ['bg-actions-primary-idle-alpha-strong', 'text-text-neutral-invert-base'],
84
66
  },
85
67
  neutral: {
86
- light: ['bg-actions-neutral-active-overlay', 'text-actions-neutral-idle'],
87
- strong: ['bg-actions-neutral-idle-alpha-strong', 'text-actions-neutral-invert-idle'],
68
+ light: ['bg-actions-neutral-active-overlay', 'text-text-neutral-base'],
69
+ strong: ['bg-actions-neutral-idle-alpha-strong', 'text-text-neutral-invert-base'],
88
70
  },
89
71
  danger: {
90
- light: ['bg-actions-danger-active-overlay', 'text-actions-danger-idle'],
91
- strong: ['bg-actions-danger-idle-alpha-strong', 'text-actions-neutral-invert-idle'],
72
+ light: ['bg-actions-danger-active-overlay', 'text-text-danger-base'],
73
+ strong: ['bg-actions-danger-idle-alpha-strong', 'text-text-neutral-invert-base'],
92
74
  },
93
75
  warning: {
94
- light: ['bg-actions-warning-active-overlay', 'text-actions-warning-idle'],
95
- strong: ['bg-actions-warning-idle-alpha-strong', 'text-actions-neutral-invert-idle'],
76
+ light: ['bg-actions-warning-active-overlay', 'text-text-warning-base'],
77
+ strong: ['bg-actions-warning-idle-alpha-strong', 'text-text-neutral-invert-base'],
96
78
  },
97
79
  success: {
98
- light: ['bg-actions-success-active-overlay', 'text-actions-success-idle'],
99
- strong: ['bg-actions-success-idle-alpha-strong', 'text-actions-neutral-invert-idle'],
80
+ light: ['bg-actions-success-active-overlay', 'text-text-success-base'],
81
+ strong: ['bg-actions-success-idle-alpha-strong', 'text-text-neutral-invert-base'],
100
82
  },
101
83
  'neutral-invert': {
102
- light: ['bg-actions-neutral-invert-active-overlay', 'text-actions-neutral-invert-idle'],
103
- strong: ['bg-actions-neutral-invert-idle-alpha-strong', 'text-actions-neutral-idle'],
84
+ light: ['bg-actions-neutral-invert-active-overlay', 'text-text-neutral-invert-base'],
85
+ strong: ['bg-actions-neutral-invert-idle-alpha-strong', 'text-text-neutral-base'],
104
86
  },
105
87
  }
106
88
 
@@ -129,7 +111,7 @@ const iconClasses = computed(() => {
129
111
 
130
112
  /* Focus ring */
131
113
  .ui-tag:focus-visible {
132
- @apply ring-2 ring-inset ring-focus-ring;
114
+ @apply focus-ring;
133
115
  }
134
116
 
135
117
  /* Hover states - only apply when withHoverEffect is true */
@@ -182,6 +164,6 @@ const iconClasses = computed(() => {
182
164
  }
183
165
 
184
166
  .ui-tag-label {
185
- @apply flex-1;
167
+ @apply flex-1 truncate;
186
168
  }
187
169
  </style>
@@ -0,0 +1,2 @@
1
+ export { default } from './UiTag.vue'
2
+ export type { UiTagColor, UiTagProps, UiTagSize, UiTagVariant } from './UiTag.types'
@@ -2,6 +2,8 @@
2
2
  import { computed, nextTick, ref, useSlots } from 'vue'
3
3
  import UiFieldWrapper from '../UiFieldWrapper/UiFieldWrapper.vue'
4
4
  import { useIconSize } from '../../composables/useIconSize'
5
+ import { useControlShape } from '../../composables/useControlShape'
6
+ import { useFieldFocusEvents } from '../../composables/useFieldFocusEvents'
5
7
  import type { TextFieldSize } from './types'
6
8
 
7
9
  interface UiTextFieldProps {
@@ -9,6 +11,10 @@ interface UiTextFieldProps {
9
11
  modelValue?: string
10
12
  placeholder?: string
11
13
  disabled?: boolean
14
+ /**
15
+ * The value can be selected and copied but not edited
16
+ */
17
+ readonly?: boolean
12
18
  loading?: boolean
13
19
  error?: boolean
14
20
  label?: string
@@ -28,6 +34,7 @@ interface UiTextFieldProps {
28
34
  const props = withDefaults(defineProps<UiTextFieldProps>(), {
29
35
  size: 'md',
30
36
  disabled: false,
37
+ readonly: false,
31
38
  loading: false,
32
39
  error: false,
33
40
  required: false,
@@ -49,22 +56,17 @@ const emit = defineEmits<{
49
56
 
50
57
  const slots = useSlots()
51
58
  const textFieldRef = ref<HTMLInputElement | null>(null)
52
- const isFocused = ref(false)
59
+ const { isFocused, handleFocus, handleBlur, handleKeydown, handleKeyup } = useFieldFocusEvents(emit)
53
60
 
54
61
  const iconSizeClass = useIconSize(() => props.size)
62
+ const shape = useControlShape('field', () => props.size)
55
63
 
56
64
  const hasLeftIcon = computed(() => !!slots.icon)
57
65
  const hasRightAction = computed(() => !!slots['right-action'])
58
- const showClearButton = computed(() => props.clearable && !!props.modelValue && !props.disabled && !props.loading)
66
+ const showClearButton = computed(() => props.clearable && !!props.modelValue && !props.disabled && !props.readonly && !props.loading)
59
67
 
60
68
  const textFieldClasses = computed(() => {
61
- const classes = ['ui-text-field-field']
62
-
63
- // Size classes
64
- if (props.size === 'sm') classes.push('h-8 text-content-text-md-regular')
65
- if (props.size === 'md') classes.push('h-10 text-content-text-md-regular')
66
- if (props.size === 'lg') classes.push('h-12 text-content-text-md-regular')
67
- if (props.size === 'xl') classes.push('h-16 text-content-text-md-regular')
69
+ const classes = ['ui-text-field-field', shape.value.radius, shape.value.height, 'text-content-text-md-regular']
68
70
 
69
71
  // State classes
70
72
  if (props.error) {
@@ -90,24 +92,6 @@ function handleClear () {
90
92
  textFieldRef.value?.focus()
91
93
  }
92
94
 
93
- function handleFocus (event: FocusEvent) {
94
- isFocused.value = true
95
- emit('focus', event)
96
- }
97
-
98
- function handleBlur (event: FocusEvent) {
99
- isFocused.value = false
100
- emit('blur', event)
101
- }
102
-
103
- function handleKeydown (event: KeyboardEvent) {
104
- emit('keydown', event)
105
- }
106
-
107
- function handleKeyup (event: KeyboardEvent) {
108
- emit('keyup', event)
109
- }
110
-
111
95
  defineExpose({
112
96
  focus: () => textFieldRef.value?.focus(),
113
97
  blur: () => textFieldRef.value?.blur(),
@@ -164,11 +148,12 @@ defineExpose({
164
148
  :value="modelValue"
165
149
  :placeholder="placeholder"
166
150
  :disabled="disabled || loading"
151
+ :readonly="readonly"
167
152
  :autocomplete="autocomplete"
168
153
  :aria-label="ariaLabel"
169
154
  :aria-invalid="error || undefined"
170
155
  :aria-describedby="ariaDescribedby || ((helperMessage || errorMessage) ? `${fieldId}-message` : undefined)"
171
- class="ui-text-field-element text-text-neutral-base placeholder:text-text-neutral-tertiary"
156
+ class="ui-text-field-element text-text-neutral-base placeholder:text-control-placeholder"
172
157
  @input="handleInput"
173
158
  @focus="handleFocus"
174
159
  @blur="handleBlur"
@@ -217,12 +202,12 @@ defineExpose({
217
202
  @reference '@livestorm/ui-tokens/output/supernova/index.css';
218
203
 
219
204
  .ui-text-field-field {
220
- @apply relative flex items-center gap-1.5 w-full rounded-lg border transition-all;
205
+ @apply relative flex items-center gap-1.5 w-full border transition-all;
221
206
  @apply pl-3 pr-0;
222
207
  }
223
208
 
224
209
  .ui-text-field-field--default {
225
- @apply bg-actions-neutral-idle-alpha-light border-borders-neutral-strong;
210
+ @apply bg-control-field-bg border-control-field-border;
226
211
 
227
212
  &:not(.ui-text-field-field--disabled) {
228
213
  &:hover .ui-text-field-overlay {
@@ -230,7 +215,7 @@ defineExpose({
230
215
  }
231
216
 
232
217
  &.ui-text-field-field--focused {
233
- @apply border-actions-primary-idle-alpha-strong ring-1 ring-inset ring-focus-ring;
218
+ @apply bg-control-field-bg-focus focus-ring;
234
219
 
235
220
  .ui-text-field-overlay {
236
221
  background: transparent;
@@ -240,7 +225,7 @@ defineExpose({
240
225
  }
241
226
 
242
227
  .ui-text-field-field--error {
243
- @apply bg-actions-danger-idle-alpha-light border-borders-danger-strong;
228
+ @apply bg-actions-danger-idle-alpha-light border-control-error-border;
244
229
 
245
230
  &:not(.ui-text-field-field--disabled) {
246
231
  &:hover .ui-text-field-overlay {
@@ -248,7 +233,7 @@ defineExpose({
248
233
  }
249
234
 
250
235
  &.ui-text-field-field--focused {
251
- @apply border-borders-danger-strong ring-1 ring-inset ring-borders-danger-strong;
236
+ @apply focus-ring;
252
237
 
253
238
  .ui-text-field-overlay {
254
239
  background: transparent;
@@ -262,7 +247,7 @@ defineExpose({
262
247
  }
263
248
 
264
249
  .ui-text-field-overlay {
265
- @apply absolute inset-0 rounded-lg pointer-events-none transition-colors;
250
+ @apply absolute -inset-px rounded-[inherit] pointer-events-none transition-colors;
266
251
  z-index: 1;
267
252
  }
268
253
 
@@ -336,7 +321,7 @@ defineExpose({
336
321
  }
337
322
 
338
323
  .ui-text-field-actions {
339
- @apply flex items-center justify-center px-3 h-full rounded-lg;
324
+ @apply flex items-center justify-center px-3 h-full rounded-[inherit];
340
325
  position: relative;
341
326
  z-index: 2;
342
327
  }
@@ -359,7 +344,7 @@ defineExpose({
359
344
  padding: 0;
360
345
 
361
346
  &:focus-visible {
362
- @apply ring-2 ring-borders-neutral-strong rounded;
347
+ @apply focus-ring rounded-row;
363
348
  }
364
349
  }
365
350
  </style>
@@ -63,7 +63,7 @@ const maxWidthClass = computed(() => {
63
63
  v-bind="{ ...forwarded, ...$attrs }"
64
64
  :data-colors-semantic="props.colorsSemantic"
65
65
  :class="[
66
- 'ui-tooltip-content 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',
66
+ 'ui-tooltip-content 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',
67
67
  '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',
68
68
  maxWidthClass
69
69
  ]"
@@ -5,6 +5,9 @@ import { TooltipProvider } from 'reka-ui'
5
5
  withDefaults(defineProps<TooltipProviderProps>(), {
6
6
  delayDuration: 700,
7
7
  skipDelayDuration: 300,
8
+ // A tap is how touch opens a tooltip, and the click that follows it would
9
+ // close it again straight away unless the trigger stops closing on click.
10
+ disableClosingTrigger: true,
8
11
  })
9
12
  </script>
10
13
 
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import type { TooltipTriggerProps } from 'reka-ui'
3
- import { TooltipTrigger, useForwardProps } from 'reka-ui'
3
+ import { TooltipTrigger, injectTooltipRootContext, useForwardProps } from 'reka-ui'
4
4
 
5
5
  const props = defineProps<TooltipTriggerProps>()
6
6
 
@@ -8,11 +8,26 @@ const props = defineProps<TooltipTriggerProps>()
8
8
  // consumed here and never reach TooltipTrigger, so `as-child` would be ignored
9
9
  // and reka would wrap the slot in its own <button>.
10
10
  const forwarded = useForwardProps(props)
11
+
12
+ const rootContext = injectTooltipRootContext()
13
+
14
+ // this is to open the tooltip on touch devices, which can't hover
15
+ // Bound to `pointerup` so a scroll that starts on the trigger does not open it.
16
+ function toggleOnTouch(event: PointerEvent) {
17
+ if (event.pointerType !== 'touch' || rootContext.disabled.value) return
18
+
19
+ if (rootContext.open.value) {
20
+ rootContext.onClose()
21
+ } else {
22
+ rootContext.onOpen()
23
+ }
24
+ }
11
25
  </script>
12
26
 
13
27
  <template>
14
28
  <TooltipTrigger
15
29
  v-bind="{ ...forwarded, ...$attrs }"
30
+ @pointerup="toggleOnTouch"
16
31
  >
17
32
  <slot />
18
33
  </TooltipTrigger>
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Does stored theming output apply?
3
+ *
4
+ * The theme picker (and its playground) is the only writer of deliberate
5
+ * theming choices: the stored modes in `themeAndMode` and the seed token map
6
+ * in `customThemeTokens`. A stored value alone is not a choice signal —
7
+ * `useStorage` persists the light/light defaults for every visitor — so
8
+ * stored theming is consumed only where the picker exists, and every surface
9
+ * ships its own defaults otherwise (the dashboard light/light, the room
10
+ * dark shell over light content).
11
+ *
12
+ * This is the single place that rule lives. `ls-internal-theming` itself
13
+ * gates the picker's visibility; it does not gate any rendering path.
14
+ * When a real user-facing theming choice ships (workspace setting, OS
15
+ * follow), its mechanism replaces this predicate.
16
+ */
17
+ export function storedThemingApplies (): boolean {
18
+ return typeof localStorage !== 'undefined' && localStorage.getItem('ls-internal-theming') === 'true'
19
+ }
@@ -1,7 +1,7 @@
1
1
  import { computed, type MaybeRefOrGetter, toValue } from 'vue'
2
2
 
3
3
  // Single source of truth for the action-button visual language (background,
4
- // text, border, hover/active overlays) keyed by `variant` × `color`. Both
4
+ // text, hover/active overlays) keyed by `variant` × `color`. Both
5
5
  // `UiButton` and `UiDropdownTrigger` consume this so a trigger styled as a
6
6
  // tertiary button stays pixel-identical to a real `UiButton` without copying
7
7
  // token names around.
@@ -81,16 +81,6 @@ const TEXT_MAP: Record<ButtonVariant, Record<ButtonColor, string>> = {
81
81
  },
82
82
  }
83
83
 
84
- // Border — only `secondary` draws one.
85
- const BORDER_MAP: Record<ButtonColor, string[]> = {
86
- 'primary': ['border', 'border-borders-primary-strong'],
87
- 'neutral': ['border', 'border-borders-neutral-strong'],
88
- 'neutral-invert': ['border', 'border-borders-neutral-invert-strong'],
89
- 'success': ['border', 'border-borders-success-strong'],
90
- 'warning': ['border', 'border-borders-warning-strong'],
91
- 'error': ['border', 'border-borders-danger-strong'],
92
- }
93
-
94
84
  // Hover overlay tints keyed by `variant-color`, in both the `group-hover:`
95
85
  // flavor (layered button overlay element) and the `before:` flavor
96
86
  // (single-element trigger pseudo). Kept as literals for Tailwind extraction.
@@ -205,9 +195,14 @@ export function useButtonStyles(options: UseButtonStylesOptions) {
205
195
  /** Text color class for the current variant × color. */
206
196
  const textClass = computed(() => TEXT_MAP[variant.value][color.value])
207
197
 
208
- /** Border classes — only `secondary` paints a border. */
198
+ /** Soft border of the secondary fill layer; transparent except in the accessible look. */
209
199
  const borderClasses = computed(() =>
210
- variant.value === 'secondary' ? (BORDER_MAP[color.value] ?? []) : [],
200
+ variant.value === 'secondary' ? ['border', 'border-control-soft-border'] : [],
201
+ )
202
+
203
+ /** Tertiary labels are underlined in the accessible look. */
204
+ const decorationClass = computed(() =>
205
+ variant.value === 'tertiary' ? '[text-decoration-line:var(--control-link-decoration)] underline-offset-3' : '',
211
206
  )
212
207
 
213
208
  /**
@@ -236,6 +231,7 @@ export function useButtonStyles(options: UseButtonStylesOptions) {
236
231
  bgClass,
237
232
  textClass,
238
233
  borderClasses,
234
+ decorationClass,
239
235
  overlayClasses,
240
236
  beforeHoverOverlayClass,
241
237
  }
@@ -0,0 +1,36 @@
1
+ import { computed, type ComputedRef, type MaybeRefOrGetter, toValue } from 'vue'
2
+ import type { ChoiceControl } from '../components/UiChoice/types'
3
+
4
+ interface ChoiceboxState {
5
+ control: ChoiceControl
6
+ selected: boolean
7
+ disabled: boolean
8
+ error?: boolean
9
+ }
10
+
11
+ // Only the row's own control rings the surface: a control in the content slot keeps its own focus
12
+ const FOCUS_RING: Record<ChoiceControl, string> = {
13
+ radio: 'has-[[role=radio]:focus-visible]:focus-ring',
14
+ switch: 'has-[[role=switch]:focus-visible]:focus-ring',
15
+ checkbox: 'has-[[type=checkbox]:focus-visible]:focus-ring',
16
+ }
17
+
18
+ /** Border, fill, radius and focus ring of a UiChoice choicebox */
19
+ export function useChoiceboxSurface (state: MaybeRefOrGetter<ChoiceboxState>): ComputedRef<string[]> {
20
+ return computed(() => {
21
+ const { control, selected, disabled, error } = toValue(state)
22
+ const classes = ['rounded-row border', FOCUS_RING[control]]
23
+
24
+ if (error) {
25
+ classes.push('bg-actions-danger-idle-alpha-light', selected ? 'border-actions-danger-idle' : 'border-control-error-border')
26
+ } else if (disabled) {
27
+ classes.push('bg-control-choice-bg-disabled border-control-choice-border-disabled')
28
+ } else if (selected) {
29
+ classes.push('bg-control-choice-selected-bg border-control-choice-selected-border hover:bg-control-choice-selected-bg-hover')
30
+ } else {
31
+ classes.push('bg-control-choice-bg border-control-choice-border hover:bg-control-choice-bg-hover hover:border-control-choice-border-hover')
32
+ }
33
+
34
+ return classes
35
+ })
36
+ }
@@ -1,5 +1,6 @@
1
- import { computed, onMounted, ref, type Ref } from 'vue'
1
+ import { computed, onMounted, onUnmounted, ref, type Ref } from 'vue'
2
2
  import useThemeAndModes from './useThemeAndModes'
3
+ import { storedThemingApplies } from './storedThemingApplies'
3
4
 
4
5
  /**
5
6
  * Registers an element as a page "content" surface so the stored content mode
@@ -7,26 +8,29 @@ import useThemeAndModes from './useThemeAndModes'
7
8
  * behaviour `UiPage` gives its page content, without pulling in the full page
8
9
  * layout (and its `<main>`).
9
10
  *
10
- * The content historically forces light mode. That pin is only lifted when the
11
- * internal theming tooling is enabled, so the stored content mode can apply;
12
- * everyone else stays light regardless of what localStorage may hold.
11
+ * Content ships light. The stored content mode applies where the picker
12
+ * exists (see storedThemingApplies); everyone else gets the shipped default
13
+ * regardless of what localStorage may hold.
13
14
  *
14
- * The flag is read in onMounted, not at setup: setup also runs during SSR (no
15
- * localStorage), and server + client-hydration markup must be identical — the
16
- * pin lifts right after mount for the internal tooling instead.
15
+ * The predicate is read in onMounted, not at setup: setup also runs during
16
+ * SSR (no localStorage), and server + client-hydration markup must be
17
+ * identical — stored modes take over right after mount instead.
17
18
  *
18
19
  * @param element Template ref of the content element to watch.
19
20
  * @param selector Unique key for the watch registry — one per logical surface
20
21
  * (e.g. `[data-name="ui-page-content"]`).
21
- * @returns `contentMode`, to bind to the element's `data-colors-semantic`, and
22
- * `isInternalThemingEnabled` for any mode-dependent styling.
22
+ * @returns `contentMode`, to bind to the element's `data-colors-semantic`.
23
23
  */
24
24
  export function useContentMode (
25
25
  element: Ref<HTMLElement | undefined>,
26
26
  selector: string,
27
27
  ) {
28
- const { addElementToWatch, currentContentMode } = useThemeAndModes()
29
- const isInternalThemingEnabled = ref(false)
28
+ const { addElementToWatch, removeElementToWatch, currentContentMode } = useThemeAndModes()
29
+ const storedApplies = ref(false)
30
+
31
+ // Captured at registration: Vue has already nulled the template ref by the
32
+ // time onUnmounted runs, and unregistering needs the element it registered.
33
+ let registered: HTMLElement | undefined
30
34
 
31
35
  // Always resolve to a concrete mode, never undefined. useThemeAndModes also
32
36
  // sets this attribute imperatively; if the binding resolved to undefined,
@@ -34,20 +38,29 @@ export function useContentMode (
34
38
  // flag flip, and the content would silently inherit the *shell* mode from
35
39
  // <body> — reading as "content mode resets to dark on every navigation".
36
40
  const contentMode = computed(() => (
37
- isInternalThemingEnabled.value ? currentContentMode.value : 'light'
41
+ storedApplies.value ? currentContentMode.value : 'light'
38
42
  ))
39
43
 
40
44
  onMounted(() => {
41
- isInternalThemingEnabled.value = localStorage.getItem('ls-internal-theming') === 'true'
45
+ storedApplies.value = storedThemingApplies()
42
46
 
43
47
  if (!element.value) { return }
44
48
 
49
+ registered = element.value
45
50
  addElementToWatch('content', {
46
51
  selector,
47
- element: element.value,
48
- ...(isInternalThemingEnabled.value ? {} : { modeOverride: 'light' }),
52
+ element: registered,
53
+ ...(storedApplies.value ? {} : { modeOverride: 'light' }),
49
54
  })
50
55
  })
51
56
 
52
- return { contentMode, isInternalThemingEnabled }
57
+ // The registry outlives this component, so a surface that unmounts without
58
+ // this stays referenced and keeps being written to.
59
+ onUnmounted(() => {
60
+ if (registered) {
61
+ removeElementToWatch('content', { selector, element: registered })
62
+ }
63
+ })
64
+
65
+ return { contentMode }
53
66
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Applies the accessibility look on the app root: `data-accessibility="on"`.
3
+ * The control tokens read this attribute.
4
+ */
5
+ export function applyControlModes (
6
+ { accessibility }: { accessibility: boolean },
7
+ root: HTMLElement = document.body,
8
+ ) {
9
+ if (accessibility) root.setAttribute('data-accessibility', 'on')
10
+ else root.removeAttribute('data-accessibility')
11
+ }
@@ -0,0 +1,34 @@
1
+ import { computed, type ComputedRef, type MaybeRefOrGetter, toValue } from 'vue'
2
+
3
+ // NOTE: every class must be a complete literal string: Tailwind's static
4
+ // extractor can't see interpolated class names.
5
+
6
+ export type FieldSize = 'sm' | 'md' | 'lg' | 'xl'
7
+ type ButtonSize = 'xs' | 'sm' | 'md' | 'lg'
8
+
9
+ interface FieldShape { height: string, radius: string, chevron: string }
10
+
11
+ const FIELD_SHAPES: Record<FieldSize, FieldShape> = {
12
+ sm: { height: 'h-field-sm', radius: 'rounded-field-sm', chevron: 'size-3.5' },
13
+ md: { height: 'h-field-md', radius: 'rounded-field-md', chevron: 'size-4' },
14
+ lg: { height: 'h-field-lg', radius: 'rounded-field-lg', chevron: 'size-5' },
15
+ xl: { height: 'h-field-xl', radius: 'rounded-field-xl', chevron: 'size-6' },
16
+ }
17
+
18
+ const BUTTON_SHAPES: Record<ButtonSize, { height: string, square: string, radius: string }> = {
19
+ xs: { height: 'h-button-xs', square: 'size-button-xs', radius: 'rounded-button-xs' },
20
+ sm: { height: 'h-button-sm', square: 'size-button-sm', radius: 'rounded-button-sm' },
21
+ md: { height: 'h-button-md', square: 'size-button-md', radius: 'rounded-button-md' },
22
+ lg: { height: 'h-button-lg', square: 'size-button-lg', radius: 'rounded-button-lg' },
23
+ }
24
+
25
+ interface ControlShapes {
26
+ field: { size: FieldSize, shape: FieldShape }
27
+ button: { size: ButtonSize, shape: { height: string, square: string, radius: string } }
28
+ }
29
+
30
+ export function useControlShape<K extends keyof ControlShapes> (kind: K, size: MaybeRefOrGetter<ControlShapes[K]['size']>): ComputedRef<ControlShapes[K]['shape']> {
31
+ return computed(() => (kind === 'field'
32
+ ? FIELD_SHAPES[toValue(size) as FieldSize]
33
+ : BUTTON_SHAPES[toValue(size) as ButtonSize]) as ControlShapes[K]['shape'])
34
+ }
@@ -0,0 +1,30 @@
1
+ import { ref } from 'vue'
2
+
3
+ export function useFieldFocusEvents(emit: {
4
+ (event: 'focus', payload: FocusEvent): void
5
+ (event: 'blur', payload: FocusEvent): void
6
+ (event: 'keydown', payload: KeyboardEvent): void
7
+ (event: 'keyup', payload: KeyboardEvent): void
8
+ }) {
9
+ const isFocused = ref(false)
10
+
11
+ function handleFocus(event: FocusEvent) {
12
+ isFocused.value = true
13
+ emit('focus', event)
14
+ }
15
+
16
+ function handleBlur(event: FocusEvent) {
17
+ isFocused.value = false
18
+ emit('blur', event)
19
+ }
20
+
21
+ function handleKeydown(event: KeyboardEvent) {
22
+ emit('keydown', event)
23
+ }
24
+
25
+ function handleKeyup(event: KeyboardEvent) {
26
+ emit('keyup', event)
27
+ }
28
+
29
+ return { isFocused, handleFocus, handleBlur, handleKeydown, handleKeyup }
30
+ }
@@ -26,3 +26,18 @@ export type IconSizeName = keyof typeof ICON_SIZES
26
26
  export function useIconSize(size: MaybeRefOrGetter<IconSizeName>) {
27
27
  return computed(() => ICON_SIZES[toValue(size)])
28
28
  }
29
+
30
+ // A button's trailing icon (external link, chevron) sits one step below its
31
+ // leading icon so it reads as an affordance, not as content.
32
+ const TRAILING_ICON_SIZES = {
33
+ xs: 'size-2.5',
34
+ sm: 'size-3.5',
35
+ md: 'size-4',
36
+ lg: 'size-5',
37
+ xl: 'size-6',
38
+ } as const
39
+
40
+ /** Reactive trailing icon size class for a control's `size`. */
41
+ export function useTrailingIconSize(size: MaybeRefOrGetter<IconSizeName>) {
42
+ return computed(() => TRAILING_ICON_SIZES[toValue(size)])
43
+ }