@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
@@ -0,0 +1,52 @@
1
+ <script setup lang="ts">
2
+ /*
3
+ * What a tab shows: its icon with a dot or a count on the corner, its label,
4
+ * and a trailing dot or count where there is no icon to carry them. Shared by
5
+ * LpTabs and LpBottomNav, so a section tab and a page in the bottom bar read
6
+ * the same. Not exported on its own.
7
+ */
8
+ import AttentionDot from "./AttentionDot.vue"
9
+ import LpBadge from "./LpBadge.vue"
10
+ import LpIcon from "./LpIcon.vue"
11
+ import LpRollingText from "./LpRollingText.vue"
12
+
13
+ defineProps<{
14
+ item: { label: string; icon?: string; dot?: boolean; badge?: number | string }
15
+ bottom?: boolean
16
+ compact?: boolean
17
+ active?: boolean
18
+ }>()
19
+ </script>
20
+
21
+ <template>
22
+ <span v-if="item.icon" class="relative z-10 inline-flex">
23
+ <LpIcon :name="item.icon" :size="bottom ? 22 : 14" />
24
+ <AttentionDot v-if="item.dot" corner />
25
+ <!-- On a bottom bar the count rides on the icon's corner, as phone
26
+ navs draw it: the label under the icon has no room beside it. -->
27
+ <Transition name="lp-tab-badge">
28
+ <LpBadge
29
+ v-if="bottom && item.badge != null"
30
+ size="sm"
31
+ :tone="active ? 'brand' : 'neutral'"
32
+ class="absolute -right-3 -top-1.5 min-w-[18px] justify-center"
33
+ >
34
+ <LpRollingText :value="item.badge" />
35
+ </LpBadge>
36
+ </Transition>
37
+ </span>
38
+ <span class="relative z-10" :class="bottom ? 'max-w-full truncate' : compact && item.icon ? 'sr-only' : ''">{{ item.label }}</span>
39
+ <AttentionDot v-if="item.dot && !item.icon" />
40
+ <!-- The count rolls when it changes, and the chip scales in and out
41
+ rather than popping. -->
42
+ <Transition name="lp-tab-badge">
43
+ <LpBadge
44
+ v-if="!(bottom && item.icon) && item.badge != null"
45
+ size="sm"
46
+ :tone="active ? 'brand' : 'neutral'"
47
+ class="relative z-10 min-w-[18px] justify-center"
48
+ >
49
+ <LpRollingText :value="item.badge" />
50
+ </LpBadge>
51
+ </Transition>
52
+ </template>
@@ -5,13 +5,10 @@ import { TabsContent, TabsList, TabsRoot, TabsTrigger } from "reka-ui"
5
5
  import { computed, nextTick, ref, useId, watch } from "vue"
6
6
  import { usePillTransition } from "../composables/usePillTransition"
7
7
  import { prefersReducedMotion } from "../composables/easing"
8
- import AttentionDot from "./AttentionDot.vue"
9
- import LpBadge from "./LpBadge.vue"
10
8
  import LpContextMenu, { type ContextMenuItemDef } from "./LpContextMenu.vue"
11
- import LpIcon from "./LpIcon.vue"
12
- import LpRollingText from "./LpRollingText.vue"
13
9
  import LpSkeleton from "./LpSkeleton.vue"
14
- import { pickTab } from "./tabs"
10
+ import LpTabFace from "./LpTabFace.vue"
11
+ import { BOTTOM_BAR, BOTTOM_ITEM, BOTTOM_MARKER, pickTab } from "./tabs"
15
12
 
16
13
  export interface TabItem {
17
14
  value: string
@@ -177,7 +174,7 @@ usePaletteCommands(() => props.items.map(item => ({
177
174
  ref="list"
178
175
  :aria-label="label"
179
176
  :class="bottom
180
- ? 'grid w-full auto-cols-fr grid-flow-col border-t border-line bg-surface pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]'
177
+ ? BOTTOM_BAR
181
178
  : [
182
179
  'max-w-full gap-1 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden',
183
180
  block ? 'flex w-full' : 'inline-flex',
@@ -201,7 +198,7 @@ usePaletteCommands(() => props.items.map(item => ({
201
198
  class="relative inline-flex items-center justify-center outline-none transition-[color,opacity] duration-[var(--duration-fast)] focus-visible:ring-2 focus-visible:ring-ring data-[disabled]:cursor-not-allowed data-[disabled]:opacity-45"
202
199
  :class="[
203
200
  bottom
204
- ? 'min-h-14 min-w-0 flex-col gap-0.5 px-1 py-1.5 text-[11px] font-medium focus-visible:ring-inset'
201
+ ? BOTTOM_ITEM
205
202
  : 'shrink-0 gap-1.5 whitespace-nowrap text-sm',
206
203
  block && !bottom ? 'flex-1' : '',
207
204
  variant === 'underline' ? 'rounded-t-inner px-3 pt-1.5 pb-2' : '',
@@ -225,10 +222,7 @@ usePaletteCommands(() => props.items.map(item => ({
225
222
  accent ? 'bg-brand' : 'bg-ink',
226
223
  ]
227
224
  : bottom
228
- ? [
229
- 'absolute inset-x-[22%] top-0 z-0 h-0.5 rounded-b-full',
230
- accent ? 'bg-brand' : 'bg-ink',
231
- ]
225
+ ? [BOTTOM_MARKER, accent ? 'bg-brand' : 'bg-ink']
232
226
  : [
233
227
  'absolute inset-0 z-0 rounded-inner shadow-sm',
234
228
  accent
@@ -236,36 +230,7 @@ usePaletteCommands(() => props.items.map(item => ({
236
230
  : 'border border-line bg-surface-raised',
237
231
  ]"
238
232
  />
239
- <span v-if="item.icon" class="relative z-10 inline-flex">
240
- <LpIcon :name="item.icon" :size="bottom ? 22 : 14" />
241
- <AttentionDot v-if="item.dot" corner />
242
- <!-- On a bottom bar the count rides on the icon's corner, as phone
243
- navs draw it: the label under the icon has no room beside it. -->
244
- <Transition name="lp-tab-badge">
245
- <LpBadge
246
- v-if="bottom && item.badge != null"
247
- size="sm"
248
- :tone="current === item.value ? 'brand' : 'neutral'"
249
- class="absolute -right-3 -top-1.5 min-w-[18px] justify-center"
250
- >
251
- <LpRollingText :value="item.badge" />
252
- </LpBadge>
253
- </Transition>
254
- </span>
255
- <span class="relative z-10" :class="bottom ? 'max-w-full truncate' : compact && item.icon ? 'sr-only' : ''">{{ item.label }}</span>
256
- <AttentionDot v-if="item.dot && !item.icon" />
257
- <!-- The count rolls when it changes, and the chip scales in and out
258
- rather than popping. -->
259
- <Transition name="lp-tab-badge">
260
- <LpBadge
261
- v-if="!(bottom && item.icon) && item.badge != null"
262
- size="sm"
263
- :tone="current === item.value ? 'brand' : 'neutral'"
264
- class="relative z-10 min-w-[18px] justify-center"
265
- >
266
- <LpRollingText :value="item.badge" />
267
- </LpBadge>
268
- </Transition>
233
+ <LpTabFace :item="item" :bottom="bottom" :compact="compact" :active="current === item.value" />
269
234
  </TabsTrigger>
270
235
  </LpContextMenu>
271
236
  <!-- Items that arrive later (counts from a server, tabs from a route)
@@ -4,7 +4,7 @@ export default { inheritAttrs: false }
4
4
 
5
5
  <script setup lang="ts">
6
6
  import { usePaletteControl, type PaletteControlOptions } from "../composables/usePaletteCommands"
7
- import { computed, useAttrs } from "vue"
7
+ import { computed, nextTick, onMounted, ref, useAttrs, watch } from "vue"
8
8
  import { useFieldControl } from "../composables/useField"
9
9
  import { mergeClass } from "../composables/useMergedClass"
10
10
  import FieldWrapper from "./FieldWrapper.vue"
@@ -25,7 +25,14 @@ const props = withDefaults(defineProps<{
25
25
  * the <textarea> for native validation.
26
26
  */
27
27
  required?: boolean
28
- }>(), { palette: undefined })
28
+ /**
29
+ * Grows with what is typed, wrapped lines included, from `rows` up to
30
+ * `maxRows`, then scrolls; the drag handle goes, since the height is the
31
+ * text's. For a composer, where a fixed box is either cramped or empty.
32
+ */
33
+ autosize?: boolean
34
+ maxRows?: number
35
+ }>(), { palette: undefined, maxRows: 8 })
29
36
 
30
37
  defineEmits<{ (e: "update:modelValue", value: string): void }>()
31
38
 
@@ -49,9 +56,31 @@ const field = useFieldControl(() => ({
49
56
  disabled: props.disabled,
50
57
  }))
51
58
 
59
+ const box = ref<HTMLTextAreaElement | null>(null)
60
+
61
+ // Measured rather than styled with `field-sizing: content`, which WebKit does
62
+ // not have: an app in a WebKitGTK window would keep a fixed box.
63
+ function fit() {
64
+ const el = box.value
65
+ if (!props.autosize || !el) return
66
+ const style = getComputedStyle(el)
67
+ const line = parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.4
68
+ const borders = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth)
69
+ const padding = parseFloat(style.paddingTop) + parseFloat(style.paddingBottom)
70
+ el.style.height = "auto"
71
+ const max = line * props.maxRows + padding + borders
72
+ el.style.height = `${Math.min(el.scrollHeight + borders, max)}px`
73
+ el.style.overflowY = el.scrollHeight + borders > max + 1 ? "auto" : "hidden"
74
+ }
75
+ watch(() => props.modelValue, () => nextTick(fit))
76
+ onMounted(fit)
77
+
78
+ defineExpose({ el: box, fit })
79
+
52
80
  const textareaClass = computed(() =>
53
81
  mergeClass(
54
- "w-full resize-y rounded-control border bg-surface-soft px-3 py-2 text-sm text-ink outline-none transition-colors duration-[var(--duration-fast)] placeholder:text-muted focus:border-brand focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-55",
82
+ props.autosize ? "w-full resize-none" : "w-full resize-y",
83
+ "rounded-control border bg-surface-soft px-3 py-2 text-sm text-ink outline-none transition-colors duration-[var(--duration-fast)] placeholder:text-muted focus:border-brand focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-55",
55
84
  field.value.invalid ? "border-danger focus:border-danger focus:ring-danger-soft" : "border-line",
56
85
  hasField.value ? "" : (attrs.class as string),
57
86
  ),
@@ -72,6 +101,7 @@ usePaletteControl({ label: () => props.label, disabled: () => field.value.disabl
72
101
  :style="attrs.style"
73
102
  >
74
103
  <textarea
104
+ ref="box"
75
105
  v-bind="textareaAttrs"
76
106
  :id="field.id"
77
107
  :value="modelValue"
@@ -83,7 +113,7 @@ usePaletteControl({ label: () => props.label, disabled: () => field.value.disabl
83
113
  :aria-describedby="field.describedBy"
84
114
  :class="textareaClass"
85
115
  :style="hasField ? undefined : (attrs.style as never)"
86
- @input="$emit('update:modelValue', ($event.target as HTMLTextAreaElement).value)"
116
+ @input="$emit('update:modelValue', ($event.target as HTMLTextAreaElement).value); fit()"
87
117
  />
88
118
  </FieldWrapper>
89
119
  </template>
@@ -10,7 +10,7 @@ import { usePaletteTheme } from "../theme/palette"
10
10
  * An app adds its own rows through `#start` and `#end` (LpThemeStudioSection
11
11
  * gives them the panel's look) and drops built-in ones with `hide`.
12
12
  */
13
- import { computed, ref } from "vue"
13
+ import { computed, h, ref, type FunctionalComponent } from "vue"
14
14
  import { useLabels } from "../composables/useLpConfig"
15
15
  import { STUDIO_BRANDS, STUDIO_SYSTEM, useThemeStudio, type StudioState, type ThemeStudio } from "../theme/studio"
16
16
  import type { TokenSet } from "../theme/tokens"
@@ -20,7 +20,10 @@ import LpCodeBlock from "./LpCodeBlock.vue"
20
20
  import LpColorPicker from "./LpColorPicker.vue"
21
21
  import LpIcon from "./LpIcon.vue"
22
22
  import LpSegmented from "./LpSegmented.vue"
23
+ import LpDisclosure from "./LpDisclosure.vue"
24
+ import LpThemeProvider from "./LpThemeProvider.vue"
23
25
  import LpThemeStudioSection from "./LpThemeStudioSection.vue"
26
+ import { withBrand } from "../theme/define"
24
27
 
25
28
  export type ThemeStudioPart = "themes" | "brand" | "corners" | "size" | "motion" | "export"
26
29
 
@@ -100,8 +103,26 @@ function origin(event: Event): RevealOrigin | undefined {
100
103
  }
101
104
 
102
105
  const TILE =
103
- "flex min-w-0 flex-col items-center gap-1 rounded-control p-1.5 text-[11px] text-muted transition-colors duration-(--duration-fast) hover:bg-surface-soft hover:text-ink"
104
- const SWATCH = "relative size-8 overflow-hidden rounded-full ring-2 transition-[box-shadow] duration-(--duration-fast)"
106
+ "group flex min-w-0 flex-col gap-1.5 text-left text-xs text-muted transition-colors duration-(--duration-fast) hover:text-ink aria-pressed:text-ink"
107
+ const FRAME = "relative block aspect-[8/5] w-full overflow-hidden rounded-control transition-[box-shadow] duration-(--duration-fast)"
108
+ const ring = (name: string) =>
109
+ state.value.theme === name ? "ring-2 ring-brand" : "ring-1 ring-line group-hover:ring-line-strong"
110
+
111
+ /** A theme as it would look with the tuning picked here: the brand rides on whichever theme is chosen. */
112
+ const tuned = (theme: TokenSet): TokenSet => (state.value.brand ? withBrand(theme, state.value.brand) : theme)
113
+
114
+ // A window of the theme in miniature: its surfaces, text and a button in its
115
+ // brand. Painted through LpThemeProvider, so it shows the tokens as the app
116
+ // would, rather than a guess made from a few of them.
117
+ const Miniature: FunctionalComponent<{ theme: TokenSet }> = (p) =>
118
+ h(LpThemeProvider, { theme: p.theme, class: "absolute inset-0 flex bg-surface" }, () => [
119
+ h("span", { class: "h-full w-1/4 border-r border-line bg-surface-raised" }),
120
+ h("span", { class: "flex min-w-0 flex-1 flex-col gap-[10%] p-[10%]" }, [
121
+ h("span", { class: "h-[12%] w-3/4 rounded-full bg-ink/80" }),
122
+ h("span", { class: "h-[10%] w-1/2 rounded-full bg-muted/60" }),
123
+ h("span", { class: "mt-auto h-[24%] w-1/2 rounded-control bg-brand" }),
124
+ ]),
125
+ ])
105
126
 
106
127
  const tuneTo = <K extends keyof Omit<StudioState, "theme">>(key: K, value: string) => studio.tune({ [key]: value } as Partial<StudioState>)
107
128
 
@@ -109,8 +130,10 @@ usePaletteTheme(() => studio ?? undefined, { hide: props.hide })
109
130
  </script>
110
131
 
111
132
  <template>
112
- <div class="flex flex-col gap-4">
113
- <div class="flex items-center justify-between">
133
+ <div class="@container flex flex-col gap-4">
134
+ <!-- An app that titles the studio itself (a settings section) passes an
135
+ empty title; the row then shows only while Reset has something to undo. -->
136
+ <div v-if="text.title || changed" class="flex items-center justify-between">
114
137
  <p class="text-sm font-semibold text-ink">{{ text.title }}</p>
115
138
  <LpButton v-if="changed" variant="ghost" size="xs" @click="studio.reset(origin($event))">
116
139
  <LpIcon name="lucide:rotate-ccw" :size="12" />{{ text.reset }}
@@ -120,39 +143,39 @@ usePaletteTheme(() => studio ?? undefined, { hide: props.hide })
120
143
  <slot name="start" :studio="studio" />
121
144
 
122
145
  <LpThemeStudioSection v-if="shows('themes')" :label="text.themes">
123
- <div class="grid grid-cols-4 gap-2">
124
- <!-- Follows the OS between its pair: drawn as both halves. -->
146
+ <div class="grid grid-cols-3 gap-2 @md:grid-cols-4 @2xl:grid-cols-8">
147
+ <!-- Follows the OS between its pair: both halves, split corner to corner. -->
125
148
  <button
126
149
  v-if="studio.system.value"
127
150
  type="button"
128
- :class="[TILE, state.theme === STUDIO_SYSTEM && 'bg-surface-soft text-ink']"
151
+ :class="TILE"
129
152
  :aria-pressed="state.theme === STUDIO_SYSTEM"
130
153
  @click="studio.pickTheme(STUDIO_SYSTEM, origin($event))"
131
154
  >
132
- <span
133
- :class="[SWATCH, state.theme === STUDIO_SYSTEM ? 'ring-brand' : 'ring-line']"
134
- :style="{ background: `linear-gradient(135deg, ${studio.system.value.light.colors.surface} 50%, ${studio.system.value.dark.colors.surface} 50%)` }"
135
- />
155
+ <span :class="[FRAME, ring(STUDIO_SYSTEM)]">
156
+ <Miniature :theme="tuned(studio.system.value.light)" />
157
+ <span class="absolute inset-0 [clip-path:polygon(100%_0,100%_100%,0_100%)]">
158
+ <Miniature :theme="tuned(studio.system.value.dark)" />
159
+ </span>
160
+ </span>
136
161
  <span class="max-w-full truncate">{{ text.system }}</span>
137
162
  </button>
138
163
  <button
139
164
  v-for="t in studio.themes.value"
140
165
  :key="t.name"
141
166
  type="button"
142
- :class="[TILE, state.theme === t.name && 'bg-surface-soft text-ink']"
167
+ :class="TILE"
143
168
  :aria-pressed="state.theme === t.name"
144
169
  @click="studio.pickTheme(t.name, origin($event))"
145
170
  >
146
- <span :class="[SWATCH, state.theme === t.name ? 'ring-brand' : 'ring-line']" :style="{ background: t.colors.surface }">
147
- <span class="absolute inset-x-0 bottom-0 h-1/2" :style="{ background: t.colors.brand }" />
148
- </span>
171
+ <span :class="[FRAME, ring(t.name)]"><Miniature :theme="tuned(t)" /></span>
149
172
  <span class="max-w-full truncate">{{ t.name }}</span>
150
173
  </button>
151
174
  </div>
152
175
  </LpThemeStudioSection>
153
176
 
154
177
  <LpThemeStudioSection v-if="shows('brand')" :label="text.brand">
155
- <div class="flex flex-wrap items-center gap-1.5">
178
+ <div class="flex flex-wrap items-center gap-2">
156
179
  <button
157
180
  v-for="color in swatches"
158
181
  :key="color"
@@ -164,55 +187,68 @@ usePaletteTheme(() => studio ?? undefined, { hide: props.hide })
164
187
  :aria-pressed="brand === color"
165
188
  @click="setBrand(color)"
166
189
  />
167
- <LpColorPicker :model-value="brand" size="sm" :aria-label="text.anyBrand" @update:model-value="setBrand" />
190
+ <LpColorPicker
191
+ variant="swatch"
192
+ size="sm"
193
+ :selected="!swatches.includes(brand)"
194
+ :swatches="[]"
195
+ :model-value="brand"
196
+ :aria-label="text.anyBrand"
197
+ @update:model-value="setBrand"
198
+ />
168
199
  </div>
169
200
  </LpThemeStudioSection>
170
201
 
171
- <LpThemeStudioSection v-if="shows('corners')" :label="text.corners">
172
- <LpSegmented
173
- :model-value="state.corners"
174
- size="sm"
175
- :options="[{ value: 'sharp', label: text.sharp }, { value: 'default', label: text.default }, { value: 'round', label: text.round }]"
176
- @update:model-value="tuneTo('corners', $event)"
177
- />
178
- </LpThemeStudioSection>
179
-
180
- <LpThemeStudioSection v-if="shows('size')" :label="text.size">
181
- <LpSegmented
182
- :model-value="state.size"
183
- size="sm"
184
- :options="[{ value: 'compact', label: text.compact }, { value: 'default', label: text.default }, { value: 'comfortable', label: text.comfortable }]"
185
- @update:model-value="tuneTo('size', $event)"
186
- />
187
- </LpThemeStudioSection>
188
-
189
- <LpThemeStudioSection v-if="shows('motion')" :label="text.motion">
190
- <LpSegmented
191
- :model-value="state.motion"
192
- size="sm"
193
- :options="[{ value: 'fast', label: text.fast }, { value: 'normal', label: text.normal }, { value: 'slow', label: text.slow }]"
194
- @update:model-value="tuneTo('motion', $event)"
195
- />
196
- </LpThemeStudioSection>
202
+ <div v-if="shows('corners') || shows('size') || shows('motion')" class="flex flex-col gap-1.5">
203
+ <LpThemeStudioSection v-if="shows('corners')" inline :label="text.corners">
204
+ <LpSegmented
205
+ :model-value="state.corners"
206
+ size="sm"
207
+ :options="[{ value: 'sharp', label: text.sharp }, { value: 'default', label: text.default }, { value: 'round', label: text.round }]"
208
+ @update:model-value="tuneTo('corners', $event)"
209
+ />
210
+ </LpThemeStudioSection>
211
+ <LpThemeStudioSection v-if="shows('size')" inline :label="text.size">
212
+ <LpSegmented
213
+ :model-value="state.size"
214
+ size="sm"
215
+ :options="[{ value: 'compact', label: text.compact }, { value: 'default', label: text.default }, { value: 'comfortable', label: text.comfortable }]"
216
+ @update:model-value="tuneTo('size', $event)"
217
+ />
218
+ </LpThemeStudioSection>
219
+ <LpThemeStudioSection v-if="shows('motion')" inline :label="text.motion">
220
+ <LpSegmented
221
+ :model-value="state.motion"
222
+ size="sm"
223
+ :options="[{ value: 'fast', label: text.fast }, { value: 'normal', label: text.normal }, { value: 'slow', label: text.slow }]"
224
+ @update:model-value="tuneTo('motion', $event)"
225
+ />
226
+ </LpThemeStudioSection>
227
+ </div>
197
228
 
198
229
  <slot name="end" :studio="studio" />
199
230
 
200
- <div v-if="shows('export')" class="flex flex-col gap-2 border-t border-line pt-3">
201
- <div class="flex items-center justify-between gap-2">
202
- <span class="text-[11px] font-medium tracking-wide text-muted uppercase">{{ text.export }}</span>
231
+ <!-- Taking the theme into code is the developer's errand, not the person
232
+ picking colours: folded, it no longer opened every studio with an
233
+ empty block of code. -->
234
+ <LpDisclosure v-if="shows('export')">
235
+ <template #trigger>
236
+ <span class="text-xs font-medium text-muted-strong">{{ text.export }}</span>
237
+ </template>
238
+ <div class="flex flex-col gap-2 pt-1">
203
239
  <LpSegmented
204
240
  v-model="format"
205
241
  size="sm"
206
242
  :options="[{ value: 'code', label: 'Code' }, { value: 'json', label: 'JSON' }, { value: 'css', label: 'CSS' }]"
207
243
  />
244
+ <LpCodeBlock
245
+ :model-value="exported[format]"
246
+ :lang="format === 'json' ? 'json' : format === 'css' ? 'plain' : 'ts'"
247
+ copyable
248
+ height="140px"
249
+ class="text-[11px]"
250
+ />
208
251
  </div>
209
- <LpCodeBlock
210
- :model-value="exported[format]"
211
- :lang="format === 'json' ? 'json' : format === 'css' ? 'plain' : 'ts'"
212
- copyable
213
- height="140px"
214
- class="text-[11px]"
215
- />
216
- </div>
252
+ </LpDisclosure>
217
253
  </div>
218
254
  </template>
@@ -1,16 +1,24 @@
1
1
  <script setup lang="ts">
2
2
  /*
3
- * One row of LpThemeStudio: a small caps label over its controls. The studio
4
- * builds its own rows with it, and an app adding its own (a font, a density
5
- * of its own, a layout switch) through the studio's slots uses it too, so the
6
- * added row reads as part of the panel.
3
+ * One row of LpThemeStudio: a label and its controls. The studio builds its
4
+ * own rows with it, and an app adding its own (a font, a density of its own,
5
+ * a layout switch) through the studio's slots uses it too, so the added row
6
+ * reads as part of the panel.
7
+ *
8
+ * `inline` puts a single control beside its label, as a settings row does: a
9
+ * three-way switch under a label of its own read as three loose rows of
10
+ * buttons, and on a wide page it sat in the corner of an empty stripe.
7
11
  */
8
- defineProps<{ label: string }>()
12
+ defineProps<{ label: string; inline?: boolean }>()
9
13
  </script>
10
14
 
11
15
  <template>
12
- <div class="flex flex-col gap-2">
13
- <span class="text-[11px] font-medium tracking-wide text-muted uppercase">{{ label }}</span>
16
+ <div v-if="inline" class="flex min-h-8 flex-wrap items-center justify-between gap-x-4 gap-y-1.5">
17
+ <span class="text-xs font-medium text-muted-strong">{{ label }}</span>
18
+ <slot />
19
+ </div>
20
+ <div v-else class="flex flex-col gap-2">
21
+ <span class="text-xs font-medium text-muted-strong">{{ label }}</span>
14
22
  <slot />
15
23
  </div>
16
24
  </template>
@@ -0,0 +1,28 @@
1
+ /*
2
+ * Where two versions of a line differ, for LpRollingText: the common start
3
+ * and end stay put and only the middle rolls. A split never lands inside a
4
+ * word, so "Online" to "Offline" rolls the word rather than "n" to "ff";
5
+ * digits may split, so "2:54" to "2:53" rolls just the last one.
6
+ */
7
+ const LETTER = /\p{L}/u
8
+
9
+ function letter(ch: string | undefined): boolean {
10
+ return ch !== undefined && LETTER.test(ch)
11
+ }
12
+
13
+ export function splitChange(before: string, after: string): { head: string; mid: string; tail: string } {
14
+ if (before === after) return { head: "", mid: after, tail: "" }
15
+ const a = [...before]
16
+ const b = [...after]
17
+ let head = 0
18
+ while (head < a.length && head < b.length && a[head] === b[head]) head++
19
+ while (head > 0 && letter(b[head - 1]) && (letter(b[head]) || letter(a[head]))) head--
20
+ let tail = 0
21
+ while (tail < a.length - head && tail < b.length - head && a[a.length - 1 - tail] === b[b.length - 1 - tail]) tail++
22
+ while (tail > 0 && letter(b[b.length - tail]) && (letter(b[b.length - tail - 1]) || letter(a[a.length - tail - 1]))) tail--
23
+ return {
24
+ head: b.slice(0, head).join(""),
25
+ mid: b.slice(head, b.length - tail).join(""),
26
+ tail: b.slice(b.length - tail).join(""),
27
+ }
28
+ }
@@ -4,6 +4,7 @@
4
4
  * Re-exported from LpSidebar.vue for back-compat with existing import sites.
5
5
  */
6
6
 
7
+ import type { Component } from "vue"
7
8
  import type { ContextMenuItemDef } from "./LpContextMenu.vue"
8
9
 
9
10
  export interface SidebarItem {
@@ -34,6 +35,38 @@ export interface SidebarItem {
34
35
  * "invoice", a page found by the feature it used to be called.
35
36
  */
36
37
  keywords?: string[]
38
+ /**
39
+ * Pages under this one, one level deep: folded under the row and opened
40
+ * while one of them is the current page, so a section's tabs are a click
41
+ * away without opening the section first. In a compact sidebar they open in
42
+ * a card beside the icon.
43
+ */
44
+ children?: SidebarItem[]
45
+ /** A dot instead of a count: something new here that has no number to it. */
46
+ dot?: boolean
47
+ /**
48
+ * The keys that open this page ("G D"), shown at the row's end on hover and
49
+ * in the compact card. Display only: the app binds the keys.
50
+ */
51
+ hotkey?: string
52
+ /**
53
+ * What the page holds, shown in a card beside the row once the pointer has
54
+ * rested on it (`previewDelay`): a glance before going there.
55
+ */
56
+ preview?: Component
57
+ /**
58
+ * The hint-system page (or pages) this row opens. With the sidebar's
59
+ * `hints`, the row carries a dot while that page has news nobody has read,
60
+ * and loses it once the news is read there.
61
+ */
62
+ hintPage?: string | string[]
63
+ /**
64
+ * Work started on this page is still running (a publish, a deploy): a thin
65
+ * bar along the row's foot, sweeping while `true`, filled to a fraction
66
+ * while a number from 0 to 1. Leaving the page no longer hides that it is
67
+ * not done.
68
+ */
69
+ busy?: boolean | number
37
70
  }
38
71
 
39
72
  export interface SidebarSection {
@@ -59,3 +92,11 @@ export interface SidebarSection {
59
92
  /** Icon for the heading — shown when the group is collapsible. */
60
93
  icon?: string
61
94
  }
95
+
96
+ /**
97
+ * Something on this page or a page under it is waiting: a count, news, a dot.
98
+ * A row or a group whose pages are out of sight shows it for them.
99
+ */
100
+ export function waiting(item: SidebarItem): boolean {
101
+ return item.badge != null || !!item.dot || !!item.children?.some(waiting)
102
+ }
@@ -24,3 +24,12 @@ export function pickTab(items: readonly TabChoice[], current: string | undefined
24
24
  if (items.some((item) => item.value === current && !item.disabled)) return current
25
25
  return items.find((item) => !item.disabled)?.value ?? current
26
26
  }
27
+
28
+ /*
29
+ * A phone's bottom bar, shared by LpTabs (`variant="bottom"`, sections of a
30
+ * page) and LpBottomNav (pages of an app), so the two never drift apart.
31
+ */
32
+ export const BOTTOM_BAR =
33
+ "grid w-full auto-cols-fr grid-flow-col border-t border-line bg-surface pb-[env(safe-area-inset-bottom)] pl-[env(safe-area-inset-left)] pr-[env(safe-area-inset-right)]"
34
+ export const BOTTOM_ITEM = "min-h-14 min-w-0 flex-col gap-0.5 px-1 py-1.5 text-[11px] font-medium focus-visible:ring-inset"
35
+ export const BOTTOM_MARKER = "absolute inset-x-[22%] top-0 z-0 h-0.5 rounded-b-full"
package/src/index.ts CHANGED
@@ -223,6 +223,7 @@ export type { TableRowMenu, TableRowMenuResult } from "./components/tableRowMenu
223
223
  export { default as LpTableOfContents } from "./components/LpTableOfContents.vue"
224
224
  export type { TocLink } from "./components/LpTableOfContents.vue"
225
225
  export { default as LpTabs } from "./components/LpTabs.vue"
226
+ export { default as LpBottomNav } from "./components/LpBottomNav.vue"
226
227
  export type { TabItem } from "./components/LpTabs.vue"
227
228
  export { default as LpTagsInput } from "./components/LpTagsInput.vue"
228
229
  export type { TagsInputLabels } from "./components/LpTagsInput.vue"