@leavepulse/ui 0.43.3 → 0.44.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/README.md +75 -7
- package/dist/assets/{LpCommandPalette-DH7sPcKP.css → LpCommandPalette-DCP5g2ik.css} +21 -21
- package/dist/assets/{LpDropzone-BBoWvi-J.css → LpDropzone-i4BdMi1H.css} +12 -12
- package/dist/assets/{LpImageEditor-Ct7Y3aNu.css → LpImageEditor-CbMA82g8.css} +6 -6
- package/dist/assets/{LpShortcutsDialog-C6YoQ8_M.css → LpShortcutsDialog-ChUyU7oV.css} +7 -7
- package/dist/assets/{LpSkeleton-D7xU8IPV.css → LpSkeleton-H3Z5dEIk.css} +4 -1
- package/dist/assets/{LpTable-BgPLJOXa.css → LpTable-DDQNdf86.css} +9 -9
- package/dist/chunks/ContextMenuItems-CrfeNPWT.js +123 -0
- package/dist/chunks/{LayoutCanvas-Dd20boUG.js → LayoutCanvas-D-jPOAvg.js} +26 -9
- package/dist/chunks/{LayoutNode-DGpZA9LX.js → LayoutNode-D8Xy9N-o.js} +20 -8
- package/dist/chunks/{LpActionBar-BlvtGB2P.js → LpActionBar-BbwCF6l5.js} +24 -10
- package/dist/chunks/{LpAddressInput-B-Zvxsn7.js → LpAddressInput-BrOqkZqg.js} +53 -48
- package/dist/chunks/{LpAlert-BAYHDSS1.js → LpAlert-DkOIjsbq.js} +15 -8
- package/dist/chunks/{LpAppShell-BGQVIY-3.js → LpAppShell-BUKPcHco.js} +51 -24
- package/dist/chunks/{LpAutocomplete-DExlIZe_.js → LpAutocomplete-DxV3Y9h4.js} +50 -23
- package/dist/chunks/{LpAvatar-B3-xAOH3.js → LpAvatar-ClBbEYZN.js} +17 -8
- package/dist/chunks/LpBreadcrumbs-B8IJgFIc.js +95 -0
- package/dist/chunks/{LpButton-5VXXCjwF.js → LpButton-kqJBHIIb.js} +44 -14
- package/dist/chunks/{LpCard-BhI1N_vL.js → LpCard-CpRByR0q.js} +1 -1
- package/dist/chunks/{LpCheckbox-CVccKYaH.js → LpCheckbox-DpqchzH4.js} +7 -3
- package/dist/chunks/{LpCodeBlock-Bo0B4TFR.js → LpCodeBlock-DeHJPq7p.js} +48 -16
- package/dist/chunks/{LpColorPicker-BlXea7LM.js → LpColorPicker-Bhf1qxhB.js} +32 -9
- package/dist/chunks/{LpConfirmDialog-Q-bxff8z.js → LpConfirmDialog-CIUOSnWo.js} +58 -16
- package/dist/chunks/LpConfirmHost-CDm0Wtpv.js +49 -0
- package/dist/chunks/LpContextMenu-DQw5dGfG.js +68 -0
- package/dist/chunks/LpDatePicker-Drirx1AK.js +191 -0
- package/dist/chunks/{LpDrawer-BZ6k3hvo.js → LpDrawer-bScx-KMO.js} +63 -26
- package/dist/chunks/{LpFileTree-_5vFBgaK.js → LpFileTree-CSI_5SK2.js} +23 -8
- package/dist/chunks/{LpFileTreeNode-BdcIYKIE.js → LpFileTreeNode-B4MiSQX2.js} +2 -2
- package/dist/chunks/LpFormField-CIk5qosh.js +87 -0
- package/dist/chunks/{LpInput-9EXWUM9B.js → LpInput-C0e8y8_d.js} +26 -11
- package/dist/chunks/{LpLightbox-2sYVMY-S.js → LpLightbox-Mr0W_6HK.js} +147 -60
- package/dist/chunks/{LpLink-DCND-zyu.js → LpLink-Krkjhe_y.js} +15 -6
- package/dist/chunks/{LpLogViewer-CaRy5MeS.js → LpLogViewer-YBOaFnc_.js} +68 -34
- package/dist/chunks/{LpMapPicker-ul4oYno6.js → LpMapPicker-NUzS9e37.js} +38 -13
- package/dist/chunks/{LpMediaGallery-FF-NquIo.js → LpMediaGallery-DfJmMW0I.js} +6 -3
- package/dist/chunks/{LpModal-Bt0pQCI_.js → LpModal-DPwGKBPv.js} +49 -26
- package/dist/chunks/{LpModalNav-YaifHrXC.js → LpModalNav-DPg5H9f3.js} +9 -6
- package/dist/chunks/{LpNotificationBell-DyGGsMAM.js → LpNotificationBell-t5yn-siX.js} +58 -24
- package/dist/chunks/{LpNumberField-CWuX08iX.js → LpNumberField-DWnQKktS.js} +20 -7
- package/dist/chunks/{LpNumberFlow-amWYaeuA.js → LpNumberFlow-z4WPZcZu.js} +22 -12
- package/dist/chunks/{LpOtpInput-y2v-pmiN.js → LpOtpInput-CziPF3Oq.js} +37 -8
- package/dist/chunks/{LpPagination-5FxjmmB5.js → LpPagination-DFngX7Xo.js} +30 -19
- package/dist/chunks/{LpPasswordInput--sjYCkLS.js → LpPasswordInput-CWVDuBJ5.js} +10 -7
- package/dist/chunks/{LpPhoneInput-BDSKVl_z.js → LpPhoneInput-CD-ZTeSd.js} +57 -33
- package/dist/chunks/{LpPie-D6OnzwlJ.js → LpPie-BLco31Nx.js} +78 -21
- package/dist/chunks/{LpPopover-8268UmlC.js → LpPopover-p_e_shN3.js} +4 -2
- package/dist/chunks/{LpProgress-Btq5W4Ut.js → LpProgress-CBU1iI69.js} +5 -4
- package/dist/chunks/{LpRadioGroup-xPIuVDO_.js → LpRadioGroup-DmEW0l78.js} +9 -2
- package/dist/chunks/{LpSegmented-fV0Sk92p.js → LpSegmented-Dejd7cIx.js} +7 -3
- package/dist/chunks/{LpSelect-ANIVtrpM.js → LpSelect-DrRD9I7L.js} +40 -18
- package/dist/chunks/{LpSidebar-ClBUwoEz.js → LpSidebar-BgwUNWpJ.js} +30 -22
- package/dist/chunks/{LpSidebarNav-llOcs_Fn.js → LpSidebarNav-Bqxz8aWT.js} +14 -9
- package/dist/chunks/{LpSkeleton-CTI-vBgW.js → LpSkeleton-BB6XIV-w.js} +1 -1
- package/dist/chunks/{LpSlider-BwNIBkAK.js → LpSlider-Cuzmrd7p.js} +7 -2
- package/dist/chunks/{LpSparkline-C6xQ2dwv.js → LpSparkline-CB80N8SJ.js} +11 -4
- package/dist/chunks/{LpStat-CQaW8R--.js → LpStat-dXtyrdX9.js} +1 -1
- package/dist/chunks/{LpSwitch-Blizvqyt.js → LpSwitch-B1aLKulj.js} +40 -26
- package/dist/chunks/{LpTableOfContents-CjvxPMpU.js → LpTableOfContents-BUcAfGPQ.js} +36 -41
- package/dist/chunks/{LpTabs-Cefe_lys.js → LpTabs-_qMVaZJ6.js} +19 -5
- package/dist/chunks/LpTextarea-yP8hGedR.js +77 -0
- package/dist/chunks/LpThemeProvider-Wjm7Tuit.js +35 -0
- package/dist/chunks/LpThemeSwitcher-CKLc10KZ.js +169 -0
- package/dist/chunks/{LpTilt-DuyKbpkA.js → LpTilt-B7KHX1zH.js} +2 -2
- package/dist/chunks/{LpTimeField-K6nAGboX.js → LpTimeField-C5HRgS5H.js} +13 -7
- package/dist/chunks/{LpToaster-gDB1EaU3.js → LpToaster--oT6lfhL.js} +57 -28
- package/dist/chunks/{LpTooltip-hCLPAqMG.js → LpTooltip-CBdzeKNf.js} +13 -6
- package/dist/chunks/LpTooltipProvider-CftjV7Xc.js +25 -0
- package/dist/chunks/{LpTopologyCanvas-Cm1L39uL.js → LpTopologyCanvas-DocsLal3.js} +6 -1
- package/dist/chunks/{LpUptimeBar-DvAMno0S.js → LpUptimeBar-DLtxOMRy.js} +43 -14
- package/dist/chunks/{LpThemeSwitcher-Cue-myST.js → useTheme-C8JHUlPM.js} +173 -215
- package/dist/component-names.d.ts +1 -1
- package/dist/components/ContextMenuItems.vue.d.ts +8 -0
- package/dist/components/ContextMenuItems.vue.js +4 -0
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.d.ts +4 -0
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LpActionBar.vue.d.ts +3 -0
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.d.ts +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.d.ts +9 -0
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.d.ts +10 -0
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.d.ts +31 -6
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.d.ts +3 -3
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBadge.vue.d.ts +2 -2
- package/dist/components/LpBreadcrumbs.vue.d.ts +13 -9
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpButton.vue.d.ts +35 -98
- package/dist/components/LpButton.vue.js +1 -1
- package/dist/components/LpCard.vue.js +1 -1
- package/dist/components/LpChart.vue.d.ts +31 -2
- package/dist/components/LpChart.vue.js +317 -95
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.d.ts +2 -0
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.d.ts +8 -10
- package/dist/components/LpCommandPalette.vue.js +88 -78
- package/dist/components/LpConfirmDialog.vue.d.ts +15 -3
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.d.ts +9 -0
- package/dist/components/LpConfirmHost.vue.js +4 -0
- package/dist/components/LpContextMenu.vue.d.ts +3 -5
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.d.ts +24 -6
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDrawer.vue.d.ts +10 -0
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +22 -6
- package/dist/components/LpFileTree.vue.d.ts +11 -2
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.d.ts +9 -0
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +92 -57
- package/dist/components/LpInput.vue.d.ts +3 -3
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpLightbox.vue.d.ts +17 -0
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLink.vue.d.ts +11 -11
- package/dist/components/LpLink.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.d.ts +20 -2
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.d.ts +4 -4
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.d.ts +16 -6
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.d.ts +0 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.d.ts +11 -2
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberField.vue.d.ts +2 -0
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.js +1 -1
- package/dist/components/LpOtpInput.vue.js +1 -1
- package/dist/components/LpPagination.vue.d.ts +2 -0
- package/dist/components/LpPagination.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.d.ts +21 -2
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPopover.vue.d.ts +2 -0
- package/dist/components/LpPopover.vue.js +1 -1
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpSection.vue.d.ts +1 -1
- package/dist/components/LpSegmented.vue.d.ts +5 -0
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.d.ts +1 -0
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.d.ts +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +11 -8
- package/dist/components/LpSidebar.vue.d.ts +6 -2
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSkeleton.vue.js +1 -1
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpSparkline.vue.d.ts +1 -0
- package/dist/components/LpSparkline.vue.js +1 -1
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTable.vue.d.ts +23 -1
- package/dist/components/LpTable.vue.js +74 -36
- package/dist/components/LpTableOfContents.vue.d.ts +4 -2
- package/dist/components/LpTableOfContents.vue.js +1 -1
- package/dist/components/LpTabs.vue.d.ts +2 -0
- package/dist/components/LpTabs.vue.js +1 -1
- package/dist/components/LpTextarea.vue.d.ts +19 -6
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeProvider.vue.d.ts +22 -0
- package/dist/components/LpThemeProvider.vue.js +4 -0
- package/dist/components/LpThemeSwitcher.vue.d.ts +13 -0
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTilt.vue.js +1 -1
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.d.ts +6 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTooltip.vue.d.ts +1 -2
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpTooltipProvider.vue.d.ts +22 -0
- package/dist/components/LpTooltipProvider.vue.js +4 -0
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.d.ts +4 -0
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/chart.d.ts +32 -1
- package/dist/components/chart.js +39 -4
- package/dist/components/codeHighlight.d.ts +6 -0
- package/dist/components/codeHighlight.js +92 -20
- package/dist/components/color.d.ts +1 -1
- package/dist/components/color.js +20 -9
- package/dist/components/commandSearch.d.ts +0 -1
- package/dist/components/commandSearch.js +1 -17
- package/dist/components/confirm.d.ts +25 -0
- package/dist/components/confirm.js +26 -0
- package/dist/components/countries.d.ts +6 -0
- package/dist/components/countries.js +7 -0
- package/dist/components/fileTree.js +16 -7
- package/dist/components/imageCrop.d.ts +42 -0
- package/dist/components/imageCrop.js +47 -0
- package/dist/components/link.d.ts +12 -0
- package/dist/components/link.js +19 -0
- package/dist/components/modalDepth.d.ts +18 -2
- package/dist/components/modalDepth.js +17 -5
- package/dist/components/otp.d.ts +5 -0
- package/dist/components/otp.js +10 -0
- package/dist/components/sidebar.d.ts +7 -1
- package/dist/components/tableSort.d.ts +8 -0
- package/dist/components/tableSort.js +31 -0
- package/dist/composables/useField.d.ts +34 -0
- package/dist/composables/useField.js +45 -0
- package/dist/composables/useFormDraft.d.ts +10 -3
- package/dist/composables/useFormDraft.js +9 -14
- package/dist/composables/useHotkeys.d.ts +17 -1
- package/dist/composables/useHotkeys.js +55 -18
- package/dist/composables/useInputFilter.d.ts +17 -0
- package/dist/composables/useInputFilter.js +47 -10
- package/dist/composables/useLpConfig.d.ts +24 -0
- package/dist/composables/useLpConfig.js +44 -0
- package/dist/composables/useRovingFocus.js +7 -0
- package/dist/composables/useTilt.d.ts +2 -2
- package/dist/composables/useTilt.js +5 -6
- package/dist/composables/useToast.d.ts +20 -0
- package/dist/composables/useToast.js +28 -4
- package/dist/composables/useZoomPan.d.ts +3 -3
- package/dist/composables/useZoomPan.js +4 -4
- package/dist/index.d.ts +33 -3
- package/dist/index.js +185 -139
- package/dist/layout/tree.d.ts +2 -0
- package/dist/layout/tree.js +16 -1
- package/dist/layout/useLayout.d.ts +15 -0
- package/dist/layout/useLayout.js +41 -14
- package/dist/theme/useTheme.d.ts +32 -9
- package/package.json +8 -7
- package/src/component-names.ts +5 -7
- package/src/components/ContextMenuItems.vue +88 -0
- package/src/components/LayoutCanvas.vue +26 -6
- package/src/components/LayoutNode.vue +19 -2
- package/src/components/LpActionBar.vue +30 -7
- package/src/components/LpAddressInput.vue +57 -41
- package/src/components/LpAlert.vue +16 -3
- package/src/components/LpAppShell.vue +45 -12
- package/src/components/LpAutocomplete.vue +46 -10
- package/src/components/LpAvatar.vue +19 -5
- package/src/components/LpBreadcrumbs.vue +65 -41
- package/src/components/LpButton.vue +59 -13
- package/src/components/LpChart.vue +272 -28
- package/src/components/LpCheckbox.vue +7 -3
- package/src/components/LpCodeBlock.vue +50 -14
- package/src/components/LpColorPicker.vue +39 -9
- package/src/components/LpCommandPalette.vue +36 -25
- package/src/components/LpConfirmDialog.vue +68 -17
- package/src/components/LpConfirmHost.vue +59 -0
- package/src/components/LpContextMenu.vue +14 -115
- package/src/components/LpDatePicker.vue +99 -68
- package/src/components/LpDrawer.vue +56 -19
- package/src/components/LpDropzone.vue +21 -4
- package/src/components/LpFileTree.vue +33 -7
- package/src/components/LpFormField.vue +30 -7
- package/src/components/LpImageEditor.vue +96 -57
- package/src/components/LpInput.vue +18 -7
- package/src/components/LpLightbox.vue +140 -29
- package/src/components/LpLink.vue +24 -13
- package/src/components/LpLogViewer.vue +86 -17
- package/src/components/LpMapPicker.vue +36 -13
- package/src/components/LpMediaGallery.vue +8 -2
- package/src/components/LpModal.vue +53 -27
- package/src/components/LpModalNav.vue +5 -2
- package/src/components/LpNotificationBell.vue +63 -17
- package/src/components/LpNumberField.vue +21 -6
- package/src/components/LpNumberFlow.vue +26 -10
- package/src/components/LpOtpInput.vue +43 -8
- package/src/components/LpPagination.vue +23 -7
- package/src/components/LpPasswordInput.vue +4 -1
- package/src/components/LpPhoneInput.vue +53 -27
- package/src/components/LpPie.vue +94 -15
- package/src/components/LpPopover.vue +3 -0
- package/src/components/LpProgress.vue +5 -3
- package/src/components/LpRadioGroup.vue +9 -2
- package/src/components/LpSegmented.vue +12 -2
- package/src/components/LpSelect.vue +0 -0
- package/src/components/LpShortcutsDialog.vue +8 -3
- package/src/components/LpSidebar.vue +14 -9
- package/src/components/LpSidebarNav.vue +17 -5
- package/src/components/LpSkeleton.vue +4 -1
- package/src/components/LpSlider.vue +7 -1
- package/src/components/LpSparkline.vue +11 -3
- package/src/components/LpSwitch.vue +30 -7
- package/src/components/LpTable.vue +91 -32
- package/src/components/LpTableOfContents.vue +50 -32
- package/src/components/LpTabs.vue +23 -6
- package/src/components/LpTextarea.vue +44 -6
- package/src/components/LpThemeProvider.vue +41 -0
- package/src/components/LpThemeSwitcher.vue +64 -17
- package/src/components/LpTilt.vue +2 -2
- package/src/components/LpTimeField.vue +14 -3
- package/src/components/LpToaster.vue +66 -26
- package/src/components/LpTooltip.vue +22 -5
- package/src/components/LpTooltipProvider.vue +24 -0
- package/src/components/LpTopologyCanvas.vue +12 -1
- package/src/components/LpUptimeBar.vue +46 -8
- package/src/components/chart.ts +77 -5
- package/src/components/codeHighlight.ts +111 -15
- package/src/components/color.ts +30 -9
- package/src/components/commandSearch.ts +0 -17
- package/src/components/confirm.ts +58 -0
- package/src/components/countries.ts +12 -0
- package/src/components/fileTree.ts +19 -7
- package/src/components/imageCrop.ts +107 -0
- package/src/components/link.ts +34 -0
- package/src/components/modalDepth.ts +48 -6
- package/src/components/otp.ts +16 -0
- package/src/components/sidebar.ts +7 -1
- package/src/components/tableSort.ts +37 -0
- package/src/composables/useField.ts +79 -0
- package/src/composables/useFormDraft.ts +25 -20
- package/src/composables/useHotkeys.ts +89 -18
- package/src/composables/useInputFilter.ts +77 -14
- package/src/composables/useLpConfig.ts +87 -0
- package/src/composables/useRovingFocus.ts +11 -0
- package/src/composables/useTilt.ts +5 -6
- package/src/composables/useToast.ts +56 -4
- package/src/composables/useZoomPan.ts +6 -6
- package/src/index.ts +38 -3
- package/src/layout/tree.ts +30 -0
- package/src/layout/useLayout.ts +68 -18
- package/src/nuxt.ts +59 -17
- package/src/runtime/config.plugin.ts +14 -0
- package/src/runtime/theme.plugin.ts +42 -22
- package/src/theme/useTheme.ts +206 -100
- package/dist/chunks/LpBreadcrumbs-p4jHe_V-.js +0 -62
- package/dist/chunks/LpContextMenu-CwW1QXe_.js +0 -217
- package/dist/chunks/LpDatePicker-CwOToxGq.js +0 -164
- package/dist/chunks/LpFormField-CazBc2-a.js +0 -62
- package/dist/chunks/LpTextarea-t7F6gv8E.js +0 -47
|
@@ -37,18 +37,31 @@ const props = withDefaults(
|
|
|
37
37
|
variant?: AlertVariants["variant"]
|
|
38
38
|
title?: string
|
|
39
39
|
icon?: string
|
|
40
|
+
/**
|
|
41
|
+
* Announce the alert to screen readers when it appears. "assertive"
|
|
42
|
+
* (role=alert) interrupts and is for something that needs action now,
|
|
43
|
+
* "polite" (role=status) waits its turn, false reads it in place. By
|
|
44
|
+
* default only `danger` interrupts: an info or success banner that is part
|
|
45
|
+
* of the page from the start should not be shouted on load.
|
|
46
|
+
*/
|
|
47
|
+
live?: "assertive" | "polite" | false
|
|
40
48
|
}>(),
|
|
41
|
-
{ variant: "info" },
|
|
49
|
+
{ variant: "info", live: undefined },
|
|
42
50
|
)
|
|
43
51
|
|
|
52
|
+
const role = computed(() => {
|
|
53
|
+
const live = props.live ?? (props.variant === "danger" ? "assertive" : false)
|
|
54
|
+
return live === "assertive" ? "alert" : live === "polite" ? "status" : undefined
|
|
55
|
+
})
|
|
56
|
+
|
|
44
57
|
const classes = computed(() => alert({ variant: props.variant }))
|
|
45
58
|
const resolvedIcon = computed(() => props.icon ?? ICONS[props.variant ?? "info"])
|
|
46
59
|
const iconTone = computed(() => ICON_TONE[props.variant ?? "info"])
|
|
47
60
|
</script>
|
|
48
61
|
|
|
49
62
|
<template>
|
|
50
|
-
<div :class="classes" role="
|
|
51
|
-
<LpIcon :name="resolvedIcon" :size="18" :class="['mt-0.5 shrink-0', iconTone]" />
|
|
63
|
+
<div :class="classes" :role="role">
|
|
64
|
+
<LpIcon :name="resolvedIcon" :size="18" :class="['mt-0.5 shrink-0', iconTone]" aria-hidden="true" />
|
|
52
65
|
<div class="min-w-0">
|
|
53
66
|
<p v-if="title" class="font-semibold text-ink">{{ title }}</p>
|
|
54
67
|
<div :class="['text-muted-strong', title ? 'mt-0.5' : '']">
|
|
@@ -31,7 +31,8 @@ export default { inheritAttrs: false }
|
|
|
31
31
|
* Overlays that aren't part of the frame (command palette, toaster) live in the
|
|
32
32
|
* host next to <LpAppShell>.
|
|
33
33
|
*/
|
|
34
|
-
import { computed, watch } from "vue"
|
|
34
|
+
import { computed, ref, useId, watch } from "vue"
|
|
35
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
35
36
|
import LpButton from "./LpButton.vue"
|
|
36
37
|
import LpIcon from "./LpIcon.vue"
|
|
37
38
|
import LpScrollArea from "./LpScrollArea.vue"
|
|
@@ -81,6 +82,8 @@ const props = withDefaults(
|
|
|
81
82
|
fullBleed?: boolean
|
|
82
83
|
/** Loading skeleton for the nav. */
|
|
83
84
|
loading?: boolean
|
|
85
|
+
/** Text overrides: the burger's name and the skip link. */
|
|
86
|
+
labels?: Partial<{ openMenu: string; skipToContent: string }>
|
|
84
87
|
}>(),
|
|
85
88
|
{
|
|
86
89
|
mobileBreakpoint: "lg",
|
|
@@ -140,10 +143,30 @@ const drawerOpen = computed({
|
|
|
140
143
|
get: () => props.open ?? false,
|
|
141
144
|
set: (v) => emit("update:open", v),
|
|
142
145
|
})
|
|
146
|
+
const text = useLabels(
|
|
147
|
+
"appShell",
|
|
148
|
+
{ openMenu: "Open menu", skipToContent: "Skip to content" },
|
|
149
|
+
() => props.labels,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
/*
|
|
153
|
+
* The page scrolls inside the shell's own region, not the window, so the
|
|
154
|
+
* router's scroll behaviour never reaches it: without this a route change from
|
|
155
|
+
* a scrolled page landed mid-way down the next one.
|
|
156
|
+
*/
|
|
157
|
+
const scrollArea = ref<{ viewportEl: HTMLElement | null } | null>(null)
|
|
158
|
+
const mainId = `lp-main-${useId()}`
|
|
159
|
+
|
|
160
|
+
// Focus instead of following the hash, which a router would treat as a navigation.
|
|
161
|
+
function skipToMain() {
|
|
162
|
+
document.getElementById(mainId)?.focus()
|
|
163
|
+
}
|
|
164
|
+
|
|
143
165
|
watch(
|
|
144
166
|
() => props.path,
|
|
145
167
|
() => {
|
|
146
168
|
if (drawerOpen.value) emit("update:open", false)
|
|
169
|
+
scrollArea.value?.viewportEl?.scrollTo({ top: 0, left: 0, behavior: "instant" })
|
|
147
170
|
},
|
|
148
171
|
)
|
|
149
172
|
|
|
@@ -176,6 +199,13 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
176
199
|
<div
|
|
177
200
|
:class="rootClass"
|
|
178
201
|
v-bind="rest">
|
|
202
|
+
<a
|
|
203
|
+
:href="`#${mainId}`"
|
|
204
|
+
@click.prevent="skipToMain"
|
|
205
|
+
class="sr-only z-(--z-toast) rounded-control bg-surface-raised px-3 py-2 text-sm font-medium text-ink focus:not-sr-only focus:fixed focus:left-3 focus:top-3 focus:outline-none focus:ring-2 focus:ring-ring"
|
|
206
|
+
>
|
|
207
|
+
{{ text.skipToContent }}
|
|
208
|
+
</a>
|
|
179
209
|
<LpSidebar
|
|
180
210
|
:model-value="modelValue"
|
|
181
211
|
:items="items"
|
|
@@ -225,7 +255,7 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
225
255
|
variant="ghost"
|
|
226
256
|
size="sm"
|
|
227
257
|
square
|
|
228
|
-
aria-label="
|
|
258
|
+
:aria-label="text.openMenu"
|
|
229
259
|
@click="emit('update:open', true)"
|
|
230
260
|
>
|
|
231
261
|
<LpIcon name="lucide:menu" :size="18" />
|
|
@@ -265,16 +295,19 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
265
295
|
|
|
266
296
|
<!-- full-bleed: page owns the whole region (canvas/map); otherwise the
|
|
267
297
|
single scroll region with padding. -->
|
|
268
|
-
<
|
|
269
|
-
<
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
298
|
+
<main :id="mainId" tabindex="-1" class="flex min-h-0 flex-1 flex-col outline-none">
|
|
299
|
+
<div v-if="fullBleed" class="min-h-0 flex-1 overflow-hidden">
|
|
300
|
+
<slot />
|
|
301
|
+
</div>
|
|
302
|
+
<LpScrollArea
|
|
303
|
+
v-else
|
|
304
|
+
ref="scrollArea"
|
|
305
|
+
class="min-h-0 flex-1"
|
|
306
|
+
:content-class="`px-4 py-6 md:px-6 ${contentClass}`"
|
|
307
|
+
>
|
|
308
|
+
<slot />
|
|
309
|
+
</LpScrollArea>
|
|
310
|
+
</main>
|
|
278
311
|
</div>
|
|
279
312
|
|
|
280
313
|
<slot name="overlays" />
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export default { inheritAttrs: false }
|
|
3
|
+
</script>
|
|
4
|
+
|
|
1
5
|
<script setup lang="ts">
|
|
2
6
|
/*
|
|
3
7
|
* Free-text autocomplete — a text field that suggests as you type but never
|
|
@@ -21,8 +25,10 @@ import {
|
|
|
21
25
|
ComboboxRoot,
|
|
22
26
|
ComboboxViewport,
|
|
23
27
|
} from "reka-ui"
|
|
24
|
-
import { computed, ref, watch } from "vue"
|
|
28
|
+
import { computed, ref, useAttrs, watch } from "vue"
|
|
29
|
+
import { useFieldControl } from "../composables/useField"
|
|
25
30
|
import { useInputFilter } from "../composables/useInputFilter"
|
|
31
|
+
import { useLabels } from "../composables/useLpConfig"
|
|
26
32
|
import { CLOSE_ICON, OPTION_ITEM, POPOVER_PANEL } from "./dropdown"
|
|
27
33
|
import LpIcon from "./LpIcon.vue"
|
|
28
34
|
|
|
@@ -78,7 +84,7 @@ const emit = defineEmits<{
|
|
|
78
84
|
(e: "select", value: string): void
|
|
79
85
|
}>()
|
|
80
86
|
|
|
81
|
-
const { isInvalid, onBeforeInput, onPaste } = useInputFilter({
|
|
87
|
+
const { isInvalid, onBeforeInput, onInput: filterInput, onCompositionEnd, onPaste } = useInputFilter({
|
|
82
88
|
pattern: () => props.pattern,
|
|
83
89
|
restrict: () => props.restrict,
|
|
84
90
|
})
|
|
@@ -88,7 +94,22 @@ const text = computed<string>({
|
|
|
88
94
|
set: (v) => emit("update:modelValue", v),
|
|
89
95
|
})
|
|
90
96
|
|
|
91
|
-
|
|
97
|
+
// class/style lay out the whole control; every other attr (name, autocomplete,
|
|
98
|
+
// @blur, @keydown…) belongs to the text <input>.
|
|
99
|
+
const attrs = useAttrs()
|
|
100
|
+
const inputAttrs = computed(() => {
|
|
101
|
+
const { class: _c, style: _s, ...rest } = attrs
|
|
102
|
+
return rest
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const field = useFieldControl(() => ({
|
|
106
|
+
wrapped: false,
|
|
107
|
+
id: typeof attrs.id === "string" ? attrs.id : undefined,
|
|
108
|
+
invalid: props.invalid || isInvalid(props.modelValue),
|
|
109
|
+
disabled: props.disabled,
|
|
110
|
+
}))
|
|
111
|
+
const isInvalidState = computed(() => field.value.invalid)
|
|
112
|
+
const labels = useLabels("autocomplete", { clear: "Clear" })
|
|
92
113
|
|
|
93
114
|
// Normalise string|option to a stable shape.
|
|
94
115
|
const items = computed<AutocompleteOption[]>(() =>
|
|
@@ -135,6 +156,13 @@ watch(canOpen, (can) => {
|
|
|
135
156
|
if (focused.value && can) open.value = true
|
|
136
157
|
})
|
|
137
158
|
|
|
159
|
+
// reka opens the list on every keystroke; below `minChars` (or with nothing to
|
|
160
|
+
// show) that only produced an empty "No suggestions" panel, so opening is
|
|
161
|
+
// refused here. Closing is always allowed.
|
|
162
|
+
function onOpenChange(next: boolean) {
|
|
163
|
+
open.value = next && canOpen.value
|
|
164
|
+
}
|
|
165
|
+
|
|
138
166
|
const hasValue = computed(() => (props.modelValue ?? "").length > 0)
|
|
139
167
|
|
|
140
168
|
const anchorSize = {
|
|
@@ -144,6 +172,7 @@ const anchorSize = {
|
|
|
144
172
|
}
|
|
145
173
|
|
|
146
174
|
function onInput(event: Event) {
|
|
175
|
+
filterInput(event)
|
|
147
176
|
typing.value = true
|
|
148
177
|
text.value = (event.target as HTMLInputElement).value
|
|
149
178
|
}
|
|
@@ -172,12 +201,14 @@ function clear() {
|
|
|
172
201
|
|
|
173
202
|
<template>
|
|
174
203
|
<ComboboxRoot
|
|
175
|
-
|
|
204
|
+
:open="open"
|
|
176
205
|
:ignore-filter="true"
|
|
177
206
|
:reset-search-term-on-blur="false"
|
|
178
207
|
:reset-search-term-on-select="false"
|
|
179
|
-
:disabled="disabled"
|
|
180
|
-
class="relative"
|
|
208
|
+
:disabled="field.disabled"
|
|
209
|
+
:class="['relative', attrs.class]"
|
|
210
|
+
:style="attrs.style as never"
|
|
211
|
+
@update:open="onOpenChange"
|
|
181
212
|
>
|
|
182
213
|
<ComboboxAnchor
|
|
183
214
|
data-lp-ring-owner
|
|
@@ -198,14 +229,18 @@ function clear() {
|
|
|
198
229
|
so `text` (and the reactive `open`) track the field exactly.
|
|
199
230
|
-->
|
|
200
231
|
<ComboboxInput
|
|
232
|
+
v-bind="inputAttrs"
|
|
233
|
+
:id="field.id"
|
|
201
234
|
:model-value="text"
|
|
202
235
|
:placeholder="placeholder"
|
|
203
|
-
:disabled="disabled"
|
|
236
|
+
:disabled="field.disabled"
|
|
237
|
+
:required="field.required || undefined"
|
|
204
238
|
:aria-invalid="isInvalidState || undefined"
|
|
205
|
-
|
|
239
|
+
:aria-describedby="field.describedBy"
|
|
206
240
|
class="lp-autofill min-w-0 flex-1 self-stretch bg-transparent outline-none placeholder:text-muted"
|
|
207
241
|
@beforeinput="onBeforeInput"
|
|
208
242
|
@paste="onPaste"
|
|
243
|
+
@compositionend="onCompositionEnd"
|
|
209
244
|
@input="onInput"
|
|
210
245
|
@focus="onFocus"
|
|
211
246
|
@blur="focused = false"
|
|
@@ -214,8 +249,9 @@ function clear() {
|
|
|
214
249
|
v-if="clearable && hasValue"
|
|
215
250
|
type="button"
|
|
216
251
|
class="group flex shrink-0 items-center text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink"
|
|
217
|
-
aria-label="
|
|
218
|
-
@pointerdown.prevent
|
|
252
|
+
:aria-label="labels.clear"
|
|
253
|
+
@pointerdown.prevent
|
|
254
|
+
@click="clear"
|
|
219
255
|
>
|
|
220
256
|
<LpIcon
|
|
221
257
|
name="lucide:x"
|
|
@@ -10,6 +10,7 @@ import { AvatarFallback, AvatarImage, AvatarRoot } from "reka-ui"
|
|
|
10
10
|
import { tv, type VariantProps } from "tailwind-variants"
|
|
11
11
|
import { computed, onMounted, ref } from "vue"
|
|
12
12
|
import LpContextMenu, { type ContextMenuItemDef } from "./LpContextMenu.vue"
|
|
13
|
+
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
13
14
|
|
|
14
15
|
const avatar = tv({
|
|
15
16
|
base: "inline-flex shrink-0 select-none items-center justify-center overflow-hidden bg-surface-soft font-medium text-muted-strong",
|
|
@@ -65,8 +66,21 @@ const sizeStyle = computed(() =>
|
|
|
65
66
|
: undefined,
|
|
66
67
|
)
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
/* "John Doe" → "JD", "Ada" → "AD". Split by code point, so a name starting
|
|
70
|
+
with an emoji or an astral letter is not cut in half. */
|
|
71
|
+
const initials = computed(() => {
|
|
72
|
+
if (props.fallback) return props.fallback
|
|
73
|
+
const words = (props.alt ?? "").trim().split(/\s+/).filter(Boolean)
|
|
74
|
+
if (!words.length) return "?"
|
|
75
|
+
const first = Array.from(words[0])
|
|
76
|
+
const letters =
|
|
77
|
+
words.length > 1 ? [first[0], Array.from(words[words.length - 1])[0]] : first.slice(0, 2)
|
|
78
|
+
return letters.join("").toUpperCase()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// A consumer's `size-20` must beat the preset's `size-9`, not race it.
|
|
82
|
+
const { class: rootClass, attrs: rest } = useMergedAttrs(() =>
|
|
83
|
+
avatar({ size: props.pixelSize ? undefined : props.size, shape: props.shape, ring: props.ring }),
|
|
70
84
|
)
|
|
71
85
|
|
|
72
86
|
// reka's AvatarImage resolves the image load on the client, so SSR renders the
|
|
@@ -83,13 +97,13 @@ onMounted(() => {
|
|
|
83
97
|
|
|
84
98
|
<template>
|
|
85
99
|
<LpContextMenu v-if="menuItems?.length" :items="menuItems">
|
|
86
|
-
<AvatarRoot :class="
|
|
100
|
+
<AvatarRoot :class="rootClass" :style="sizeStyle" v-bind="rest">
|
|
87
101
|
<AvatarImage v-if="src && mounted" :src="src" :alt="alt" class="size-full object-cover" />
|
|
88
102
|
<AvatarFallback>{{ initials }}</AvatarFallback>
|
|
89
103
|
</AvatarRoot>
|
|
90
104
|
</LpContextMenu>
|
|
91
|
-
<AvatarRoot v-else :class="
|
|
92
|
-
<AvatarImage v-if="src" :src="src" :alt="alt" class="size-full object-cover" />
|
|
105
|
+
<AvatarRoot v-else :class="rootClass" :style="sizeStyle" v-bind="rest">
|
|
106
|
+
<AvatarImage v-if="src && mounted" :src="src" :alt="alt" class="size-full object-cover" />
|
|
93
107
|
<AvatarFallback>{{ initials }}</AvatarFallback>
|
|
94
108
|
</AvatarRoot>
|
|
95
109
|
</template>
|
|
@@ -6,32 +6,51 @@ export default { inheritAttrs: false }
|
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<script setup lang="ts">
|
|
9
|
-
import type
|
|
9
|
+
import { computed, type Component } from "vue"
|
|
10
10
|
import LpIcon from "./LpIcon.vue"
|
|
11
|
+
import { useLabels, useLinkComponent } from "../composables/useLpConfig"
|
|
11
12
|
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
13
|
+
import { resolveLink } from "./link"
|
|
12
14
|
|
|
13
15
|
export interface Crumb {
|
|
14
16
|
label: string
|
|
15
17
|
href?: string
|
|
16
|
-
/** Destination for router links;
|
|
18
|
+
/** Destination for router links; rendered through the link component. */
|
|
17
19
|
to?: string | Record<string, unknown>
|
|
18
20
|
/** Leading icon, for crumbs that carry a glyph (a home root, a section). */
|
|
19
21
|
icon?: string
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
{ as: "a" },
|
|
32
|
-
)
|
|
24
|
+
const props = defineProps<{
|
|
25
|
+
items: Crumb[]
|
|
26
|
+
/**
|
|
27
|
+
* Component for linked crumbs. By default `to` and internal `href` use the
|
|
28
|
+
* link component from provideLpConfig and other hrefs a plain <a>.
|
|
29
|
+
*/
|
|
30
|
+
as?: string | Component
|
|
31
|
+
labels?: Partial<{ nav: string }>
|
|
32
|
+
}>()
|
|
33
33
|
defineEmits<{ (e: "navigate", item: Crumb, index: number): void }>()
|
|
34
34
|
|
|
35
|
+
const text = useLabels("breadcrumbs", { nav: "Breadcrumb" }, () => props.labels)
|
|
36
|
+
const linkComponent = useLinkComponent()
|
|
37
|
+
|
|
38
|
+
function linkOf(item: Crumb) {
|
|
39
|
+
if (props.as && props.as !== "a") {
|
|
40
|
+
if (item.to != null) return { is: props.as, props: { to: item.to } }
|
|
41
|
+
return item.href != null ? { is: "a", props: { href: item.href } } : null
|
|
42
|
+
}
|
|
43
|
+
return resolveLink(props.as === "a" ? "a" : linkComponent.value, { to: item.to, href: item.href })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const crumbs = computed(() =>
|
|
47
|
+
props.items.map((item, i) => ({
|
|
48
|
+
item,
|
|
49
|
+
last: i === props.items.length - 1,
|
|
50
|
+
link: i === props.items.length - 1 ? null : linkOf(item),
|
|
51
|
+
})),
|
|
52
|
+
)
|
|
53
|
+
|
|
35
54
|
// The trail sets its own gap, which a dense header bar needs to reduce.
|
|
36
55
|
const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
37
56
|
"flex items-center gap-1.5 text-sm",
|
|
@@ -39,35 +58,40 @@ const { class: rootClass, attrs: rest } = useMergedAttrs(
|
|
|
39
58
|
</script>
|
|
40
59
|
|
|
41
60
|
<template>
|
|
42
|
-
<nav
|
|
43
|
-
:class="rootClass"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
v-if="i > 0"
|
|
48
|
-
name="lucide:chevron-right"
|
|
49
|
-
:size="14"
|
|
50
|
-
class="text-muted"
|
|
51
|
-
/>
|
|
52
|
-
<component
|
|
53
|
-
:is="as"
|
|
54
|
-
v-if="(item.href || item.to) && i < items.length - 1"
|
|
55
|
-
:href="as === 'a' ? item.href : undefined"
|
|
56
|
-
:to="as === 'a' ? undefined : item.to"
|
|
57
|
-
class="inline-flex items-center gap-1.5 rounded-xs text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
58
|
-
@click="$emit('navigate', item, i)"
|
|
59
|
-
>
|
|
60
|
-
<LpIcon v-if="item.icon" :name="item.icon" :size="14" />
|
|
61
|
-
{{ item.label }}
|
|
62
|
-
</component>
|
|
63
|
-
<span
|
|
64
|
-
v-else
|
|
61
|
+
<nav :aria-label="text.nav" v-bind="rest">
|
|
62
|
+
<ol :class="rootClass">
|
|
63
|
+
<li
|
|
64
|
+
v-for="({ item, last, link }, i) in crumbs"
|
|
65
|
+
:key="i"
|
|
65
66
|
class="inline-flex items-center gap-1.5"
|
|
66
|
-
:class="i === items.length - 1 ? 'text-ink' : 'text-muted'"
|
|
67
67
|
>
|
|
68
|
-
<LpIcon
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
<LpIcon
|
|
69
|
+
v-if="i > 0"
|
|
70
|
+
name="lucide:chevron-right"
|
|
71
|
+
:size="14"
|
|
72
|
+
class="text-muted"
|
|
73
|
+
aria-hidden="true"
|
|
74
|
+
/>
|
|
75
|
+
<component
|
|
76
|
+
:is="link.is"
|
|
77
|
+
v-if="link"
|
|
78
|
+
v-bind="link.props"
|
|
79
|
+
class="inline-flex items-center gap-1.5 rounded-xs text-muted outline-none transition-colors duration-[var(--duration-fast)] hover:text-ink focus-visible:ring-2 focus-visible:ring-ring"
|
|
80
|
+
@click="$emit('navigate', item, i)"
|
|
81
|
+
>
|
|
82
|
+
<LpIcon v-if="item.icon" :name="item.icon" :size="14" />
|
|
83
|
+
{{ item.label }}
|
|
84
|
+
</component>
|
|
85
|
+
<span
|
|
86
|
+
v-else
|
|
87
|
+
class="inline-flex items-center gap-1.5"
|
|
88
|
+
:class="last ? 'text-ink' : 'text-muted'"
|
|
89
|
+
:aria-current="last ? 'page' : undefined"
|
|
90
|
+
>
|
|
91
|
+
<LpIcon v-if="item.icon" :name="item.icon" :size="14" />
|
|
92
|
+
{{ item.label }}
|
|
93
|
+
</span>
|
|
94
|
+
</li>
|
|
95
|
+
</ol>
|
|
72
96
|
</nav>
|
|
73
97
|
</template>
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The consumer's `class` is merged into the root's own rather than appended,
|
|
3
|
+
// so `class="h-12"` or `class="px-6"` beats the size's defaults.
|
|
4
|
+
export default { inheritAttrs: false }
|
|
5
|
+
</script>
|
|
6
|
+
|
|
1
7
|
<script setup lang="ts">
|
|
2
8
|
import { tv, type VariantProps } from "tailwind-variants"
|
|
3
9
|
import { computed, type Component } from "vue"
|
|
10
|
+
import { useLinkComponent } from "../composables/useLpConfig"
|
|
11
|
+
import { useMergedAttrs } from "../composables/useMergedClass"
|
|
12
|
+
import { resolveLink, type LinkTarget } from "./link"
|
|
4
13
|
|
|
5
14
|
const button = tv({
|
|
6
15
|
base: [
|
|
@@ -16,13 +25,15 @@ const button = tv({
|
|
|
16
25
|
solid: "bg-brand text-ink-inverse hover:bg-brand-hover",
|
|
17
26
|
action:
|
|
18
27
|
"bg-action text-ink-inverse shadow-[0_14px_28px_color-mix(in_srgb,var(--color-action)_24%,transparent)] hover:bg-action-hover",
|
|
19
|
-
outline: "border border-line-strong text-ink hover:border-brand hover:bg-
|
|
28
|
+
outline: "border border-line-strong text-ink hover:border-brand hover:bg-ink/[0.04]",
|
|
20
29
|
// raised surface tile with a border — a visible, neutral filled button
|
|
21
30
|
soft: "border border-line bg-surface-raised/80 text-ink hover:border-line-strong hover:bg-surface-soft",
|
|
22
|
-
ghost: "text-ink hover:bg-
|
|
23
|
-
muted: "text-muted hover:bg-
|
|
24
|
-
// high-contrast light fill (e.g. a "download" CTA on a dark hero)
|
|
25
|
-
|
|
31
|
+
ghost: "text-ink hover:bg-ink/[0.06]",
|
|
32
|
+
muted: "text-muted hover:bg-ink/[0.04] hover:text-ink",
|
|
33
|
+
// high-contrast light fill (e.g. a "download" CTA on a dark hero). The
|
|
34
|
+
// text is a fixed dark tone: `ink-inverse` is light in light themes,
|
|
35
|
+
// which made this white on white there.
|
|
36
|
+
light: "border border-black/10 bg-white text-[#0b1210] hover:bg-white/90",
|
|
26
37
|
danger: "bg-danger text-ink-inverse hover:bg-danger-hover",
|
|
27
38
|
},
|
|
28
39
|
size: {
|
|
@@ -58,7 +69,7 @@ const button = tv({
|
|
|
58
69
|
glow: true,
|
|
59
70
|
variant: "light",
|
|
60
71
|
class:
|
|
61
|
-
"shadow-[
|
|
72
|
+
"shadow-[0_8px_18px_color-mix(in_srgb,var(--color-ink)_20%,transparent)] hover:shadow-[0_12px_24px_color-mix(in_srgb,var(--color-ink)_30%,transparent)]",
|
|
62
73
|
},
|
|
63
74
|
],
|
|
64
75
|
defaultVariants: { variant: "solid", size: "md" },
|
|
@@ -74,20 +85,52 @@ const props = withDefaults(
|
|
|
74
85
|
square?: boolean
|
|
75
86
|
glow?: boolean
|
|
76
87
|
/**
|
|
77
|
-
* Root element/component. Defaults to a native <button
|
|
78
|
-
*
|
|
79
|
-
*
|
|
88
|
+
* Root element/component. Defaults to a native <button>, or a link when
|
|
89
|
+
* `to`/`href` is set (the link component from provideLpConfig for internal
|
|
90
|
+
* targets). Pass a component or "a" to override.
|
|
80
91
|
*/
|
|
81
92
|
as?: string | Component
|
|
93
|
+
/** Router destination: renders the configured link component. */
|
|
94
|
+
to?: LinkTarget
|
|
95
|
+
/** Link URL: renders an anchor (or the link component for internal paths). */
|
|
96
|
+
href?: string
|
|
82
97
|
type?: "button" | "submit" | "reset"
|
|
83
98
|
disabled?: boolean
|
|
84
99
|
}>(),
|
|
85
|
-
{
|
|
100
|
+
{ type: "button" },
|
|
86
101
|
)
|
|
87
102
|
|
|
88
|
-
const
|
|
103
|
+
const linkComponent = useLinkComponent()
|
|
104
|
+
|
|
105
|
+
const resolved = computed<{ is: string | Component; props: Record<string, unknown> }>(() => {
|
|
106
|
+
if (props.as && props.as !== "a") {
|
|
107
|
+
const target = props.to != null ? { to: props.to } : props.href != null ? { href: props.href } : {}
|
|
108
|
+
return { is: props.as, props: props.as === "button" ? {} : target }
|
|
109
|
+
}
|
|
110
|
+
if (props.as === "a") return { is: "a", props: { href: props.href } }
|
|
111
|
+
return resolveLink(linkComponent.value, { to: props.to, href: props.href }) ?? { is: "button", props: {} }
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
const isNativeButton = computed(() => resolved.value.is === "button")
|
|
115
|
+
|
|
116
|
+
/*
|
|
117
|
+
* A disabled link has no `disabled` attribute to lean on: it must leave the tab
|
|
118
|
+
* order and swallow the click. The capture phase runs before a router link's
|
|
119
|
+
* own handler, which skips navigation once the event is default-prevented.
|
|
120
|
+
*/
|
|
121
|
+
function blockDisabled(event: MouseEvent) {
|
|
122
|
+
if (!props.disabled || isNativeButton.value) return
|
|
123
|
+
event.preventDefault()
|
|
124
|
+
event.stopImmediatePropagation()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const linkProps = computed(() => {
|
|
128
|
+
if (isNativeButton.value) return {}
|
|
129
|
+
if (!props.disabled) return resolved.value.props
|
|
130
|
+
return resolved.value.is === "a" ? {} : resolved.value.props
|
|
131
|
+
})
|
|
89
132
|
|
|
90
|
-
const classes =
|
|
133
|
+
const { class: classes, attrs: rest } = useMergedAttrs(() =>
|
|
91
134
|
button({
|
|
92
135
|
variant: props.variant,
|
|
93
136
|
size: props.size,
|
|
@@ -100,11 +143,14 @@ const classes = computed(() =>
|
|
|
100
143
|
|
|
101
144
|
<template>
|
|
102
145
|
<component
|
|
103
|
-
:is="
|
|
146
|
+
:is="resolved.is"
|
|
147
|
+
v-bind="{ ...rest, ...linkProps }"
|
|
104
148
|
:type="isNativeButton ? type : undefined"
|
|
105
149
|
:disabled="isNativeButton ? disabled : undefined"
|
|
106
150
|
:aria-disabled="!isNativeButton && disabled ? 'true' : undefined"
|
|
151
|
+
:tabindex="!isNativeButton && disabled ? -1 : undefined"
|
|
107
152
|
:class="classes"
|
|
153
|
+
@click.capture="blockDisabled"
|
|
108
154
|
>
|
|
109
155
|
<slot />
|
|
110
156
|
</component>
|