@leavepulse/ui 0.71.2 → 0.72.1

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 (276) hide show
  1. package/dist/assets/{LpKeys-BxRhfQda.css → LpKeys-C5VMcBOW.css} +10 -5
  2. package/dist/assets/LpTabs-CDchzoRL.css +18 -0
  3. package/dist/chunks/{ContextMenuItems-eS7qx9gD.js → ContextMenuItems-BXumicbh.js} +1 -1
  4. package/dist/chunks/{DropzoneLinkField-DkFhdfEJ.js → DropzoneLinkField-DpOB20Lx.js} +2 -2
  5. package/dist/chunks/{FieldWrapper-YzbvvC3M.js → FieldWrapper-DHhRxO5G.js} +1 -1
  6. package/dist/chunks/{LayoutCanvas-Ba4A9QC5.js → LayoutCanvas-CNOAUk7D.js} +1 -1
  7. package/dist/chunks/{LayoutNode-CTTTLMDM.js → LayoutNode-DF5BMN0J.js} +1 -1
  8. package/dist/chunks/{LpActionBar-B7anGlkI.js → LpActionBar-BqicINE_.js} +3 -3
  9. package/dist/chunks/{LpAddressInput-Bu6ws3Fp.js → LpAddressInput-CkbKLufb.js} +7 -7
  10. package/dist/chunks/{LpAlert-BHi7ARqu.js → LpAlert-BigMdAbu.js} +3 -3
  11. package/dist/chunks/{LpAppShell-C5YW5pI7.js → LpAppShell-DIMy-vjP.js} +68 -18
  12. package/dist/chunks/{LpAutocomplete-DDqe2M5g.js → LpAutocomplete-DOu7FN5P.js} +2 -2
  13. package/dist/chunks/{LpAvatar-DqfKCIdr.js → LpAvatar-Cg6Az3wz.js} +1 -1
  14. package/dist/chunks/{LpBadge-C8fKEmTF.js → LpBadge-Huo8EKew.js} +3 -3
  15. package/dist/chunks/LpBottomNav-BZIpahzA.js +89 -0
  16. package/dist/chunks/{LpBreadcrumbs-DhcoStFa.js → LpBreadcrumbs-DGtpBhWt.js} +3 -3
  17. package/dist/chunks/{LpButton-BQDAFXBd.js → LpButton-CW34A1e4.js} +1 -1
  18. package/dist/chunks/{LpCalendar-NmrA47J9.js → LpCalendar-CuoWks-0.js} +1 -1
  19. package/dist/chunks/{LpCard-CqKLnEhl.js → LpCard-B0sFXQzc.js} +1 -1
  20. package/dist/chunks/{LpCheckbox-BoOuLnGB.js → LpCheckbox-iaD63DEF.js} +2 -2
  21. package/dist/chunks/{LpCodeBlock-Czu-fq1M.js → LpCodeBlock-CFFa_pJb.js} +2 -2
  22. package/dist/chunks/{LpCodeDisplay-C9pIMF2R.js → LpCodeDisplay-CFmGMAmP.js} +6 -6
  23. package/dist/chunks/{LpColorPicker-Ee1DZiRd.js → LpColorPicker-1y1QJHZR.js} +56 -28
  24. package/dist/chunks/{LpConfirmDialog-i15pulMT.js → LpConfirmDialog-BDystNtm.js} +3 -3
  25. package/dist/chunks/{LpConfirmHost-D0oGavtd.js → LpConfirmHost-oOsxJHfQ.js} +1 -1
  26. package/dist/chunks/{LpContextMenu-DXpFJjlO.js → LpContextMenu-rx2tCjwk.js} +2 -2
  27. package/dist/chunks/{LpCopyButton-Dmq7DmFw.js → LpCopyButton-BNcGbVXF.js} +2 -2
  28. package/dist/chunks/{LpCountdown-CXrEK0ev.js → LpCountdown-Cak7cq3X.js} +3 -3
  29. package/dist/chunks/{LpDataList-CFPtSsBB.js → LpDataList-B6Meb4M9.js} +3 -3
  30. package/dist/chunks/{LpDatePicker-cC9K-Pk8.js → LpDatePicker-CA0QTfBC.js} +4 -4
  31. package/dist/chunks/{LpDateRangePicker-BVQKPbvo.js → LpDateRangePicker-u3J51xXk.js} +2 -2
  32. package/dist/chunks/{LpDisclosure-hRnX3FWL.js → LpDisclosure-Cpa7Hc5T.js} +1 -1
  33. package/dist/chunks/{LpDrawer-u4rxnuR-.js → LpDrawer-RRamxbLV.js} +1 -1
  34. package/dist/chunks/{LpDropdownMenu-BgZ0Z_Im.js → LpDropdownMenu-Dl7Z4ZhS.js} +2 -2
  35. package/dist/chunks/{LpEditable-CjxuOVh_.js → LpEditable-BNogo5DL.js} +5 -5
  36. package/dist/chunks/{LpEmptyState-CKFTtQgJ.js → LpEmptyState-EQHiRYhG.js} +7 -3
  37. package/dist/chunks/{LpFieldNotice-CPI8EwtU.js → LpFieldNotice-CzQ2_5rQ.js} +1 -1
  38. package/dist/chunks/{LpFileTree-BH0gNlSJ.js → LpFileTree-CHgtVh14.js} +2 -2
  39. package/dist/chunks/{LpFileTreeNode-gPVB1oXM.js → LpFileTreeNode-LqHgHd3B.js} +5 -5
  40. package/dist/chunks/{LpFormField-gRASfyj2.js → LpFormField-CC57DdTi.js} +2 -2
  41. package/dist/chunks/{LpGauge-DFdt33x8.js → LpGauge-BO1PYYqD.js} +2 -2
  42. package/dist/chunks/{LpHintLayer-C2asBIfs.js → LpHintLayer-CvAj4aa6.js} +2 -2
  43. package/dist/chunks/{LpHints-CNZQ76JV.js → LpHints-BtRvHQQ0.js} +3 -3
  44. package/dist/chunks/{LpIcon-DkP-9mzK.js → LpIcon--5WSb3jJ.js} +1 -1
  45. package/dist/chunks/{LpInfraNode-yTh4-5qa.js → LpInfraNode-CxngdQAG.js} +2 -2
  46. package/dist/chunks/{LpInput-2H-5ILOB.js → LpInput-DqmL-438.js} +2 -2
  47. package/dist/chunks/{LpLightbox-lAt5uIfx.js → LpLightbox-7YP6zSDm.js} +3 -3
  48. package/dist/chunks/{LpLoader-DjJEX8hL.js → LpLoader-CUo1MX_0.js} +1 -1
  49. package/dist/chunks/{LpLogViewer-x78YfhJf.js → LpLogViewer-DQOIsnZ1.js} +6 -6
  50. package/dist/chunks/{LpMapPicker-Dp5RSCi9.js → LpMapPicker-Dm3vJHN2.js} +2 -2
  51. package/dist/chunks/{LpMediaGallery-D92oJfY_.js → LpMediaGallery-znEbvIEo.js} +3 -3
  52. package/dist/chunks/{LpModal-CpH0bNo7.js → LpModal-BnjZ2iem.js} +1 -1
  53. package/dist/chunks/{LpModalNav-Cuna8c_3.js → LpModalNav-CH1UE9Ch.js} +4 -4
  54. package/dist/chunks/{LpNotificationBell-BdUQqZhs.js → LpNotificationBell-CrywodQ_.js} +8 -8
  55. package/dist/chunks/{LpNumberField-DZY3Z670.js → LpNumberField-e0IBkWU1.js} +1 -1
  56. package/dist/chunks/{LpNumberFlow-Dwb9MV1A.js → LpNumberFlow-CcxEGUy0.js} +4 -2
  57. package/dist/chunks/{LpPagination-DbeACavM.js → LpPagination-BIIiYHXR.js} +1 -1
  58. package/dist/chunks/{LpPaletteTheme-DkikZ362.js → LpPaletteTheme-BzHBh-mN.js} +4 -4
  59. package/dist/chunks/{LpPasswordInput-BzAZsf5V.js → LpPasswordInput-DVgRgTVx.js} +2 -2
  60. package/dist/chunks/{LpPhoneInput-BMpd-0QR.js → LpPhoneInput-CETEp5W7.js} +2 -2
  61. package/dist/chunks/{LpPie-C1uIUFxu.js → LpPie-BjDbeytx.js} +6 -6
  62. package/dist/chunks/{LpPipeline-zjEM1Lwc.js → LpPipeline-zZUj7I6K.js} +4 -4
  63. package/dist/chunks/{LpProgress-WNaH4emv.js → LpProgress-rOlyafqp.js} +1 -1
  64. package/dist/chunks/{LpQuota-Cz9PvklM.js → LpQuota-B12yijdB.js} +3 -3
  65. package/dist/chunks/{LpRadioGroup-CsXeV9Ct.js → LpRadioGroup-ggaPL92T.js} +1 -1
  66. package/dist/chunks/{LpRangeCalendar-l-0nSFmY.js → LpRangeCalendar-CYYUYqJ7.js} +1 -1
  67. package/dist/chunks/{LpRelativeTime-UM8EkfrN.js → LpRelativeTime-DwgCb5-8.js} +1 -1
  68. package/dist/chunks/{LpRepeater-OvzByE5L.js → LpRepeater-BQB0twJ5.js} +3 -3
  69. package/dist/chunks/LpRollingText-B54Pje7P.js +86 -0
  70. package/dist/chunks/{LpScramble-TDO5Kdou.js → LpScramble-DbQyBjUY.js} +1 -1
  71. package/dist/chunks/{LpSecretField-DwQiPOko.js → LpSecretField-uoD6Fngf.js} +7 -7
  72. package/dist/chunks/{LpSection-DcQsieHG.js → LpSection-DFK_uoJE.js} +1 -1
  73. package/dist/chunks/{LpSegmented-D2VST0o3.js → LpSegmented-CeACsMMA.js} +4 -2
  74. package/dist/chunks/{LpSelect-D3pRJfRY.js → LpSelect-Cxq4yBGd.js} +3 -3
  75. package/dist/chunks/LpShift-C-UGuKMN.js +171 -0
  76. package/dist/chunks/LpSidebar-DXdbvgM6.js +494 -0
  77. package/dist/chunks/LpSidebarNav-CE6CVCb0.js +367 -0
  78. package/dist/chunks/LpSidebarRow-CF9VFZ2w.js +304 -0
  79. package/dist/chunks/{LpStat-Dzn6WFzi.js → LpStat-CjpXrC8R.js} +6 -4
  80. package/dist/chunks/{LpStatus-BQU1TUsv.js → LpStatus-g-8z9b_u.js} +3 -3
  81. package/dist/chunks/{LpStepMark-CXa7DJy_.js → LpStepMark-DU7xalwW.js} +3 -3
  82. package/dist/chunks/{LpStepper-C9RM_yTG.js → LpStepper-CbRXiXNs.js} +1 -1
  83. package/dist/chunks/{LpSwap-DzE6M3Mg.js → LpSwap-Xi4j9PR_.js} +1 -1
  84. package/dist/chunks/{LpSwitch-bGZxUDSx.js → LpSwitch-BWcXEahf.js} +1 -1
  85. package/dist/chunks/LpTabFace-CG0VqD8I.js +77 -0
  86. package/dist/chunks/{LpTableFilter-CTZMqFDU.js → LpTableFilter-CYtK9GsF.js} +3 -3
  87. package/dist/chunks/{LpTagsInput-DrjmrOge.js → LpTagsInput-D1RzCwb5.js} +5 -5
  88. package/dist/chunks/{LpTextarea-BbYqmG1S.js → LpTextarea-MdIzNTjw.js} +32 -7
  89. package/dist/chunks/{useTheme-BOV_-AiY.js → LpThemeProvider-8MG6yqU9.js} +66 -1
  90. package/dist/chunks/{LpThemeStudio-IupHqP0x.js → LpThemeStudio-B0Kk1bX0.js} +138 -104
  91. package/dist/chunks/LpThemeStudioSection-B7bt8Iyg.js +32 -0
  92. package/dist/chunks/{LpThemeSwitcher-DarPHuX8.js → LpThemeSwitcher-CI4I7ygL.js} +6 -6
  93. package/dist/chunks/{LpTimeField-DQ8WvPr2.js → LpTimeField-BawSVsmt.js} +1 -1
  94. package/dist/chunks/{LpToaster-BTTl_wuy.js → LpToaster-C-P1nmMS.js} +2 -2
  95. package/dist/chunks/{LpTopologyCanvas-Cq-MiJwo.js → LpTopologyCanvas-CbEZhsP1.js} +1 -1
  96. package/dist/chunks/{LpTour-BgCfNlaW.js → LpTour-DRyzgpjt.js} +1 -1
  97. package/dist/chunks/{LpTruncate-TcqfkyJM.js → LpTruncate-4WvCWqnW.js} +1 -1
  98. package/dist/chunks/{LpUptimeBar-BbhVTPnL.js → LpUptimeBar-B2LNSNxs.js} +4 -4
  99. package/dist/chunks/{MenuTitle-Dab3EGOp.js → MenuTitle-BmCNNTKH.js} +1 -1
  100. package/dist/chunks/{OptionContent-DPzNsybd.js → OptionContent-22UT8zLn.js} +1 -1
  101. package/dist/chunks/{PickerField-DwLw7j0L.js → PickerField-Bfh8id3d.js} +2 -2
  102. package/dist/chunks/{RevealToggle-BVFM_FAU.js → RevealToggle-jBLJwVwO.js} +2 -2
  103. package/dist/chunks/{TourCard--2IfgnUU.js → TourCard-BOBcGbEo.js} +5 -5
  104. package/dist/chunks/{icons-CLu1iR1j.js → icons-DMITlfw5.js} +1 -1
  105. package/dist/component-names.d.ts +1 -1
  106. package/dist/components/ContextMenuItems.vue.js +1 -1
  107. package/dist/components/DropzoneLinkField.vue.js +1 -1
  108. package/dist/components/FieldWrapper.vue.js +1 -1
  109. package/dist/components/LayoutCanvas.vue.js +1 -1
  110. package/dist/components/LayoutNode.vue.js +1 -1
  111. package/dist/components/LpActionBar.vue.js +1 -1
  112. package/dist/components/LpAddressInput.vue.js +1 -1
  113. package/dist/components/LpAlert.vue.js +1 -1
  114. package/dist/components/LpAppShell.vue.d.ts +67 -2
  115. package/dist/components/LpAppShell.vue.js +1 -1
  116. package/dist/components/LpAutocomplete.vue.js +1 -1
  117. package/dist/components/LpAvatar.vue.js +1 -1
  118. package/dist/components/LpBadge.vue.js +1 -1
  119. package/dist/components/LpBottomNav.vue.d.ts +28 -0
  120. package/dist/components/LpBottomNav.vue.js +4 -0
  121. package/dist/components/LpBreadcrumbs.vue.js +1 -1
  122. package/dist/components/LpButton.vue.js +1 -1
  123. package/dist/components/LpCalendar.vue.js +1 -1
  124. package/dist/components/LpCard.vue.js +1 -1
  125. package/dist/components/LpChart.vue.js +4 -4
  126. package/dist/components/LpCheckbox.vue.js +1 -1
  127. package/dist/components/LpCodeBlock.vue.js +1 -1
  128. package/dist/components/LpCodeDisplay.vue.js +1 -1
  129. package/dist/components/LpColorPicker.vue.d.ts +15 -2
  130. package/dist/components/LpColorPicker.vue.js +1 -1
  131. package/dist/components/LpCommandPalette.vue.js +4 -4
  132. package/dist/components/LpConfirmDialog.vue.js +1 -1
  133. package/dist/components/LpConfirmHost.vue.js +1 -1
  134. package/dist/components/LpContextMenu.vue.js +1 -1
  135. package/dist/components/LpCopyButton.vue.js +1 -1
  136. package/dist/components/LpCountdown.vue.js +1 -1
  137. package/dist/components/LpDataList.vue.js +1 -1
  138. package/dist/components/LpDatePicker.vue.js +1 -1
  139. package/dist/components/LpDateRangePicker.vue.js +1 -1
  140. package/dist/components/LpDisclosure.vue.js +1 -1
  141. package/dist/components/LpDock.vue.js +1 -1
  142. package/dist/components/LpDrawer.vue.js +1 -1
  143. package/dist/components/LpDropdownMenu.vue.js +1 -1
  144. package/dist/components/LpDropzone.vue.js +2 -2
  145. package/dist/components/LpEditable.vue.js +1 -1
  146. package/dist/components/LpEmptyState.vue.d.ts +2 -0
  147. package/dist/components/LpEmptyState.vue.js +1 -1
  148. package/dist/components/LpFieldNotice.vue.js +1 -1
  149. package/dist/components/LpFileTree.vue.js +1 -1
  150. package/dist/components/LpFileTreeNode.vue.js +1 -1
  151. package/dist/components/LpFormField.vue.js +1 -1
  152. package/dist/components/LpGauge.vue.js +1 -1
  153. package/dist/components/LpHintLayer.vue.js +1 -1
  154. package/dist/components/LpHints.vue.js +1 -1
  155. package/dist/components/LpIcon.vue.js +1 -1
  156. package/dist/components/LpImageEditor.vue.js +3 -3
  157. package/dist/components/LpInfraNode.vue.js +1 -1
  158. package/dist/components/LpInput.vue.js +1 -1
  159. package/dist/components/LpKeys.vue.js +13 -10
  160. package/dist/components/LpLightbox.vue.js +1 -1
  161. package/dist/components/LpLoader.vue.js +1 -1
  162. package/dist/components/LpLogViewer.vue.d.ts +1 -1
  163. package/dist/components/LpLogViewer.vue.js +1 -1
  164. package/dist/components/LpMapPicker.vue.js +1 -1
  165. package/dist/components/LpMediaGallery.vue.js +1 -1
  166. package/dist/components/LpModal.vue.js +1 -1
  167. package/dist/components/LpModalNav.vue.js +1 -1
  168. package/dist/components/LpNotificationBell.vue.js +1 -1
  169. package/dist/components/LpNumberField.vue.js +1 -1
  170. package/dist/components/LpNumberFlow.vue.js +1 -1
  171. package/dist/components/LpPagination.vue.js +1 -1
  172. package/dist/components/LpPaletteFacts.vue.js +2 -2
  173. package/dist/components/LpPaletteHeader.vue.js +2 -2
  174. package/dist/components/LpPaletteStats.vue.js +2 -2
  175. package/dist/components/LpPaletteTheme.vue.js +1 -1
  176. package/dist/components/LpPasswordInput.vue.js +1 -1
  177. package/dist/components/LpPhoneInput.vue.js +1 -1
  178. package/dist/components/LpPie.vue.js +1 -1
  179. package/dist/components/LpPipeline.vue.js +1 -1
  180. package/dist/components/LpProgress.vue.js +1 -1
  181. package/dist/components/LpQuota.vue.js +1 -1
  182. package/dist/components/LpRadioGroup.vue.js +1 -1
  183. package/dist/components/LpRangeCalendar.vue.js +1 -1
  184. package/dist/components/LpRelativeTime.vue.js +1 -1
  185. package/dist/components/LpRepeater.vue.js +1 -1
  186. package/dist/components/LpRollingText.vue.d.ts +2 -2
  187. package/dist/components/LpRollingText.vue.js +1 -1
  188. package/dist/components/LpScramble.vue.js +1 -1
  189. package/dist/components/LpSecretField.vue.js +1 -1
  190. package/dist/components/LpSection.vue.js +1 -1
  191. package/dist/components/LpSegmented.vue.js +1 -1
  192. package/dist/components/LpSelect.vue.js +1 -1
  193. package/dist/components/LpShift.vue.d.ts +20 -0
  194. package/dist/components/LpShift.vue.js +1 -1
  195. package/dist/components/LpShortcutsDialog.vue.js +1 -1
  196. package/dist/components/LpSidebar.vue.d.ts +74 -23
  197. package/dist/components/LpSidebar.vue.js +1 -1
  198. package/dist/components/LpSidebarNav.vue.d.ts +31 -3
  199. package/dist/components/LpSidebarNav.vue.js +1 -1
  200. package/dist/components/LpSidebarRow.vue.d.ts +49 -0
  201. package/dist/components/LpSidebarRow.vue.js +4 -0
  202. package/dist/components/LpStat.vue.d.ts +11 -0
  203. package/dist/components/LpStat.vue.js +1 -1
  204. package/dist/components/LpStatus.vue.js +1 -1
  205. package/dist/components/LpStepMark.vue.js +1 -1
  206. package/dist/components/LpStepper.vue.js +1 -1
  207. package/dist/components/LpSwap.vue.js +1 -1
  208. package/dist/components/LpSwitch.vue.js +1 -1
  209. package/dist/components/LpTabFace.vue.d.ts +14 -0
  210. package/dist/components/LpTabFace.vue.js +4 -0
  211. package/dist/components/LpTable.vue.js +11 -11
  212. package/dist/components/LpTableFilter.vue.js +1 -1
  213. package/dist/components/LpTabs.vue.d.ts +4 -4
  214. package/dist/components/LpTabs.vue.js +18 -72
  215. package/dist/components/LpTagsInput.vue.js +1 -1
  216. package/dist/components/LpTextarea.vue.d.ts +19 -1
  217. package/dist/components/LpTextarea.vue.js +1 -1
  218. package/dist/components/LpThemeProvider.vue.js +1 -1
  219. package/dist/components/LpThemeStudio.vue.d.ts +2 -2
  220. package/dist/components/LpThemeStudio.vue.js +1 -1
  221. package/dist/components/LpThemeStudioSection.vue.d.ts +4 -1
  222. package/dist/components/LpThemeStudioSection.vue.js +1 -1
  223. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  224. package/dist/components/LpTimeField.vue.js +1 -1
  225. package/dist/components/LpToaster.vue.js +1 -1
  226. package/dist/components/LpTopologyCanvas.vue.js +1 -1
  227. package/dist/components/LpTour.vue.js +1 -1
  228. package/dist/components/LpTruncate.vue.js +1 -1
  229. package/dist/components/LpUptimeBar.vue.js +1 -1
  230. package/dist/components/MenuTitle.vue.js +1 -1
  231. package/dist/components/OptionContent.vue.js +1 -1
  232. package/dist/components/PickerField.vue.js +1 -1
  233. package/dist/components/RevealToggle.vue.js +1 -1
  234. package/dist/components/TourCard.vue.js +1 -1
  235. package/dist/components/menuWhen.js +1 -1
  236. package/dist/components/rolling.d.ts +5 -0
  237. package/dist/components/rolling.js +23 -0
  238. package/dist/components/sidebar.d.ts +38 -0
  239. package/dist/components/sidebar.js +6 -1
  240. package/dist/components/tabs.d.ts +3 -0
  241. package/dist/components/tabs.js +6 -0
  242. package/dist/icons-all.js +1 -1
  243. package/dist/index.d.ts +1 -0
  244. package/dist/index.js +154 -153
  245. package/package.json +1 -1
  246. package/src/component-names.ts +1 -0
  247. package/src/components/LpAppShell.vue +75 -7
  248. package/src/components/LpBottomNav.vue +84 -0
  249. package/src/components/LpColorPicker.vue +32 -1
  250. package/src/components/LpEmptyState.vue +4 -1
  251. package/src/components/LpKeys.vue +10 -1
  252. package/src/components/LpNumberFlow.vue +4 -2
  253. package/src/components/LpRollingText.vue +27 -2
  254. package/src/components/LpSegmented.vue +3 -1
  255. package/src/components/LpShift.vue +189 -2
  256. package/src/components/LpSidebar.vue +304 -18
  257. package/src/components/LpSidebarNav.vue +211 -78
  258. package/src/components/LpSidebarRow.vue +242 -0
  259. package/src/components/LpStat.vue +7 -1
  260. package/src/components/LpTabFace.vue +52 -0
  261. package/src/components/LpTabs.vue +6 -41
  262. package/src/components/LpTextarea.vue +34 -4
  263. package/src/components/LpThemeStudio.vue +91 -55
  264. package/src/components/LpThemeStudioSection.vue +15 -7
  265. package/src/components/rolling.ts +28 -0
  266. package/src/components/sidebar.ts +41 -0
  267. package/src/components/tabs.ts +9 -0
  268. package/src/index.ts +1 -0
  269. package/src/kit-icons.ts +1 -1
  270. package/dist/assets/LpTabs-DT0XPTc2.css +0 -18
  271. package/dist/chunks/LpRollingText-DiL717LK.js +0 -51
  272. package/dist/chunks/LpShift-DXNsauQZ.js +0 -40
  273. package/dist/chunks/LpSidebar-Bk7b-K4l.js +0 -256
  274. package/dist/chunks/LpSidebarNav-BR2AW3KT.js +0 -216
  275. package/dist/chunks/LpThemeProvider-DcbfOxsU.js +0 -69
  276. package/dist/chunks/LpThemeStudioSection-BwhqYDLf.js +0 -20
@@ -12,19 +12,15 @@ export default { inheritAttrs: false }
12
12
  * in two shells — the static desktop rail and the swipeable mobile drawer —
13
13
  * without duplicating markup. Not exported on its own; an LpSidebar detail.
14
14
  */
15
- import { Motion } from "motion-v"
16
15
  import { CollapsibleContent, CollapsibleRoot, CollapsibleTrigger } from "reka-ui"
17
16
  import { computed, ref } from "vue"
18
- import { useLocale } from "../composables/useLocale"
19
- import { usePillTransition } from "../composables/usePillTransition"
20
17
  import LpBadge from "./LpBadge.vue"
21
18
  import LpContextMenu from "./LpContextMenu.vue"
22
19
  import LpIcon from "./LpIcon.vue"
23
20
  import LpScrollArea from "./LpScrollArea.vue"
24
21
  import LpSkeleton from "./LpSkeleton.vue"
25
- import type { SidebarItem, SidebarSection } from "./sidebar"
26
- import { resolveLink, type ResolvedLink } from "./link"
27
- import { useLinkComponent } from "../composables/useLpConfig"
22
+ import LpSidebarRow from "./LpSidebarRow.vue"
23
+ import { waiting, type SidebarItem, type SidebarSection } from "./sidebar"
28
24
  import { useMergedAttrs } from "../composables/useMergedClass"
29
25
 
30
26
  const props = withDefaults(
@@ -52,22 +48,24 @@ const props = withDefaults(
52
48
  * short rail (an app switcher, a settings list) can turn it on.
53
49
  */
54
50
  hoverFollow?: boolean
51
+ /** Icons only, each row's name in a card beside it. */
52
+ compact?: boolean
53
+ /** Rows offer "Pin to top" in their menu; `pinned` are shown first. */
54
+ pinnable?: boolean
55
+ pinned?: SidebarItem[]
56
+ previewDelay?: number
57
+ labels: { pinned: string; pin: string; unpin: string; expand: string }
55
58
  }>(),
56
- { skeletonRows: 6, skeletonHeader: true, collapsedKeys: () => [], hoverFollow: false },
59
+ { skeletonRows: 6, skeletonHeader: true, collapsedKeys: () => [], hoverFollow: false, pinned: () => [], previewDelay: 900 },
57
60
  )
58
61
 
59
62
  const emit = defineEmits<{
60
63
  (e: "activate", item: SidebarItem): void
61
64
  (e: "toggle-section", key: string, collapsed: boolean): void
65
+ (e: "toggle-pin", id: string): void
62
66
  }>()
63
67
 
64
68
  /** `key` when the app set one, else the title — see SidebarSection.key. */
65
- // A count reads in the app's locale like every other figure: "7 563", not "7563".
66
- const locale = useLocale()
67
- function badgeText(badge: number | string): string {
68
- return typeof badge === "number" ? badge.toLocaleString(locale.value) : badge
69
- }
70
-
71
69
  function sectionKey(section: SidebarSection): string {
72
70
  return section.key ?? section.title ?? ""
73
71
  }
@@ -80,7 +78,7 @@ function sectionKey(section: SidebarSection): string {
80
78
  const foldedKeys = computed(() => {
81
79
  const holdsActive = new Set(
82
80
  props.groups
83
- .filter((g) => g.items.some((i) => props.itemActive(i)))
81
+ .filter((g) => g.items.some((i) => props.itemActive(i) || onPath(i)))
84
82
  .map(sectionKey),
85
83
  )
86
84
  return new Set(
@@ -89,11 +87,10 @@ const foldedKeys = computed(() => {
89
87
  })
90
88
 
91
89
  function isOpen(section: SidebarSection): boolean {
92
- return !foldedKeys.value.has(sectionKey(section))
90
+ // Compact, a group has no heading to unfold it by.
91
+ return props.compact || !foldedKeys.value.has(sectionKey(section))
93
92
  }
94
93
 
95
- const pillTransition = usePillTransition()
96
-
97
94
  const hovered = ref<string | null>(null)
98
95
 
99
96
  function hover(item: SidebarItem) {
@@ -105,16 +102,112 @@ function pillUnder(item: SidebarItem): boolean {
105
102
  return hovered.value ? hovered.value === item.id : props.itemActive(item)
106
103
  }
107
104
 
108
- const linkComponent = useLinkComponent()
105
+ /** One of the item's pages is the current one. */
106
+ function onPath(item: SidebarItem): boolean {
107
+ return !!item.children?.some((c) => props.itemActive(c))
108
+ }
109
+
110
+ // Pages under a row open with the current page among them and otherwise when
111
+ // asked; the asked-for ones are remembered for as long as the nav lives.
112
+ const opened = ref(new Set<string>())
113
+ function expanded(item: SidebarItem): boolean {
114
+ return onPath(item) || opened.value.has(item.id)
115
+ }
116
+ function toggle(item: SidebarItem) {
117
+ const next = new Set(opened.value)
118
+ if (next.has(item.id)) next.delete(item.id)
119
+ else next.add(item.id)
120
+ opened.value = next
121
+ }
122
+
123
+ const pinnedIds = computed(() => new Set(props.pinned.map((i) => i.id)))
124
+ function menuOf(item: SidebarItem) {
125
+ const own = item.menu ?? []
126
+ if (!props.pinnable) return own
127
+ const pinned = pinnedIds.value.has(item.id)
128
+ return [
129
+ ...own,
130
+ {
131
+ label: pinned ? props.labels.unpin : props.labels.pin,
132
+ icon: pinned ? "lucide:pin-off" : "lucide:pin",
133
+ separatorBefore: own.length > 0,
134
+ onSelect: () => emit("toggle-pin", item.id),
135
+ },
136
+ ]
137
+ }
109
138
 
110
139
  /*
111
- * A disabled item is always a disabled button: an anchor cannot be disabled,
112
- * and the old enabled-button fallback for a disabled link stayed focusable.
140
+ * The keyboard, as a tree: arrows walk the rows that are showing, right and
141
+ * left open and close a group or the pages under a row (left from such a page
142
+ * goes back to its row), Home and End jump to the ends, and typing jumps to the
143
+ * next row starting with what was typed. Tab still reaches every row, so this
144
+ * only adds a faster way through a long nav.
113
145
  */
114
- function rowOf(item: SidebarItem): ResolvedLink {
115
- const link = item.disabled ? null : resolveLink(linkComponent.value, { to: item.to, href: item.href })
116
- if (link) return link
117
- return { is: "button", props: { type: "button", disabled: item.disabled || undefined } }
146
+ let typed = ""
147
+ let typedAt = 0
148
+
149
+ function onKey(event: KeyboardEvent) {
150
+ const box = event.currentTarget as HTMLElement
151
+ const list = [...box.querySelectorAll<HTMLElement>("[data-lp-nav-row]")].filter((r) => r.offsetParent !== null)
152
+ const at = list.indexOf(document.activeElement as HTMLElement)
153
+ if (at < 0) return
154
+ const row = list[at]!
155
+ const go = (i: number) => {
156
+ event.preventDefault()
157
+ list[(i + list.length) % list.length]?.focus()
158
+ }
159
+ switch (event.key) {
160
+ case "ArrowDown":
161
+ return go(at + 1)
162
+ case "ArrowUp":
163
+ return go(at - 1)
164
+ case "Home":
165
+ return go(0)
166
+ case "End":
167
+ return go(list.length - 1)
168
+ case "ArrowRight":
169
+ case "ArrowLeft": {
170
+ const open = event.key === "ArrowRight"
171
+ const toggle = row.hasAttribute("aria-expanded")
172
+ ? row
173
+ : row.parentElement?.querySelector<HTMLElement>(":scope > button[aria-expanded]")
174
+ if (toggle && (toggle.getAttribute("aria-expanded") === "true") !== open) {
175
+ event.preventDefault()
176
+ toggle.click()
177
+ return
178
+ }
179
+ if (!open && row.dataset.lpNavNested !== undefined) {
180
+ for (let i = at - 1; i >= 0; i--) if (list[i]!.dataset.lpNavNested === undefined) return go(i)
181
+ }
182
+ return
183
+ }
184
+ }
185
+ if (event.key.length !== 1 || event.ctrlKey || event.metaKey || event.altKey) return
186
+ const now = Date.now()
187
+ typed = now - typedAt > 700 ? event.key : typed + event.key
188
+ typedAt = now
189
+ const needle = typed.toLocaleLowerCase()
190
+ for (let k = 1; k <= list.length; k++) {
191
+ const next = list[(at + k) % list.length]!
192
+ if ((next.textContent ?? "").trim().toLocaleLowerCase().startsWith(needle)) return go(at + k)
193
+ }
194
+ }
195
+
196
+ const SECTION = "lp-section:"
197
+ function sectionItem(section: SidebarSection): SidebarItem {
198
+ const icon = section.icon ?? section.items.find((i) => i.icon)?.icon
199
+ return {
200
+ id: `${SECTION}${sectionKey(section)}`,
201
+ label: section.title ?? "",
202
+ icon,
203
+ children: section.items,
204
+ }
205
+ }
206
+
207
+ function activate(item: SidebarItem) {
208
+ if (item.id.startsWith(SECTION)) return
209
+ if (item.children?.length && !item.to && !item.href) toggle(item)
210
+ emit("activate", item)
118
211
  }
119
212
 
120
213
  // The nav fills the remaining height with a fixed gap between groups; a short sidebar needs both under the consumer's control.
@@ -151,12 +244,51 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
151
244
  <LpScrollArea
152
245
  v-else
153
246
  class="min-h-0 flex-1"
154
- content-class="flex min-h-full flex-col gap-4"
247
+ :content-class="compact ? 'flex min-h-full flex-col gap-1' : 'flex min-h-full flex-col gap-4'"
155
248
  @pointerleave="hovered = null"
156
249
  >
250
+ <!-- `contents`: no box of its own, only somewhere for the rows' keys to bubble to. -->
251
+ <div class="contents" @keydown="onKey">
252
+ <!-- Pinned pages come first and carry no pill: the same page further down
253
+ already holds it, and one pill shared by two rows would jump between
254
+ them. -->
255
+ <div v-if="pinned.length" class="flex flex-col gap-0.5">
256
+ <p v-if="!compact" class="px-2 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted">
257
+ {{ labels.pinned }}
258
+ </p>
259
+ <LpSidebarRow
260
+ v-for="item in pinned"
261
+ :key="`pinned:${item.id}`"
262
+ :item="item"
263
+ :active="itemActive(item)"
264
+ :pill="false"
265
+ :pill-id="pillId"
266
+ :compact="compact"
267
+ :menu="menuOf(item)"
268
+ :preview-delay="previewDelay"
269
+ :item-active="itemActive"
270
+ @activate="activate"
271
+ />
272
+ </div>
273
+ <template v-for="(section, si) in groups" :key="si">
274
+ <!-- Compact, a group that folds is one icon: its pages open in the card
275
+ beside it, as a page's own pages do. A hundred initials in a column
276
+ would be no nav at all. -->
277
+ <div v-if="compact && section.collapsible && section.title" class="flex flex-col gap-0.5">
278
+ <LpSidebarRow
279
+ :item="sectionItem(section)"
280
+ :active="false"
281
+ :pill="false"
282
+ :pill-id="pillId"
283
+ compact
284
+ :on-path="section.items.some((i) => itemActive(i) || onPath(i))"
285
+ :preview-delay="previewDelay"
286
+ :item-active="itemActive"
287
+ @activate="activate"
288
+ />
289
+ </div>
157
290
  <CollapsibleRoot
158
- v-for="(section, si) in groups"
159
- :key="si"
291
+ v-else
160
292
  as="div"
161
293
  class="flex flex-col gap-0.5"
162
294
  :open="isOpen(section)"
@@ -165,8 +297,10 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
165
297
  >
166
298
  <!-- A plain heading while the group is fixed; a control once it folds,
167
299
  because a thing that reacts to a click must look like one. -->
300
+ <div v-if="compact && (si > 0 || pinned.length)" class="mx-3 mb-1 border-t border-line" />
168
301
  <CollapsibleTrigger
169
- v-if="section.title && section.collapsible"
302
+ v-else-if="!compact && section.title && section.collapsible"
303
+ data-lp-nav-row
170
304
  class="group/section mt-2 flex cursor-pointer items-center gap-2 rounded-control px-2 py-1 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:bg-ink/[0.04] hover:text-ink focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
171
305
  >
172
306
  <LpIcon
@@ -182,6 +316,11 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
182
316
  It is always rendered and merely fades, never removed: taking it
183
317
  out of the row changes the heading's height, and a heading that
184
318
  resizes under the cursor makes its own click feel like a misfire. -->
319
+ <span
320
+ v-if="section.items.some(waiting)"
321
+ class="size-1.5 shrink-0 rounded-full bg-brand transition-opacity duration-[var(--duration-fast)] motion-reduce:transition-none"
322
+ :class="isOpen(section) ? 'opacity-0' : 'opacity-100'"
323
+ />
185
324
  <LpBadge
186
325
  tone="neutral"
187
326
  class="shrink-0 transition-opacity duration-[var(--duration-fast)] motion-reduce:transition-none"
@@ -197,7 +336,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
197
336
  />
198
337
  </CollapsibleTrigger>
199
338
  <p
200
- v-else-if="section.title"
339
+ v-else-if="!compact && section.title"
201
340
  class="px-2 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted"
202
341
  >
203
342
  {{ section.title }}
@@ -206,58 +345,52 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
206
345
  <CollapsibleContent
207
346
  class="flex flex-col gap-0.5 overflow-hidden data-[state=open]:animate-[collapsible-down_260ms_var(--ease-emphasized)] data-[state=closed]:animate-[collapsible-up_200ms_var(--ease-emphasized)] motion-reduce:animate-none"
208
347
  >
209
- <!-- Optional right-click menu per item; passthrough when item.menu is
210
- absent or empty, so the item keeps the native context menu. Headed
211
- by the item's label and icon unless it names its own. -->
212
- <LpContextMenu
213
- v-for="item in section.items"
214
- :key="item.id"
215
- :items="item.menu ?? []"
216
- :title="(item.menuTitle ?? item.label) || undefined"
217
- :title-icon="item.icon"
218
- >
219
- <component
220
- :is="rowOf(item).is"
221
- v-bind="rowOf(item).props"
222
- :aria-current="itemActive(item) ? 'page' : undefined"
223
- :aria-disabled="item.disabled || undefined"
224
- class="group/item relative flex items-center gap-3 rounded-control px-3 py-2 text-sm font-medium outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
225
- :class="item.disabled
226
- ? 'cursor-not-allowed text-muted/50'
227
- : itemActive(item) || hovered === item.id
228
- ? 'text-ink'
229
- : 'text-muted hover:text-ink'"
230
- @click="emit('activate', item)"
231
- @pointerenter="hover(item)"
348
+ <template v-for="item in section.items" :key="item.id">
349
+ <LpSidebarRow
350
+ :item="item"
351
+ :active="itemActive(item)"
352
+ :pill="pillUnder(item)"
353
+ :pill-id="pillId"
354
+ :compact="compact"
355
+ :menu="menuOf(item)"
356
+ :expandable="!compact && !!item.children?.length"
357
+ :expanded="expanded(item)"
358
+ :on-path="onPath(item)"
359
+ :preview-delay="previewDelay"
360
+ :hovered="hovered === item.id"
361
+ :expand-label="labels.expand"
362
+ :item-active="itemActive"
363
+ @activate="activate"
364
+ @hover="hover"
365
+ @toggle="toggle(item)"
232
366
  >
233
- <!-- Sliding active pill -->
234
- <Motion
235
- v-if="pillUnder(item)"
236
- :layout-id="pillId"
237
- :transition="pillTransition"
238
- class="absolute inset-0 z-0 rounded-control bg-brand-soft"
239
- />
240
- <!-- #item slot for fully custom rows; falls back to icon+label+badge. -->
241
- <slot name="item" :item="item" :active="itemActive(item)">
242
- <LpIcon
243
- v-if="item.icon"
244
- :name="item.icon"
245
- :size="17"
246
- class="relative z-10 shrink-0 transition-colors"
247
- :class="itemActive(item) ? 'text-brand' : ''"
367
+ <template v-if="$slots.item" #default="slotProps"><slot name="item" v-bind="slotProps" /></template>
368
+ </LpSidebarRow>
369
+ <CollapsibleRoot v-if="!compact && item.children?.length" :open="expanded(item)">
370
+ <CollapsibleContent
371
+ class="flex flex-col gap-0.5 overflow-hidden data-[state=open]:animate-[collapsible-down_220ms_var(--ease-emphasized)] data-[state=closed]:animate-[collapsible-up_180ms_var(--ease-emphasized)] motion-reduce:animate-none"
372
+ >
373
+ <LpSidebarRow
374
+ v-for="child in item.children"
375
+ :key="child.id"
376
+ nested
377
+ :item="child"
378
+ :active="itemActive(child)"
379
+ :pill="pillUnder(child)"
380
+ :pill-id="pillId"
381
+ :menu="menuOf(child)"
382
+ :preview-delay="previewDelay"
383
+ :hovered="hovered === child.id"
384
+ :item-active="itemActive"
385
+ @activate="activate"
386
+ @hover="hover"
248
387
  />
249
- <span class="relative z-10 min-w-0 flex-1 truncate text-left">{{ item.label }}</span>
250
- <LpBadge
251
- v-if="item.badge != null"
252
- :tone="itemActive(item) ? 'brand' : 'neutral'"
253
- class="relative z-10 shrink-0"
254
- >
255
- {{ badgeText(item.badge) }}
256
- </LpBadge>
257
- </slot>
258
- </component>
259
- </LpContextMenu>
388
+ </CollapsibleContent>
389
+ </CollapsibleRoot>
390
+ </template>
260
391
  </CollapsibleContent>
261
392
  </CollapsibleRoot>
393
+ </template>
394
+ </div>
262
395
  </LpScrollArea>
263
396
  </template>
@@ -0,0 +1,242 @@
1
+ <script setup lang="ts">
2
+ /*
3
+ * One row of LpSidebarNav: the link or button, its active pill, icon, label,
4
+ * count and keys, its right-click menu and the card beside it. A page, a page
5
+ * under another and a pinned page are drawn by this one component, so the
6
+ * three never disagree on what a row looks like. Not exported on its own.
7
+ *
8
+ * The card beside the row does two jobs that are one mechanism: in a compact
9
+ * sidebar it is the row's name (and the pages under it), since the row shows
10
+ * only an icon; with a `preview` it is a glance at the page before going there.
11
+ */
12
+ import { Motion } from "motion-v"
13
+ import { computed, onBeforeUnmount, ref, watch } from "vue"
14
+ import { useLocale } from "../composables/useLocale"
15
+ import { usePillTransition } from "../composables/usePillTransition"
16
+ import { useLinkComponent } from "../composables/useLpConfig"
17
+ import LpBadge from "./LpBadge.vue"
18
+ import LpContextMenu, { type ContextMenuItemDef } from "./LpContextMenu.vue"
19
+ import LpHoverCard from "./LpHoverCard.vue"
20
+ import LpIcon from "./LpIcon.vue"
21
+ import LpKeys from "./LpKeys.vue"
22
+ import LpNumberFlow from "./LpNumberFlow.vue"
23
+ import { waiting, type SidebarItem } from "./sidebar"
24
+ import { resolveLink, type ResolvedLink } from "./link"
25
+
26
+ const props = withDefaults(
27
+ defineProps<{
28
+ item: SidebarItem
29
+ active: boolean
30
+ /** The pill sits under this row (active, or hovered while following hover). */
31
+ pill: boolean
32
+ pillId: string
33
+ compact?: boolean
34
+ /** A page under another: indented, a size smaller. */
35
+ nested?: boolean
36
+ menu?: ContextMenuItemDef[]
37
+ /** Has pages under it; `expanded` says whether they show. */
38
+ expandable?: boolean
39
+ expanded?: boolean
40
+ /** One of its pages is the current one: the row reads as on the path. */
41
+ onPath?: boolean
42
+ previewDelay?: number
43
+ hovered?: boolean
44
+ expandLabel?: string
45
+ itemActive?: (item: SidebarItem) => boolean
46
+ }>(),
47
+ { previewDelay: 900, menu: () => [] },
48
+ )
49
+
50
+ const emit = defineEmits<{
51
+ (e: "activate", item: SidebarItem): void
52
+ (e: "hover", item: SidebarItem): void
53
+ (e: "toggle"): void
54
+ }>()
55
+
56
+ const locale = useLocale()
57
+ const pillTransition = usePillTransition()
58
+ const linkComponent = useLinkComponent()
59
+
60
+ /*
61
+ * A disabled item is always a disabled button: an anchor cannot be disabled,
62
+ * and the old enabled-button fallback for a disabled link stayed focusable.
63
+ */
64
+ function rowOf(item: SidebarItem): ResolvedLink {
65
+ const link = item.disabled ? null : resolveLink(linkComponent.value, { to: item.to, href: item.href })
66
+ if (link) return link
67
+ return { is: "button", props: { type: "button", disabled: item.disabled || undefined } }
68
+ }
69
+
70
+ // A count that grows pulses once: a number that changes in place is easy to
71
+ // miss in the corner of the eye, and the nav is exactly the corner of the eye.
72
+ const bumped = ref(false)
73
+ let bumpTimer: ReturnType<typeof setTimeout> | undefined
74
+ watch(
75
+ () => props.item.badge,
76
+ (now, was) => {
77
+ if (typeof now !== "number" || typeof was !== "number" || now <= was) return
78
+ bumped.value = true
79
+ clearTimeout(bumpTimer)
80
+ bumpTimer = setTimeout(() => (bumped.value = false), 1200)
81
+ },
82
+ )
83
+ onBeforeUnmount(() => clearTimeout(bumpTimer))
84
+
85
+ /** A compact row without an icon shows the first letters of its name. */
86
+ const initials = computed(() =>
87
+ props.item.label
88
+ .split(/\s+/)
89
+ .filter(Boolean)
90
+ .slice(0, 2)
91
+ .map((w) => [...w][0])
92
+ .join(""),
93
+ )
94
+
95
+ // Something waiting on a page under this row shows on the row while those pages
96
+ // are out of sight, folded away or behind a compact icon: otherwise closing a
97
+ // group would hide that anything is waiting in it.
98
+ const hidden = computed(() => (props.compact || !props.expanded) && !!props.item.children?.some(waiting))
99
+ const hasBadge = computed(() => props.item.badge != null || !!props.item.dot || hidden.value)
100
+ const card = computed(() => !props.item.disabled && (props.compact || !!props.item.preview))
101
+ const delay = computed(() => (props.compact && !props.item.preview ? 250 : props.previewDelay))
102
+ </script>
103
+
104
+ <template>
105
+ <LpHoverCard
106
+ :disabled="!card"
107
+ side="right"
108
+ align="start"
109
+ :side-offset="compact ? 12 : 16"
110
+ :open-delay="delay"
111
+ :close-delay="120"
112
+ touch="none"
113
+ :panel-class="item.preview ? 'w-80 p-0 overflow-hidden' : 'w-56 p-2'"
114
+ >
115
+ <!-- The card anchors on a plain box: the context menu inside it renders a
116
+ fragment, which an as-child trigger cannot take. -->
117
+ <template #trigger>
118
+ <div class="relative flex flex-col">
119
+ <LpContextMenu :items="menu" :title="(item.menuTitle ?? item.label) || undefined" :title-icon="item.icon">
120
+ <component
121
+ :is="rowOf(item).is"
122
+ v-bind="rowOf(item).props"
123
+ :aria-current="active ? 'page' : undefined"
124
+ :aria-disabled="item.disabled || undefined"
125
+ :aria-label="compact ? item.label : undefined"
126
+ data-lp-nav-row
127
+ :aria-keyshortcuts="item.hotkey"
128
+ :aria-busy="item.busy ? true : undefined"
129
+ :data-lp-nav-nested="nested || undefined"
130
+ class="group/item relative flex items-center rounded-control font-medium outline-none transition-colors duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
131
+ :class="[
132
+ compact ? ['ml-1 size-10 justify-center', onPath && !active ? 'bg-ink/[0.06]' : ''] : nested ? 'gap-2.5 py-1.5 pl-9 pr-3 text-[13px]' : 'gap-3 px-3 py-2 text-sm',
133
+ item.disabled
134
+ ? 'cursor-not-allowed text-muted/50'
135
+ : active || hovered || onPath
136
+ ? 'text-ink'
137
+ : 'text-muted hover:text-ink',
138
+ ]"
139
+ @click="emit('activate', item)"
140
+ @pointerenter="emit('hover', item)"
141
+ >
142
+ <span
143
+ v-if="item.busy !== undefined && item.busy !== false"
144
+ class="pointer-events-none absolute bottom-0.5 z-10 h-0.5 overflow-hidden rounded-pill bg-brand/15"
145
+ :class="compact ? 'inset-x-2' : 'inset-x-3'"
146
+ >
147
+ <span
148
+ v-if="typeof item.busy === 'number'"
149
+ class="block h-full rounded-pill bg-brand transition-[width] duration-(--duration-medium) ease-[var(--ease-emphasized)]"
150
+ :style="{ width: `${Math.round(Math.min(1, Math.max(0, item.busy)) * 100)}%` }"
151
+ />
152
+ <span v-else class="lp-sweep absolute inset-y-0 left-0 w-2/5 rounded-pill bg-brand" />
153
+ </span>
154
+ <Motion v-if="pill" :layout-id="pillId" :transition="pillTransition" class="absolute inset-0 z-0 rounded-control bg-brand-soft" />
155
+ <slot :item="item" :active="active">
156
+ <span v-if="item.icon" class="relative z-10 shrink-0">
157
+ <LpIcon :name="item.icon" :size="nested ? 15 : 17" class="transition-colors" :class="active ? 'text-brand' : ''" />
158
+ <!-- Compact, the count has no room: a dot on the icon says there is one. -->
159
+ <span
160
+ v-if="compact && hasBadge"
161
+ class="absolute -right-1 -top-1 size-2 rounded-full bg-brand ring-2 ring-surface-raised"
162
+ :class="bumped ? 'animate-pulse' : ''"
163
+ />
164
+ </span>
165
+ <span v-else-if="compact" class="relative z-10 text-xs font-semibold uppercase">{{ initials }}</span>
166
+ <span v-else-if="nested" class="relative z-10 size-1 shrink-0 rounded-full bg-current opacity-50" />
167
+ <template v-if="!compact">
168
+ <span class="relative z-10 min-w-0 flex-1 truncate text-left">{{ item.label }}</span>
169
+ <!-- Over the row's end and only where nothing else sits there: in
170
+ the flow, keys that show on hover would take the label's
171
+ width even while hidden. -->
172
+ <LpKeys
173
+ v-if="item.hotkey && !hasBadge && !expandable"
174
+ :combo="item.hotkey"
175
+ size="sm"
176
+ aria-hidden="true"
177
+ class="pointer-events-none absolute right-2 z-10 rounded-small bg-surface-raised/90 opacity-0 transition-opacity duration-(--duration-fast) group-hover/item:opacity-100 group-focus-visible/item:opacity-100"
178
+ />
179
+ <span v-if="hasBadge && item.badge == null" class="relative z-10 size-2 shrink-0 rounded-full bg-brand" />
180
+ <LpBadge v-if="item.badge != null" :tone="active ? 'brand' : 'neutral'" class="relative z-10 shrink-0">
181
+ <span v-if="bumped" class="absolute inset-0 animate-ping rounded-pill bg-brand/30" />
182
+ <LpNumberFlow v-if="typeof item.badge === 'number'" :value="item.badge" grouped :locale="locale" :animate-on-mount="false" />
183
+ <template v-else>{{ item.badge }}</template>
184
+ </LpBadge>
185
+ <span v-if="expandable" class="w-5 shrink-0" aria-hidden="true" />
186
+ </template>
187
+ </slot>
188
+ </component>
189
+ </LpContextMenu>
190
+ <!-- Beside the row, not inside it: a control within the link or button
191
+ would be one control to a screen reader, and the pages under a
192
+ link have to open without following it. -->
193
+ <button
194
+ v-if="expandable && !compact"
195
+ type="button"
196
+ class="absolute right-2 top-1/2 z-20 grid size-6 -translate-y-1/2 place-items-center rounded-small text-muted outline-none transition-colors hover:bg-ink/[0.06] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
197
+ :aria-label="expandLabel"
198
+ :aria-expanded="expanded"
199
+ @click="emit('toggle')"
200
+ >
201
+ <LpIcon
202
+ name="lucide:chevron-right"
203
+ :size="14"
204
+ class="transition-transform duration-(--duration-fast) ease-[var(--ease-emphasized)] motion-reduce:transition-none"
205
+ :class="expanded ? 'rotate-90' : ''"
206
+ />
207
+ </button>
208
+ </div>
209
+ </template>
210
+
211
+ <div v-if="item.preview" class="flex flex-col">
212
+ <div v-if="compact" class="flex items-center gap-2 border-b border-line px-3 py-2 text-sm font-medium text-ink">
213
+ <span class="min-w-0 flex-1 truncate">{{ item.label }}</span>
214
+ <LpKeys v-if="item.hotkey" :combo="item.hotkey" size="sm" />
215
+ </div>
216
+ <component :is="item.preview" />
217
+ </div>
218
+ <div v-else class="flex flex-col gap-0.5">
219
+ <div class="flex items-center gap-2 px-2 py-1 text-sm font-medium text-ink">
220
+ <span class="min-w-0 flex-1 truncate">{{ item.label }}</span>
221
+ <LpBadge v-if="item.badge != null" tone="neutral">{{ typeof item.badge === "number" ? item.badge.toLocaleString(locale) : item.badge }}</LpBadge>
222
+ <LpKeys v-if="item.hotkey" :combo="item.hotkey" size="sm" />
223
+ </div>
224
+ <div v-if="item.children?.length" class="flex max-h-[min(70vh,30rem)] flex-col gap-0.5 overflow-y-auto">
225
+ <component
226
+ :is="rowOf(child).is"
227
+ v-for="child in item.children"
228
+ :key="child.id"
229
+ v-bind="rowOf(child).props"
230
+ :aria-current="itemActive?.(child) ? 'page' : undefined"
231
+ class="flex shrink-0 items-center gap-2 rounded-control px-2 py-1.5 text-left text-[13px] outline-none transition-colors hover:bg-ink/[0.05] focus-visible:ring-2 focus-visible:ring-ring"
232
+ :class="child.disabled ? 'opacity-50' : itemActive?.(child) ? 'text-brand' : 'text-muted hover:text-ink'"
233
+ @click="emit('activate', child)"
234
+ >
235
+ <LpIcon v-if="child.icon" :name="child.icon" :size="15" class="shrink-0" />
236
+ <span class="min-w-0 flex-1 truncate">{{ child.label }}</span>
237
+ <LpBadge v-if="child.badge != null" tone="neutral">{{ child.badge }}</LpBadge>
238
+ </component>
239
+ </div>
240
+ </div>
241
+ </LpHoverCard>
242
+ </template>
@@ -29,6 +29,11 @@ const props = withDefaults(
29
29
  /** Formatting for a numeric `value` — see LpNumberFlow. */
30
30
  compact?: boolean
31
31
  grouped?: boolean
32
+ /**
33
+ * Count up from zero when the tile first shows (the default), or show the
34
+ * value outright: a report or a screenshot wants the figure, not the run.
35
+ */
36
+ animateOnMount?: boolean
32
37
  decimals?: number
33
38
  prefix?: string
34
39
  valueSuffix?: string
@@ -54,7 +59,7 @@ const props = withDefaults(
54
59
  */
55
60
  loading?: boolean
56
61
  }>(),
57
- { trend: "auto", decimals: 0 },
62
+ { trend: "auto", decimals: 0, animateOnMount: true },
58
63
  )
59
64
 
60
65
  // Resolve the trend direction: explicit wins; "auto" reads a numeric delta.
@@ -159,6 +164,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
159
164
  :value="value"
160
165
  :compact="compact"
161
166
  :grouped="grouped"
167
+ :animate-on-mount="animateOnMount"
162
168
  :decimals="decimals"
163
169
  :prefix="prefix"
164
170
  :suffix="valueSuffix"