@leavepulse/ui 0.71.1 → 0.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{LpCommandPalette-DgPZX0us.css → LpCommandPalette-Rtbg669l.css} +19 -19
- package/dist/assets/{LpNumberFlow-rZkwQwlU.css → LpNumberFlow-Chp9pEDe.css} +8 -0
- package/dist/assets/LpTabs-CDchzoRL.css +18 -0
- package/dist/chunks/{ContextMenuItems-eS7qx9gD.js → ContextMenuItems-BXumicbh.js} +1 -1
- package/dist/chunks/{DropzoneLinkField-DkFhdfEJ.js → DropzoneLinkField-D2Rnkcys.js} +2 -2
- package/dist/chunks/{FieldWrapper-YzbvvC3M.js → FieldWrapper-DnA1rxZ8.js} +1 -1
- package/dist/chunks/{LayoutCanvas-Ba4A9QC5.js → LayoutCanvas-CNOAUk7D.js} +1 -1
- package/dist/chunks/{LayoutNode-CTTTLMDM.js → LayoutNode-DF5BMN0J.js} +1 -1
- package/dist/chunks/{LpActionBar-B7anGlkI.js → LpActionBar-DfiBL1dE.js} +3 -3
- package/dist/chunks/{LpAddressInput-CD0bzrt-.js → LpAddressInput-B1bB3F6M.js} +7 -7
- package/dist/chunks/{LpAlert-BHi7ARqu.js → LpAlert-CpRF9eYr.js} +3 -3
- package/dist/chunks/{LpAppShell-1RmBp6xQ.js → LpAppShell-D8Q0Jv9d.js} +68 -18
- package/dist/chunks/{LpAutocomplete-DDqe2M5g.js → LpAutocomplete-DOu7FN5P.js} +2 -2
- package/dist/chunks/{LpAvatar-DqfKCIdr.js → LpAvatar-Cg6Az3wz.js} +1 -1
- package/dist/chunks/{LpBadge-C8fKEmTF.js → LpBadge-vtCpsDmv.js} +3 -3
- package/dist/chunks/LpBottomNav-VXJf0cKc.js +89 -0
- package/dist/chunks/{LpBreadcrumbs-DhcoStFa.js → LpBreadcrumbs-BdzjQcql.js} +3 -3
- package/dist/chunks/{LpButton-BQDAFXBd.js → LpButton-Dk7eT4ro.js} +1 -1
- package/dist/chunks/{LpCalendar-NmrA47J9.js → LpCalendar-CuoWks-0.js} +1 -1
- package/dist/chunks/{LpCard-CqKLnEhl.js → LpCard-Ce3rw8zm.js} +1 -1
- package/dist/chunks/{LpCheckbox-BoOuLnGB.js → LpCheckbox-DLX4amba.js} +2 -2
- package/dist/chunks/{LpCodeBlock-Czu-fq1M.js → LpCodeBlock-j0W5bpmF.js} +2 -2
- package/dist/chunks/{LpCodeDisplay-BK2FHbtM.js → LpCodeDisplay-COdiv4xu.js} +6 -6
- package/dist/chunks/{LpColorPicker-Ee1DZiRd.js → LpColorPicker-1y1QJHZR.js} +56 -28
- package/dist/chunks/{LpCommandScope-ubtWDMa0.js → LpCommandScope-BlKcgBnK.js} +1 -1
- package/dist/chunks/{LpConfirmDialog-i15pulMT.js → LpConfirmDialog-w1ni5nxh.js} +3 -3
- package/dist/chunks/{LpConfirmHost-D0oGavtd.js → LpConfirmHost-Br_-Gn-6.js} +1 -1
- package/dist/chunks/{LpContextMenu-DXpFJjlO.js → LpContextMenu-rx2tCjwk.js} +2 -2
- package/dist/chunks/{LpCopyButton-Dmq7DmFw.js → LpCopyButton-DaD1KCn6.js} +2 -2
- package/dist/chunks/{LpCountdown-DTdMF8DL.js → LpCountdown-Cgy0mVEq.js} +3 -3
- package/dist/chunks/{LpDataList-CFPtSsBB.js → LpDataList-CkcGFJgv.js} +3 -3
- package/dist/chunks/{LpDatePicker-cC9K-Pk8.js → LpDatePicker-CA0QTfBC.js} +4 -4
- package/dist/chunks/{LpDateRangePicker-BVQKPbvo.js → LpDateRangePicker-u3J51xXk.js} +2 -2
- package/dist/chunks/{LpDisclosure-hRnX3FWL.js → LpDisclosure-Cpa7Hc5T.js} +1 -1
- package/dist/chunks/{LpDrawer-u4rxnuR-.js → LpDrawer-RRamxbLV.js} +1 -1
- package/dist/chunks/{LpDropdownMenu-BgZ0Z_Im.js → LpDropdownMenu-Dl7Z4ZhS.js} +2 -2
- package/dist/chunks/{LpEditable-CjxuOVh_.js → LpEditable-D3PvD6Zm.js} +5 -5
- package/dist/chunks/{LpEmptyState-CKFTtQgJ.js → LpEmptyState-EQHiRYhG.js} +7 -3
- package/dist/chunks/{LpFieldNotice-CPI8EwtU.js → LpFieldNotice-CzQ2_5rQ.js} +1 -1
- package/dist/chunks/{LpFileTree-BH0gNlSJ.js → LpFileTree-D7KTaJiM.js} +2 -2
- package/dist/chunks/{LpFileTreeNode-gPVB1oXM.js → LpFileTreeNode-14GVqQTd.js} +5 -5
- package/dist/chunks/{LpFormField-gRASfyj2.js → LpFormField-MSQhRCTz.js} +2 -2
- package/dist/chunks/{LpGauge-BdjPGnqw.js → LpGauge-BO1PYYqD.js} +2 -2
- package/dist/chunks/{LpHintLayer-C2asBIfs.js → LpHintLayer-CmYm37z7.js} +2 -2
- package/dist/chunks/{LpHints-CNZQ76JV.js → LpHints-iLIRUkCj.js} +3 -3
- package/dist/chunks/{LpIcon-DkP-9mzK.js → LpIcon--5WSb3jJ.js} +1 -1
- package/dist/chunks/{LpInfraNode-yTh4-5qa.js → LpInfraNode-BT0VQrOa.js} +2 -2
- package/dist/chunks/{LpInput-2H-5ILOB.js → LpInput-1eNvtzXs.js} +2 -2
- package/dist/chunks/{LpLightbox-lAt5uIfx.js → LpLightbox-DWw6OSA4.js} +3 -3
- package/dist/chunks/{LpLoader-DjJEX8hL.js → LpLoader-B82_CcV0.js} +1 -1
- package/dist/chunks/{LpLogViewer-x78YfhJf.js → LpLogViewer-pzanZASC.js} +6 -6
- package/dist/chunks/{LpMapPicker-Dp5RSCi9.js → LpMapPicker-DNIUvF6V.js} +2 -2
- package/dist/chunks/{LpMediaGallery-D92oJfY_.js → LpMediaGallery-Dx8xzg5N.js} +3 -3
- package/dist/chunks/{LpModal-CpH0bNo7.js → LpModal-BnjZ2iem.js} +1 -1
- package/dist/chunks/{LpModalNav-BH4ANQGJ.js → LpModalNav-Cu04W1pV.js} +4 -4
- package/dist/chunks/{LpNotificationBell-BdUQqZhs.js → LpNotificationBell-PSQqFqEv.js} +8 -8
- package/dist/chunks/{LpNumberField-DZY3Z670.js → LpNumberField-e0IBkWU1.js} +1 -1
- package/dist/chunks/{LpNumberFlow-5khYxsfX.js → LpNumberFlow-CcxEGUy0.js} +10 -7
- package/dist/chunks/{LpPagination-DbeACavM.js → LpPagination-BIIiYHXR.js} +1 -1
- package/dist/chunks/{LpPaletteTheme-CqWNRwHE.js → LpPaletteTheme-D05ywM1W.js} +8 -6
- package/dist/chunks/{LpPasswordInput-BzAZsf5V.js → LpPasswordInput-vdcw-koG.js} +2 -2
- package/dist/chunks/{LpPhoneInput-BMpd-0QR.js → LpPhoneInput-CETEp5W7.js} +2 -2
- package/dist/chunks/{LpPie-DdFSTbGE.js → LpPie-BlWDxCtG.js} +6 -6
- package/dist/chunks/{LpPipeline-zjEM1Lwc.js → LpPipeline-BXjoxkq_.js} +4 -4
- package/dist/chunks/{LpProgress-BX33EKkN.js → LpProgress-rOlyafqp.js} +1 -1
- package/dist/chunks/{LpQuota-DqNv1yjs.js → LpQuota-B12yijdB.js} +3 -3
- package/dist/chunks/{LpRadioGroup-CsXeV9Ct.js → LpRadioGroup-ggaPL92T.js} +1 -1
- package/dist/chunks/{LpRangeCalendar-l-0nSFmY.js → LpRangeCalendar-CYYUYqJ7.js} +1 -1
- package/dist/chunks/{LpRelativeTime-UM8EkfrN.js → LpRelativeTime-DwgCb5-8.js} +1 -1
- package/dist/chunks/{LpRepeater-OvzByE5L.js → LpRepeater-fliVBcN7.js} +3 -3
- package/dist/chunks/LpRollingText-B54Pje7P.js +86 -0
- package/dist/chunks/{LpScramble-TDO5Kdou.js → LpScramble-Cw-af0Yh.js} +1 -1
- package/dist/chunks/{LpSecretField-DwQiPOko.js → LpSecretField-D_IMxEGW.js} +7 -7
- package/dist/chunks/{LpSection-DcQsieHG.js → LpSection-DFK_uoJE.js} +1 -1
- package/dist/chunks/{LpSegmented-D2VST0o3.js → LpSegmented-CeACsMMA.js} +4 -2
- package/dist/chunks/{LpSelect-DYX2-u5A.js → LpSelect-Cxq4yBGd.js} +7 -7
- package/dist/chunks/LpShift-BAp8lGoa.js +160 -0
- package/dist/chunks/LpSidebar-BftYsk5d.js +494 -0
- package/dist/chunks/LpSidebarNav-BzNWMZfc.js +357 -0
- package/dist/chunks/LpSidebarRow-BIReMT1f.js +302 -0
- package/dist/chunks/{LpStat-DIsQfbeD.js → LpStat-BFCM6fUn.js} +9 -7
- package/dist/chunks/{LpStatus-BQU1TUsv.js → LpStatus-BWTLVPVh.js} +3 -3
- package/dist/chunks/{LpStepMark-CXa7DJy_.js → LpStepMark-DkdkebLC.js} +3 -3
- package/dist/chunks/{LpStepper-C9RM_yTG.js → LpStepper-BgqjKrqg.js} +1 -1
- package/dist/chunks/{LpSwap-DzE6M3Mg.js → LpSwap-B5YOXgtx.js} +1 -1
- package/dist/chunks/{LpSwitch-bGZxUDSx.js → LpSwitch-DyrRes7s.js} +1 -1
- package/dist/chunks/LpTabFace-D6BMCbAP.js +77 -0
- package/dist/chunks/{LpTableFilter-CTZMqFDU.js → LpTableFilter-Dkzamzkl.js} +3 -3
- package/dist/chunks/{LpTagsInput-DrjmrOge.js → LpTagsInput-DAZNNehI.js} +5 -5
- package/dist/chunks/{LpTextarea-BbYqmG1S.js → LpTextarea-DbmVPj2X.js} +32 -7
- package/dist/chunks/{useTheme-BOV_-AiY.js → LpThemeProvider-8MG6yqU9.js} +66 -1
- package/dist/chunks/{LpThemeStudio-B2nPaG-h.js → LpThemeStudio-DWtG6OZZ.js} +140 -106
- package/dist/chunks/LpThemeStudioSection-B7bt8Iyg.js +32 -0
- package/dist/chunks/{LpThemeSwitcher-C0A5sg_E.js → LpThemeSwitcher-Dcx-VkSk.js} +9 -8
- package/dist/chunks/{LpTimeField-DQ8WvPr2.js → LpTimeField-BawSVsmt.js} +1 -1
- package/dist/chunks/{LpToaster-BTTl_wuy.js → LpToaster-50hp_B5O.js} +2 -2
- package/dist/chunks/{LpTopologyCanvas-B0wEvAoq.js → LpTopologyCanvas-CnUh8uzS.js} +43 -13
- package/dist/chunks/{LpTour-BgCfNlaW.js → LpTour-DC3LTlxg.js} +1 -1
- package/dist/chunks/{LpTruncate-TcqfkyJM.js → LpTruncate-Ced4gquB.js} +1 -1
- package/dist/chunks/{LpUptimeBar-DRadGq4Y.js → LpUptimeBar-CuB0LX8K.js} +4 -4
- package/dist/chunks/{MenuTitle-Dab3EGOp.js → MenuTitle-BmCNNTKH.js} +1 -1
- package/dist/chunks/{OptionContent-DPzNsybd.js → OptionContent-22UT8zLn.js} +1 -1
- package/dist/chunks/{PickerField-DwLw7j0L.js → PickerField-Bfh8id3d.js} +2 -2
- package/dist/chunks/{RevealToggle-BVFM_FAU.js → RevealToggle-B0ITuIX-.js} +2 -2
- package/dist/chunks/{TourCard--2IfgnUU.js → TourCard-CBkj1GYi.js} +5 -5
- package/dist/chunks/{icons-CLu1iR1j.js → icons-DMITlfw5.js} +1 -1
- package/dist/chunks/{palette-BiY7yQZL.js → palette-Cvxl_1tM.js} +1 -1
- package/dist/component-names.d.ts +1 -1
- package/dist/components/ContextMenuItems.vue.js +1 -1
- package/dist/components/DropzoneLinkField.vue.js +1 -1
- package/dist/components/FieldWrapper.vue.js +1 -1
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.d.ts +67 -2
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBadge.vue.js +1 -1
- package/dist/components/LpBottomNav.vue.d.ts +28 -0
- package/dist/components/LpBottomNav.vue.js +4 -0
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpCalendar.vue.js +1 -1
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpChart.vue.js +26 -8
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpCodeDisplay.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.d.ts +15 -2
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.js +47 -26
- package/dist/components/LpCommandScope.vue.js +1 -1
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.js +1 -1
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpCopyButton.vue.js +1 -1
- package/dist/components/LpCountdown.vue.js +1 -1
- package/dist/components/LpDataList.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDateRangePicker.vue.js +1 -1
- package/dist/components/LpDisclosure.vue.js +1 -1
- package/dist/components/LpDock.vue.js +1 -1
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropdownMenu.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +2 -2
- package/dist/components/LpEditable.vue.js +1 -1
- package/dist/components/LpEmptyState.vue.d.ts +2 -0
- package/dist/components/LpEmptyState.vue.js +1 -1
- package/dist/components/LpFieldNotice.vue.js +1 -1
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpGauge.vue.js +1 -1
- package/dist/components/LpHintLayer.vue.js +1 -1
- package/dist/components/LpHints.vue.js +1 -1
- package/dist/components/LpIcon.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +2 -2
- package/dist/components/LpInfraNode.vue.js +1 -1
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLoader.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.d.ts +1 -1
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.js +1 -1
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPaletteFacts.vue.js +2 -2
- package/dist/components/LpPaletteHeader.vue.js +2 -2
- package/dist/components/LpPaletteStats.vue.js +2 -2
- package/dist/components/LpPaletteTheme.vue.d.ts +2 -0
- package/dist/components/LpPaletteTheme.vue.js +1 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.js +1 -1
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPipeline.vue.js +1 -1
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpQuota.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpRangeCalendar.vue.js +1 -1
- package/dist/components/LpRelativeTime.vue.js +1 -1
- package/dist/components/LpRepeater.vue.js +1 -1
- package/dist/components/LpRollingText.vue.d.ts +2 -2
- package/dist/components/LpRollingText.vue.js +1 -1
- package/dist/components/LpScramble.vue.js +1 -1
- package/dist/components/LpSecretField.vue.js +1 -1
- package/dist/components/LpSection.vue.js +1 -1
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShift.vue.d.ts +20 -0
- package/dist/components/LpShift.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +1 -1
- package/dist/components/LpSidebar.vue.d.ts +74 -23
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.d.ts +30 -2
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSidebarRow.vue.d.ts +49 -0
- package/dist/components/LpSidebarRow.vue.js +4 -0
- package/dist/components/LpStat.vue.d.ts +11 -0
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpStatus.vue.js +1 -1
- package/dist/components/LpStepMark.vue.js +1 -1
- package/dist/components/LpStepper.vue.js +1 -1
- package/dist/components/LpSwap.vue.js +1 -1
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTabFace.vue.d.ts +14 -0
- package/dist/components/LpTabFace.vue.js +4 -0
- package/dist/components/LpTable.vue.js +11 -11
- package/dist/components/LpTableFilter.vue.js +1 -1
- package/dist/components/LpTabs.vue.d.ts +12 -4
- package/dist/components/LpTabs.vue.js +22 -74
- package/dist/components/LpTagsInput.vue.js +1 -1
- package/dist/components/LpTextarea.vue.d.ts +19 -1
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeProvider.vue.js +1 -1
- package/dist/components/LpThemeStudio.vue.d.ts +2 -2
- package/dist/components/LpThemeStudio.vue.js +1 -1
- package/dist/components/LpThemeStudioSection.vue.d.ts +4 -1
- package/dist/components/LpThemeStudioSection.vue.js +1 -1
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTopologyCanvas.vue.d.ts +3 -1
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpTour.vue.js +1 -1
- package/dist/components/LpTruncate.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/MenuTitle.vue.js +1 -1
- package/dist/components/OptionContent.vue.js +1 -1
- package/dist/components/PickerField.vue.js +1 -1
- package/dist/components/RevealToggle.vue.js +1 -1
- package/dist/components/TourCard.vue.js +1 -1
- package/dist/components/chart.d.ts +14 -0
- package/dist/components/chart.js +13 -0
- package/dist/components/format.d.ts +10 -3
- package/dist/components/format.js +14 -2
- package/dist/components/menuWhen.js +1 -1
- package/dist/components/rolling.d.ts +5 -0
- package/dist/components/rolling.js +23 -0
- package/dist/components/sidebar.d.ts +33 -0
- package/dist/components/tabs.d.ts +3 -0
- package/dist/components/tabs.js +6 -0
- package/dist/composables/usePaletteCommands.js +1 -0
- package/dist/composables/useReveal.js +1 -22
- package/dist/icons-all.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +156 -154
- package/dist/theme/palette.d.ts +3 -0
- package/package.json +1 -1
- package/src/component-names.ts +1 -0
- package/src/components/LpAppShell.vue +75 -7
- package/src/components/LpBottomNav.vue +84 -0
- package/src/components/LpChart.vue +23 -3
- package/src/components/LpColorPicker.vue +32 -1
- package/src/components/LpCommandPalette.vue +23 -5
- package/src/components/LpEmptyState.vue +4 -1
- package/src/components/LpNumberFlow.vue +23 -9
- package/src/components/LpPaletteTheme.vue +3 -3
- package/src/components/LpRollingText.vue +27 -2
- package/src/components/LpSegmented.vue +3 -1
- package/src/components/LpSelect.vue +5 -4
- package/src/components/LpShift.vue +174 -2
- package/src/components/LpSidebar.vue +304 -18
- package/src/components/LpSidebarNav.vue +200 -70
- package/src/components/LpSidebarRow.vue +238 -0
- package/src/components/LpStat.vue +12 -4
- package/src/components/LpTabFace.vue +52 -0
- package/src/components/LpTabs.vue +15 -42
- package/src/components/LpTextarea.vue +34 -4
- package/src/components/LpThemeStudio.vue +92 -56
- package/src/components/LpThemeStudioSection.vue +15 -7
- package/src/components/LpThemeSwitcher.vue +4 -1
- package/src/components/LpTopologyCanvas.vue +35 -4
- package/src/components/chart.ts +26 -0
- package/src/components/format.ts +31 -5
- package/src/components/rolling.ts +28 -0
- package/src/components/sidebar.ts +33 -0
- package/src/components/tabs.ts +9 -0
- package/src/composables/usePaletteCommands.ts +3 -0
- package/src/composables/useReveal.ts +7 -30
- package/src/index.ts +2 -1
- package/src/kit-icons.ts +1 -1
- package/src/theme/palette.ts +4 -2
- package/dist/assets/LpTabs-CO3EcsNV.css +0 -18
- package/dist/chunks/LpRollingText-DiL717LK.js +0 -51
- package/dist/chunks/LpShift-DXNsauQZ.js +0 -40
- package/dist/chunks/LpSidebar-D44peKq2.js +0 -256
- package/dist/chunks/LpSidebarNav--VwkXO_a.js +0 -211
- package/dist/chunks/LpThemeProvider-DcbfOxsU.js +0 -69
- package/dist/chunks/LpThemeStudioSection-BwhqYDLf.js +0 -20
|
@@ -12,18 +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 { usePillTransition } from "../composables/usePillTransition"
|
|
19
17
|
import LpBadge from "./LpBadge.vue"
|
|
20
18
|
import LpContextMenu from "./LpContextMenu.vue"
|
|
21
19
|
import LpIcon from "./LpIcon.vue"
|
|
22
20
|
import LpScrollArea from "./LpScrollArea.vue"
|
|
23
21
|
import LpSkeleton from "./LpSkeleton.vue"
|
|
22
|
+
import LpSidebarRow from "./LpSidebarRow.vue"
|
|
24
23
|
import type { SidebarItem, SidebarSection } from "./sidebar"
|
|
25
|
-
import { resolveLink, type ResolvedLink } from "./link"
|
|
26
|
-
import { useLinkComponent } from "../composables/useLpConfig"
|
|
27
24
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
28
25
|
|
|
29
26
|
const props = withDefaults(
|
|
@@ -51,13 +48,21 @@ const props = withDefaults(
|
|
|
51
48
|
* short rail (an app switcher, a settings list) can turn it on.
|
|
52
49
|
*/
|
|
53
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 }
|
|
54
58
|
}>(),
|
|
55
|
-
{ skeletonRows: 6, skeletonHeader: true, collapsedKeys: () => [], hoverFollow: false },
|
|
59
|
+
{ skeletonRows: 6, skeletonHeader: true, collapsedKeys: () => [], hoverFollow: false, pinned: () => [], previewDelay: 900 },
|
|
56
60
|
)
|
|
57
61
|
|
|
58
62
|
const emit = defineEmits<{
|
|
59
63
|
(e: "activate", item: SidebarItem): void
|
|
60
64
|
(e: "toggle-section", key: string, collapsed: boolean): void
|
|
65
|
+
(e: "toggle-pin", id: string): void
|
|
61
66
|
}>()
|
|
62
67
|
|
|
63
68
|
/** `key` when the app set one, else the title — see SidebarSection.key. */
|
|
@@ -73,7 +78,7 @@ function sectionKey(section: SidebarSection): string {
|
|
|
73
78
|
const foldedKeys = computed(() => {
|
|
74
79
|
const holdsActive = new Set(
|
|
75
80
|
props.groups
|
|
76
|
-
.filter((g) => g.items.some((i) => props.itemActive(i)))
|
|
81
|
+
.filter((g) => g.items.some((i) => props.itemActive(i) || onPath(i)))
|
|
77
82
|
.map(sectionKey),
|
|
78
83
|
)
|
|
79
84
|
return new Set(
|
|
@@ -82,11 +87,10 @@ const foldedKeys = computed(() => {
|
|
|
82
87
|
})
|
|
83
88
|
|
|
84
89
|
function isOpen(section: SidebarSection): boolean {
|
|
85
|
-
|
|
90
|
+
// Compact, a group has no heading to unfold it by.
|
|
91
|
+
return props.compact || !foldedKeys.value.has(sectionKey(section))
|
|
86
92
|
}
|
|
87
93
|
|
|
88
|
-
const pillTransition = usePillTransition()
|
|
89
|
-
|
|
90
94
|
const hovered = ref<string | null>(null)
|
|
91
95
|
|
|
92
96
|
function hover(item: SidebarItem) {
|
|
@@ -98,16 +102,107 @@ function pillUnder(item: SidebarItem): boolean {
|
|
|
98
102
|
return hovered.value ? hovered.value === item.id : props.itemActive(item)
|
|
99
103
|
}
|
|
100
104
|
|
|
101
|
-
|
|
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
|
+
}
|
|
102
138
|
|
|
103
139
|
/*
|
|
104
|
-
*
|
|
105
|
-
* and
|
|
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.
|
|
106
145
|
*/
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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 { id: `${SECTION}${sectionKey(section)}`, label: section.title ?? "", icon, children: section.items }
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function activate(item: SidebarItem) {
|
|
203
|
+
if (item.id.startsWith(SECTION)) return
|
|
204
|
+
if (item.children?.length && !item.to && !item.href) toggle(item)
|
|
205
|
+
emit("activate", item)
|
|
111
206
|
}
|
|
112
207
|
|
|
113
208
|
// The nav fills the remaining height with a fixed gap between groups; a short sidebar needs both under the consumer's control.
|
|
@@ -144,12 +239,51 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
144
239
|
<LpScrollArea
|
|
145
240
|
v-else
|
|
146
241
|
class="min-h-0 flex-1"
|
|
147
|
-
content-class="flex min-h-full flex-col gap-4"
|
|
242
|
+
:content-class="compact ? 'flex min-h-full flex-col gap-1' : 'flex min-h-full flex-col gap-4'"
|
|
148
243
|
@pointerleave="hovered = null"
|
|
149
244
|
>
|
|
245
|
+
<!-- `contents`: no box of its own, only somewhere for the rows' keys to bubble to. -->
|
|
246
|
+
<div class="contents" @keydown="onKey">
|
|
247
|
+
<!-- Pinned pages come first and carry no pill: the same page further down
|
|
248
|
+
already holds it, and one pill shared by two rows would jump between
|
|
249
|
+
them. -->
|
|
250
|
+
<div v-if="pinned.length" class="flex flex-col gap-0.5">
|
|
251
|
+
<p v-if="!compact" class="px-2 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted">
|
|
252
|
+
{{ labels.pinned }}
|
|
253
|
+
</p>
|
|
254
|
+
<LpSidebarRow
|
|
255
|
+
v-for="item in pinned"
|
|
256
|
+
:key="`pinned:${item.id}`"
|
|
257
|
+
:item="item"
|
|
258
|
+
:active="itemActive(item)"
|
|
259
|
+
:pill="false"
|
|
260
|
+
:pill-id="pillId"
|
|
261
|
+
:compact="compact"
|
|
262
|
+
:menu="menuOf(item)"
|
|
263
|
+
:preview-delay="previewDelay"
|
|
264
|
+
:item-active="itemActive"
|
|
265
|
+
@activate="activate"
|
|
266
|
+
/>
|
|
267
|
+
</div>
|
|
268
|
+
<template v-for="(section, si) in groups" :key="si">
|
|
269
|
+
<!-- Compact, a group that folds is one icon: its pages open in the card
|
|
270
|
+
beside it, as a page's own pages do. A hundred initials in a column
|
|
271
|
+
would be no nav at all. -->
|
|
272
|
+
<div v-if="compact && section.collapsible && section.title" class="flex flex-col gap-0.5">
|
|
273
|
+
<LpSidebarRow
|
|
274
|
+
:item="sectionItem(section)"
|
|
275
|
+
:active="false"
|
|
276
|
+
:pill="false"
|
|
277
|
+
:pill-id="pillId"
|
|
278
|
+
compact
|
|
279
|
+
:on-path="section.items.some((i) => itemActive(i) || onPath(i))"
|
|
280
|
+
:preview-delay="previewDelay"
|
|
281
|
+
:item-active="itemActive"
|
|
282
|
+
@activate="activate"
|
|
283
|
+
/>
|
|
284
|
+
</div>
|
|
150
285
|
<CollapsibleRoot
|
|
151
|
-
v-
|
|
152
|
-
:key="si"
|
|
286
|
+
v-else
|
|
153
287
|
as="div"
|
|
154
288
|
class="flex flex-col gap-0.5"
|
|
155
289
|
:open="isOpen(section)"
|
|
@@ -158,8 +292,10 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
158
292
|
>
|
|
159
293
|
<!-- A plain heading while the group is fixed; a control once it folds,
|
|
160
294
|
because a thing that reacts to a click must look like one. -->
|
|
295
|
+
<div v-if="compact && (si > 0 || pinned.length)" class="mx-3 mb-1 border-t border-line" />
|
|
161
296
|
<CollapsibleTrigger
|
|
162
|
-
v-if="section.title && section.collapsible"
|
|
297
|
+
v-else-if="!compact && section.title && section.collapsible"
|
|
298
|
+
data-lp-nav-row
|
|
163
299
|
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"
|
|
164
300
|
>
|
|
165
301
|
<LpIcon
|
|
@@ -190,7 +326,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
190
326
|
/>
|
|
191
327
|
</CollapsibleTrigger>
|
|
192
328
|
<p
|
|
193
|
-
v-else-if="section.title"
|
|
329
|
+
v-else-if="!compact && section.title"
|
|
194
330
|
class="px-2 pb-1 pt-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted"
|
|
195
331
|
>
|
|
196
332
|
{{ section.title }}
|
|
@@ -199,58 +335,52 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
199
335
|
<CollapsibleContent
|
|
200
336
|
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"
|
|
201
337
|
>
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
:
|
|
214
|
-
|
|
215
|
-
:
|
|
216
|
-
:
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
: itemActive(item) || hovered === item.id
|
|
221
|
-
? 'text-ink'
|
|
222
|
-
: 'text-muted hover:text-ink'"
|
|
223
|
-
@click="emit('activate', item)"
|
|
224
|
-
@pointerenter="hover(item)"
|
|
338
|
+
<template v-for="item in section.items" :key="item.id">
|
|
339
|
+
<LpSidebarRow
|
|
340
|
+
:item="item"
|
|
341
|
+
:active="itemActive(item)"
|
|
342
|
+
:pill="pillUnder(item)"
|
|
343
|
+
:pill-id="pillId"
|
|
344
|
+
:compact="compact"
|
|
345
|
+
:menu="menuOf(item)"
|
|
346
|
+
:expandable="!compact && !!item.children?.length"
|
|
347
|
+
:expanded="expanded(item)"
|
|
348
|
+
:on-path="onPath(item)"
|
|
349
|
+
:preview-delay="previewDelay"
|
|
350
|
+
:hovered="hovered === item.id"
|
|
351
|
+
:expand-label="labels.expand"
|
|
352
|
+
:item-active="itemActive"
|
|
353
|
+
@activate="activate"
|
|
354
|
+
@hover="hover"
|
|
355
|
+
@toggle="toggle(item)"
|
|
225
356
|
>
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
:
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
:
|
|
238
|
-
:
|
|
239
|
-
|
|
240
|
-
:
|
|
357
|
+
<template v-if="$slots.item" #default="slotProps"><slot name="item" v-bind="slotProps" /></template>
|
|
358
|
+
</LpSidebarRow>
|
|
359
|
+
<CollapsibleRoot v-if="!compact && item.children?.length" :open="expanded(item)">
|
|
360
|
+
<CollapsibleContent
|
|
361
|
+
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"
|
|
362
|
+
>
|
|
363
|
+
<LpSidebarRow
|
|
364
|
+
v-for="child in item.children"
|
|
365
|
+
:key="child.id"
|
|
366
|
+
nested
|
|
367
|
+
:item="child"
|
|
368
|
+
:active="itemActive(child)"
|
|
369
|
+
:pill="pillUnder(child)"
|
|
370
|
+
:pill-id="pillId"
|
|
371
|
+
:menu="menuOf(child)"
|
|
372
|
+
:preview-delay="previewDelay"
|
|
373
|
+
:hovered="hovered === child.id"
|
|
374
|
+
:item-active="itemActive"
|
|
375
|
+
@activate="activate"
|
|
376
|
+
@hover="hover"
|
|
241
377
|
/>
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
:tone="itemActive(item) ? 'brand' : 'neutral'"
|
|
246
|
-
class="relative z-10 shrink-0"
|
|
247
|
-
>
|
|
248
|
-
{{ item.badge }}
|
|
249
|
-
</LpBadge>
|
|
250
|
-
</slot>
|
|
251
|
-
</component>
|
|
252
|
-
</LpContextMenu>
|
|
378
|
+
</CollapsibleContent>
|
|
379
|
+
</CollapsibleRoot>
|
|
380
|
+
</template>
|
|
253
381
|
</CollapsibleContent>
|
|
254
382
|
</CollapsibleRoot>
|
|
383
|
+
</template>
|
|
384
|
+
</div>
|
|
255
385
|
</LpScrollArea>
|
|
256
386
|
</template>
|
|
@@ -0,0 +1,238 @@
|
|
|
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 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
|
+
const hasBadge = computed(() => props.item.badge != null || props.item.dot)
|
|
96
|
+
const card = computed(() => !props.item.disabled && (props.compact || !!props.item.preview))
|
|
97
|
+
const delay = computed(() => (props.compact && !props.item.preview ? 250 : props.previewDelay))
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<template>
|
|
101
|
+
<LpHoverCard
|
|
102
|
+
:disabled="!card"
|
|
103
|
+
side="right"
|
|
104
|
+
align="start"
|
|
105
|
+
:side-offset="compact ? 12 : 16"
|
|
106
|
+
:open-delay="delay"
|
|
107
|
+
:close-delay="120"
|
|
108
|
+
touch="none"
|
|
109
|
+
:panel-class="item.preview ? 'w-80 p-0 overflow-hidden' : 'w-56 p-2'"
|
|
110
|
+
>
|
|
111
|
+
<!-- The card anchors on a plain box: the context menu inside it renders a
|
|
112
|
+
fragment, which an as-child trigger cannot take. -->
|
|
113
|
+
<template #trigger>
|
|
114
|
+
<div class="relative flex flex-col">
|
|
115
|
+
<LpContextMenu :items="menu" :title="(item.menuTitle ?? item.label) || undefined" :title-icon="item.icon">
|
|
116
|
+
<component
|
|
117
|
+
:is="rowOf(item).is"
|
|
118
|
+
v-bind="rowOf(item).props"
|
|
119
|
+
:aria-current="active ? 'page' : undefined"
|
|
120
|
+
:aria-disabled="item.disabled || undefined"
|
|
121
|
+
:aria-label="compact ? item.label : undefined"
|
|
122
|
+
data-lp-nav-row
|
|
123
|
+
:aria-keyshortcuts="item.hotkey"
|
|
124
|
+
:aria-busy="item.busy ? true : undefined"
|
|
125
|
+
:data-lp-nav-nested="nested || undefined"
|
|
126
|
+
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"
|
|
127
|
+
:class="[
|
|
128
|
+
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',
|
|
129
|
+
item.disabled
|
|
130
|
+
? 'cursor-not-allowed text-muted/50'
|
|
131
|
+
: active || hovered || onPath
|
|
132
|
+
? 'text-ink'
|
|
133
|
+
: 'text-muted hover:text-ink',
|
|
134
|
+
]"
|
|
135
|
+
@click="emit('activate', item)"
|
|
136
|
+
@pointerenter="emit('hover', item)"
|
|
137
|
+
>
|
|
138
|
+
<span
|
|
139
|
+
v-if="item.busy !== undefined && item.busy !== false"
|
|
140
|
+
class="pointer-events-none absolute bottom-0.5 z-10 h-0.5 overflow-hidden rounded-pill bg-brand/15"
|
|
141
|
+
:class="compact ? 'inset-x-2' : 'inset-x-3'"
|
|
142
|
+
>
|
|
143
|
+
<span
|
|
144
|
+
v-if="typeof item.busy === 'number'"
|
|
145
|
+
class="block h-full rounded-pill bg-brand transition-[width] duration-(--duration-medium) ease-[var(--ease-emphasized)]"
|
|
146
|
+
:style="{ width: `${Math.round(Math.min(1, Math.max(0, item.busy)) * 100)}%` }"
|
|
147
|
+
/>
|
|
148
|
+
<span v-else class="lp-sweep absolute inset-y-0 left-0 w-2/5 rounded-pill bg-brand" />
|
|
149
|
+
</span>
|
|
150
|
+
<Motion v-if="pill" :layout-id="pillId" :transition="pillTransition" class="absolute inset-0 z-0 rounded-control bg-brand-soft" />
|
|
151
|
+
<slot :item="item" :active="active">
|
|
152
|
+
<span v-if="item.icon" class="relative z-10 shrink-0">
|
|
153
|
+
<LpIcon :name="item.icon" :size="nested ? 15 : 17" class="transition-colors" :class="active ? 'text-brand' : ''" />
|
|
154
|
+
<!-- Compact, the count has no room: a dot on the icon says there is one. -->
|
|
155
|
+
<span
|
|
156
|
+
v-if="compact && hasBadge"
|
|
157
|
+
class="absolute -right-1 -top-1 size-2 rounded-full bg-brand ring-2 ring-surface-raised"
|
|
158
|
+
:class="bumped ? 'animate-pulse' : ''"
|
|
159
|
+
/>
|
|
160
|
+
</span>
|
|
161
|
+
<span v-else-if="compact" class="relative z-10 text-xs font-semibold uppercase">{{ initials }}</span>
|
|
162
|
+
<span v-else-if="nested" class="relative z-10 size-1 shrink-0 rounded-full bg-current opacity-50" />
|
|
163
|
+
<template v-if="!compact">
|
|
164
|
+
<span class="relative z-10 min-w-0 flex-1 truncate text-left">{{ item.label }}</span>
|
|
165
|
+
<!-- Over the row's end and only where nothing else sits there: in
|
|
166
|
+
the flow, keys that show on hover would take the label's
|
|
167
|
+
width even while hidden. -->
|
|
168
|
+
<LpKeys
|
|
169
|
+
v-if="item.hotkey && !hasBadge && !expandable"
|
|
170
|
+
:combo="item.hotkey"
|
|
171
|
+
size="sm"
|
|
172
|
+
aria-hidden="true"
|
|
173
|
+
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"
|
|
174
|
+
/>
|
|
175
|
+
<span v-if="item.dot && item.badge == null" class="relative z-10 size-2 shrink-0 rounded-full bg-brand" />
|
|
176
|
+
<LpBadge v-if="item.badge != null" :tone="active ? 'brand' : 'neutral'" class="relative z-10 shrink-0">
|
|
177
|
+
<span v-if="bumped" class="absolute inset-0 animate-ping rounded-pill bg-brand/30" />
|
|
178
|
+
<LpNumberFlow v-if="typeof item.badge === 'number'" :value="item.badge" grouped :locale="locale" :animate-on-mount="false" />
|
|
179
|
+
<template v-else>{{ item.badge }}</template>
|
|
180
|
+
</LpBadge>
|
|
181
|
+
<span v-if="expandable" class="w-5 shrink-0" aria-hidden="true" />
|
|
182
|
+
</template>
|
|
183
|
+
</slot>
|
|
184
|
+
</component>
|
|
185
|
+
</LpContextMenu>
|
|
186
|
+
<!-- Beside the row, not inside it: a control within the link or button
|
|
187
|
+
would be one control to a screen reader, and the pages under a
|
|
188
|
+
link have to open without following it. -->
|
|
189
|
+
<button
|
|
190
|
+
v-if="expandable && !compact"
|
|
191
|
+
type="button"
|
|
192
|
+
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"
|
|
193
|
+
:aria-label="expandLabel"
|
|
194
|
+
:aria-expanded="expanded"
|
|
195
|
+
@click="emit('toggle')"
|
|
196
|
+
>
|
|
197
|
+
<LpIcon
|
|
198
|
+
name="lucide:chevron-right"
|
|
199
|
+
:size="14"
|
|
200
|
+
class="transition-transform duration-(--duration-fast) ease-[var(--ease-emphasized)] motion-reduce:transition-none"
|
|
201
|
+
:class="expanded ? 'rotate-90' : ''"
|
|
202
|
+
/>
|
|
203
|
+
</button>
|
|
204
|
+
</div>
|
|
205
|
+
</template>
|
|
206
|
+
|
|
207
|
+
<div v-if="item.preview" class="flex flex-col">
|
|
208
|
+
<div v-if="compact" class="flex items-center gap-2 border-b border-line px-3 py-2 text-sm font-medium text-ink">
|
|
209
|
+
<span class="min-w-0 flex-1 truncate">{{ item.label }}</span>
|
|
210
|
+
<LpKeys v-if="item.hotkey" :combo="item.hotkey" size="sm" />
|
|
211
|
+
</div>
|
|
212
|
+
<component :is="item.preview" />
|
|
213
|
+
</div>
|
|
214
|
+
<div v-else class="flex flex-col gap-0.5">
|
|
215
|
+
<div class="flex items-center gap-2 px-2 py-1 text-sm font-medium text-ink">
|
|
216
|
+
<span class="min-w-0 flex-1 truncate">{{ item.label }}</span>
|
|
217
|
+
<LpBadge v-if="item.badge != null" tone="neutral">{{ typeof item.badge === "number" ? item.badge.toLocaleString(locale) : item.badge }}</LpBadge>
|
|
218
|
+
<LpKeys v-if="item.hotkey" :combo="item.hotkey" size="sm" />
|
|
219
|
+
</div>
|
|
220
|
+
<div v-if="item.children?.length" class="flex max-h-[min(70vh,30rem)] flex-col gap-0.5 overflow-y-auto">
|
|
221
|
+
<component
|
|
222
|
+
:is="rowOf(child).is"
|
|
223
|
+
v-for="child in item.children"
|
|
224
|
+
:key="child.id"
|
|
225
|
+
v-bind="rowOf(child).props"
|
|
226
|
+
:aria-current="itemActive?.(child) ? 'page' : undefined"
|
|
227
|
+
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"
|
|
228
|
+
:class="child.disabled ? 'opacity-50' : itemActive?.(child) ? 'text-brand' : 'text-muted hover:text-ink'"
|
|
229
|
+
@click="emit('activate', child)"
|
|
230
|
+
>
|
|
231
|
+
<LpIcon v-if="child.icon" :name="child.icon" :size="15" class="shrink-0" />
|
|
232
|
+
<span class="min-w-0 flex-1 truncate">{{ child.label }}</span>
|
|
233
|
+
<LpBadge v-if="child.badge != null" tone="neutral">{{ child.badge }}</LpBadge>
|
|
234
|
+
</component>
|
|
235
|
+
</div>
|
|
236
|
+
</div>
|
|
237
|
+
</LpHoverCard>
|
|
238
|
+
</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.
|
|
@@ -141,10 +146,12 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
141
146
|
</Transition>
|
|
142
147
|
</span>
|
|
143
148
|
</div>
|
|
144
|
-
|
|
149
|
+
<!-- A narrow tile moves the delta under the figure rather than breaking the
|
|
150
|
+
figure itself: "2085 ч" split across two lines reads as two values. -->
|
|
151
|
+
<div class="flex flex-wrap items-baseline gap-x-2">
|
|
145
152
|
<!-- The placeholder sits INSIDE the value's own type size, so its line box
|
|
146
153
|
is the real one: the tile cannot change height when the figure lands. -->
|
|
147
|
-
<span class="font-mono text-3xl font-medium tracking-tight text-ink">
|
|
154
|
+
<span class="whitespace-nowrap font-mono text-3xl font-medium tracking-tight text-ink">
|
|
148
155
|
<!-- Each side wrapped in a plain element: LpNumberFlow renders more
|
|
149
156
|
than one root node, which a Transition cannot fade. -->
|
|
150
157
|
<Transition v-bind="FADE">
|
|
@@ -157,6 +164,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
157
164
|
:value="value"
|
|
158
165
|
:compact="compact"
|
|
159
166
|
:grouped="grouped"
|
|
167
|
+
:animate-on-mount="animateOnMount"
|
|
160
168
|
:decimals="decimals"
|
|
161
169
|
:prefix="prefix"
|
|
162
170
|
:suffix="valueSuffix"
|
|
@@ -173,7 +181,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
173
181
|
</span>
|
|
174
182
|
<span
|
|
175
183
|
v-else-if="showDelta"
|
|
176
|
-
class="inline-flex items-center gap-0.5 text-sm font-medium tabular-nums"
|
|
184
|
+
class="inline-flex items-center gap-0.5 whitespace-nowrap text-sm font-medium tabular-nums"
|
|
177
185
|
:class="trendColor"
|
|
178
186
|
>
|
|
179
187
|
<LpIcon :name="trendIcon" :size="14" />
|