@leavepulse/ui 0.71.2 → 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/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-Bu6ws3Fp.js → LpAddressInput-B1bB3F6M.js} +7 -7
- package/dist/chunks/{LpAlert-BHi7ARqu.js → LpAlert-CpRF9eYr.js} +3 -3
- package/dist/chunks/{LpAppShell-C5YW5pI7.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-C9pIMF2R.js → LpCodeDisplay-COdiv4xu.js} +6 -6
- package/dist/chunks/{LpColorPicker-Ee1DZiRd.js → LpColorPicker-1y1QJHZR.js} +56 -28
- 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-CXrEK0ev.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-DFdt33x8.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-Cuna8c_3.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-Dwb9MV1A.js → LpNumberFlow-CcxEGUy0.js} +4 -2
- package/dist/chunks/{LpPagination-DbeACavM.js → LpPagination-BIIiYHXR.js} +1 -1
- package/dist/chunks/{LpPaletteTheme-DkikZ362.js → LpPaletteTheme-D05ywM1W.js} +4 -4
- 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-C1uIUFxu.js → LpPie-BlWDxCtG.js} +6 -6
- package/dist/chunks/{LpPipeline-zjEM1Lwc.js → LpPipeline-BXjoxkq_.js} +4 -4
- package/dist/chunks/{LpProgress-WNaH4emv.js → LpProgress-rOlyafqp.js} +1 -1
- package/dist/chunks/{LpQuota-Cz9PvklM.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-D3pRJfRY.js → LpSelect-Cxq4yBGd.js} +3 -3
- 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-Dzn6WFzi.js → LpStat-BFCM6fUn.js} +6 -4
- 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-IupHqP0x.js → LpThemeStudio-DWtG6OZZ.js} +138 -104
- package/dist/chunks/LpThemeStudioSection-B7bt8Iyg.js +32 -0
- package/dist/chunks/{LpThemeSwitcher-DarPHuX8.js → LpThemeSwitcher-Dcx-VkSk.js} +6 -6
- 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-Cq-MiJwo.js → LpTopologyCanvas-CnUh8uzS.js} +1 -1
- 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-BbhVTPnL.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/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 +4 -4
- 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 +4 -4
- 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.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 +4 -4
- package/dist/components/LpTabs.vue.js +18 -72
- 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.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/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/icons-all.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +154 -153
- 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/LpColorPicker.vue +32 -1
- package/src/components/LpEmptyState.vue +4 -1
- package/src/components/LpNumberFlow.vue +4 -2
- package/src/components/LpRollingText.vue +27 -2
- package/src/components/LpSegmented.vue +3 -1
- package/src/components/LpShift.vue +174 -2
- package/src/components/LpSidebar.vue +304 -18
- package/src/components/LpSidebarNav.vue +200 -77
- package/src/components/LpSidebarRow.vue +238 -0
- package/src/components/LpStat.vue +7 -1
- package/src/components/LpTabFace.vue +52 -0
- package/src/components/LpTabs.vue +6 -41
- package/src/components/LpTextarea.vue +34 -4
- package/src/components/LpThemeStudio.vue +91 -55
- package/src/components/LpThemeStudioSection.vue +15 -7
- package/src/components/rolling.ts +28 -0
- package/src/components/sidebar.ts +33 -0
- package/src/components/tabs.ts +9 -0
- package/src/index.ts +1 -0
- package/src/kit-icons.ts +1 -1
- package/dist/assets/LpTabs-DT0XPTc2.css +0 -18
- package/dist/chunks/LpRollingText-DiL717LK.js +0 -51
- package/dist/chunks/LpShift-DXNsauQZ.js +0 -40
- package/dist/chunks/LpSidebar-Bk7b-K4l.js +0 -256
- package/dist/chunks/LpSidebarNav-BR2AW3KT.js +0 -216
- package/dist/chunks/LpThemeProvider-DcbfOxsU.js +0 -69
- 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
|
|
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
|
-
?
|
|
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
|
-
?
|
|
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
|
-
<
|
|
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
|
-
|
|
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-
|
|
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
|
|
104
|
-
const
|
|
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
|
-
|
|
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-
|
|
124
|
-
<!-- Follows the OS between its pair:
|
|
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="
|
|
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
|
-
:
|
|
134
|
-
|
|
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="
|
|
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="[
|
|
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-
|
|
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
|
|
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
|
-
<
|
|
172
|
-
<
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
</
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
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
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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-
|
|
13
|
-
<span class="text-
|
|
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 {
|
package/src/components/tabs.ts
CHANGED
|
@@ -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"
|