@leavepulse/ui 0.78.3 → 0.79.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/chunks/{ContextMenuItems-DmaKSJVv.js → ContextMenuItems-DLK4q-mm.js} +1 -1
- package/dist/chunks/{DropzoneLinkField-CyE7TJ5f.js → DropzoneLinkField-CcJu8De3.js} +1 -1
- package/dist/chunks/{FieldLeading-mr81aEAP.js → FieldLeading-CldGLINn.js} +1 -1
- package/dist/chunks/{FieldWrapper-DnuMTrqW.js → FieldWrapper-Cj8ZgoKp.js} +1 -1
- package/dist/chunks/{IconCloseButton-COZEsa9u.js → IconCloseButton-CV4iHRpy.js} +1 -1
- package/dist/chunks/{LayoutCanvas-CZKOltfA.js → LayoutCanvas-BQbq7Ekw.js} +1 -1
- package/dist/chunks/{LayoutNode-D2f7v683.js → LayoutNode-BMedRk3g.js} +1 -1
- package/dist/chunks/{LpActionBar-BGMk5KoG.js → LpActionBar-C7g52Cyo.js} +1 -1
- package/dist/chunks/{LpAddressInput-ft8fQOGx.js → LpAddressInput-1uakhQk5.js} +7 -7
- package/dist/chunks/{LpAlert-CjvTZIKF.js → LpAlert-DWFeR82L.js} +1 -1
- package/dist/chunks/{LpAppShell-DRHoQtr-.js → LpAppShell-InIK01Nn.js} +4 -4
- package/dist/chunks/{LpAutocomplete-CrZbd0uv.js → LpAutocomplete-DY3LvpB9.js} +4 -4
- package/dist/chunks/{LpAvatar-cQVsNk6F.js → LpAvatar-CEZmMcGh.js} +1 -1
- package/dist/chunks/{LpBadge-D5QQdDQ_.js → LpBadge-C_VqLHtg.js} +1 -1
- package/dist/chunks/{LpBottomNav-zxLGGbjm.js → LpBottomNav-4I89sPDZ.js} +1 -1
- package/dist/chunks/{LpBreadcrumbs-CIIBsygI.js → LpBreadcrumbs-1BBzrfxG.js} +1 -1
- package/dist/chunks/{LpCalendar-D4-LVjfF.js → LpCalendar-D2dcXjUK.js} +1 -1
- package/dist/chunks/{LpCheckbox-CCWCwD0y.js → LpCheckbox-CN-MEnbX.js} +2 -2
- package/dist/chunks/{LpCodeBlock-DBqy4RLd.js → LpCodeBlock-Cj7zk4QE.js} +2 -2
- package/dist/chunks/{LpCodeDisplay-D59uPwRb.js → LpCodeDisplay-COzosm77.js} +4 -4
- package/dist/chunks/{LpColorPicker-BONURBFN.js → LpColorPicker-BezW6DX1.js} +3 -3
- package/dist/chunks/{LpConfirmDialog-CnrmzkuN.js → LpConfirmDialog-D9GkDMYr.js} +2 -2
- package/dist/chunks/{LpConfirmHost-CwbObVGH.js → LpConfirmHost-BH7kf0dm.js} +1 -1
- package/dist/chunks/{LpContextMenu-Bx_IhQDS.js → LpContextMenu-bn5bc9t7.js} +2 -2
- package/dist/chunks/{LpContextSwitcher-1C1tExdl.js → LpContextSwitcher-CGr6ZAuq.js} +2 -2
- package/dist/chunks/{LpCopyButton-sAMvWeLP.js → LpCopyButton-BSr6YaFR.js} +1 -1
- package/dist/chunks/{LpDataList-CaVfsozH.js → LpDataList-Cv7ZUlyy.js} +2 -2
- package/dist/chunks/{LpDatePicker-C2AOtxgF.js → LpDatePicker-CR7vsb5M.js} +5 -5
- package/dist/chunks/{LpDateRangePicker-CiXdIAyh.js → LpDateRangePicker-B9fRO3G6.js} +3 -3
- package/dist/chunks/{LpDisclosure-DG7JCo-d.js → LpDisclosure-CE4mneaE.js} +1 -1
- package/dist/chunks/{LpDrawer-DXNQFpAA.js → LpDrawer-DU_5MqBz.js} +1 -1
- package/dist/chunks/{LpDropdownMenu-CLzxIOgM.js → LpDropdownMenu-_u6GPWhp.js} +2 -2
- package/dist/chunks/{LpEditable-DVT0Rh8F.js → LpEditable-DYoMayjt.js} +2 -2
- package/dist/chunks/{LpEmptyState-DurPF001.js → LpEmptyState-DpdHeSfz.js} +1 -1
- package/dist/chunks/{LpFieldNotice-DJ10cYIo.js → LpFieldNotice-DONFCWV7.js} +1 -1
- package/dist/chunks/{LpFileTree-CpNurwZp.js → LpFileTree-f5ktGqsN.js} +4 -4
- package/dist/chunks/{LpFileTreeNode-DdF8Cq-s.js → LpFileTreeNode-nbp-JwV9.js} +4 -4
- package/dist/chunks/{LpFormField-BKYn9dyP.js → LpFormField-Bz50_LZW.js} +1 -1
- package/dist/chunks/{LpHintLayer-BeKRsRon.js → LpHintLayer-B6bA5Cb_.js} +1 -1
- package/dist/chunks/{LpHints-qf3xYsza.js → LpHints-DyJ5hVcr.js} +3 -3
- package/dist/chunks/{LpIcon-C6JpQ5td.js → LpIcon-BCQLuHSP.js} +1 -1
- package/dist/chunks/{LpInfraNode-Bb5nbuFY.js → LpInfraNode-BS1RXYAs.js} +2 -2
- package/dist/chunks/{LpInput-CbI7z5fQ.js → LpInput-BjbXdjcW.js} +3 -3
- package/dist/chunks/{LpInspector-DO3fTCQ1.js → LpInspector-BIyJyM6r.js} +4 -4
- package/dist/chunks/{LpLightbox-CtDTQV01.js → LpLightbox-CSAJuUaJ.js} +2 -2
- package/dist/chunks/{LpLogInspector-CcSJSGan.js → LpLogInspector-OJEz1vod.js} +2 -2
- package/dist/chunks/{LpLogViewer-CWu49Dko.js → LpLogViewer-CRLwQpk0.js} +6 -6
- package/dist/chunks/{LpMapPicker-D5_YLLBC.js → LpMapPicker-DW2EnrCJ.js} +1 -1
- package/dist/chunks/{LpMediaGallery-BYEwySoF.js → LpMediaGallery-BNEYctDP.js} +3 -3
- package/dist/chunks/{LpModal-DDvrSAnf.js → LpModal-DdTqdf4x.js} +1 -1
- package/dist/chunks/{LpModalNav-DI3BW5ff.js → LpModalNav-BL7Dp0om.js} +3 -3
- package/dist/chunks/LpNotificationBell-d0F6MWxD.js +163 -0
- package/dist/chunks/LpNotificationFeed-BIe5lapb.js +470 -0
- package/dist/chunks/{LpNumberField-C8LfUz_R.js → LpNumberField-D5Dx-hzD.js} +3 -3
- package/dist/chunks/{LpOtpInput-B1X9l9no.js → LpOtpInput-BVl74Lfl.js} +1 -1
- package/dist/chunks/{LpPagination-snQ020xQ.js → LpPagination-D0wb-Uru.js} +1 -1
- package/dist/chunks/{LpPaletteTheme-B-xuZiKK.js → LpPaletteTheme-DdilCBXA.js} +2 -2
- package/dist/chunks/{LpPasswordInput-Duc_3wPv.js → LpPasswordInput-DUtqWMkA.js} +2 -2
- package/dist/chunks/{LpPhoneInput-CctcRK9K.js → LpPhoneInput-CalRjkqc.js} +3 -3
- package/dist/chunks/{LpPie-oVNHjsKD.js → LpPie-BVAEmVBT.js} +2 -2
- package/dist/chunks/{LpPipeline-BSXT6oUJ.js → LpPipeline-Dk7_sRP1.js} +2 -2
- package/dist/chunks/{LpQuota-CgWPgdVt.js → LpQuota-DKx0IZYA.js} +1 -1
- package/dist/chunks/{LpRadioGroup-DTM0nd9G.js → LpRadioGroup-E3SP6j9h.js} +2 -2
- package/dist/chunks/{LpRangeCalendar-BrqlGZzE.js → LpRangeCalendar-BFJFaen4.js} +1 -1
- package/dist/chunks/{LpRangeSlider-DJsEZXad.js → LpRangeSlider-b6ipywRB.js} +1 -1
- package/dist/chunks/{LpRepeater-Czib3rmv.js → LpRepeater-BLrw62Bh.js} +2 -2
- package/dist/chunks/{LpSecretField-BtlCk3IH.js → LpSecretField-BV8BNjKc.js} +5 -5
- package/dist/chunks/{LpSection-BfteipxU.js → LpSection-CghNNxIv.js} +1 -1
- package/dist/chunks/{LpSegmented-sskrJKJJ.js → LpSegmented-DjvrHL_G.js} +2 -2
- package/dist/chunks/{LpSelect-JFZehOkd.js → LpSelect-DYPimzAZ.js} +4 -4
- package/dist/chunks/{LpSidebar-CvRvVBTt.js → LpSidebar-D3S8rYFf.js} +4 -4
- package/dist/chunks/{LpSidebarNav-CE0jQS8i.js → LpSidebarNav-Dlse8vxT.js} +3 -3
- package/dist/chunks/{LpSidebarRow-DOTg1txp.js → LpSidebarRow-Cqd_6tGG.js} +3 -3
- package/dist/chunks/{LpSlider-C4ilCtZX.js → LpSlider-B9XIC75O.js} +1 -1
- package/dist/chunks/{LpStat-DPjc6_fX.js → LpStat-kyLRmX-m.js} +2 -2
- package/dist/chunks/{LpStatus-DGJFwk5F.js → LpStatus-szB2dWzM.js} +1 -1
- package/dist/chunks/{LpStepMark-Dw2MRTrt.js → LpStepMark-DP3CDSm9.js} +1 -1
- package/dist/chunks/{LpStepper-DNnh7lCu.js → LpStepper-BS3iaJIz.js} +1 -1
- package/dist/chunks/{LpSwitch-DyAYTchz.js → LpSwitch-DRFy8RNr.js} +1 -1
- package/dist/chunks/{LpTabFace-DQoV8mQG.js → LpTabFace-C9-OperL.js} +2 -2
- package/dist/chunks/{LpTableFilter-CJg5eIcV.js → LpTableFilter-CEIwLupk.js} +2 -2
- package/dist/chunks/{LpTagsInput-CkNxpNYa.js → LpTagsInput-ClDeF_aU.js} +5 -5
- package/dist/chunks/{LpTextarea-CFK_5QB8.js → LpTextarea-DEY6pUBy.js} +1 -1
- package/dist/chunks/{LpThemeStudio-R-wohbCY.js → LpThemeStudio-WY72Z1Db.js} +5 -5
- package/dist/chunks/{LpThemeSwitcher-Bzy15MCP.js → LpThemeSwitcher-9uCH9c-K.js} +3 -3
- package/dist/chunks/{LpTimeField-B5n0m8m0.js → LpTimeField-BmgxCzQO.js} +2 -2
- package/dist/chunks/{LpToaster-Ct_ZOhCh.js → LpToaster-DLKO-efn.js} +2 -2
- package/dist/chunks/{LpTopologyCanvas-BNgw9RyZ.js → LpTopologyCanvas-B-PhPa7k.js} +4 -4
- package/dist/chunks/{LpTour-ClKYhx4h.js → LpTour-ODlBcat2.js} +1 -1
- package/dist/chunks/{LpTruncate-ellq9RYM.js → LpTruncate--nhT0iUo.js} +1 -1
- package/dist/chunks/{LpWindow-B8YSRVTH.js → LpWindow-DJxE8Zl-.js} +2 -2
- package/dist/chunks/{LpWorkspaceSwitcher-ugv-fyig.js → LpWorkspaceSwitcher-Bpf41Bw5.js} +3 -3
- package/dist/chunks/{MenuTitle-Bh5dAdV1.js → MenuTitle-CMjvLsvC.js} +1 -1
- package/dist/chunks/{OptionContent-CGb8QlHA.js → OptionContent-DY8NaG8s.js} +1 -1
- package/dist/chunks/{PickerField-BM1dyafh.js → PickerField-CgfkaBgg.js} +1 -1
- package/dist/chunks/{RevealToggle-D_iyrB8d.js → RevealToggle-CPMXxaKv.js} +1 -1
- package/dist/chunks/{TourCard-DAzTYfav.js → TourCard-D4b3lMHi.js} +1 -1
- package/dist/chunks/icons-C1vppR4f.js +59 -0
- 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/FieldLeading.vue.js +1 -1
- package/dist/components/FieldWrapper.vue.js +1 -1
- package/dist/components/IconCloseButton.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.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.js +1 -1
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpCalendar.vue.js +1 -1
- package/dist/components/LpChart.vue.js +2 -2
- 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.js +1 -1
- package/dist/components/LpCommandPalette.vue.js +2 -2
- 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/LpContextSwitcher.vue.js +1 -1
- package/dist/components/LpCopyButton.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.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/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 +1 -1
- package/dist/components/LpInfraNode.vue.js +1 -1
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpInspector.vue.js +1 -1
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLogInspector.vue.js +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.d.ts +28 -64
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNotificationFeed.vue.d.ts +78 -0
- package/dist/components/LpNotificationFeed.vue.js +4 -0
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpOtpInput.vue.js +1 -1
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPaletteFacts.vue.js +1 -1
- package/dist/components/LpPaletteHeader.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/LpQuota.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpRangeCalendar.vue.js +1 -1
- package/dist/components/LpRangeSlider.vue.js +1 -1
- package/dist/components/LpRepeater.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/LpShortcutsDialog.vue.js +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSidebarRow.vue.js +1 -1
- package/dist/components/LpSlider.vue.js +1 -1
- 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/LpSwitch.vue.js +1 -1
- package/dist/components/LpTabFace.vue.js +1 -1
- package/dist/components/LpTable.vue.js +9 -9
- package/dist/components/LpTableFilter.vue.js +1 -1
- package/dist/components/LpTabs.vue.js +2 -2
- package/dist/components/LpTagsInput.vue.js +1 -1
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeStudio.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/LpWindow.vue.js +1 -1
- package/dist/components/LpWorkspaceSwitcher.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/notifications.d.ts +156 -0
- package/dist/components/notifications.js +59 -0
- package/dist/icons-all.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +208 -206
- package/package.json +1 -1
- package/src/component-names.ts +1 -0
- package/src/components/LpNotificationBell.vue +105 -279
- package/src/components/LpNotificationFeed.vue +404 -0
- package/src/components/notifications.ts +197 -0
- package/src/index.ts +6 -1
- package/src/kit-icons.ts +1 -1
- package/dist/chunks/LpNotificationBell-DUbVyK6R.js +0 -311
- package/dist/chunks/icons-CYfcpSDS.js +0 -59
|
@@ -1,84 +1,41 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
/*
|
|
3
|
-
* Notification bell: an icon button with an unread badge that opens
|
|
4
|
-
* feed
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
3
|
+
* Notification bell: an icon button with an unread badge that opens the
|
|
4
|
+
* notification feed (LpNotificationFeed) in a popover on left-click and a
|
|
5
|
+
* quick-action menu on right-click. Data in, events out, no fetching inside:
|
|
6
|
+
* the consumer binds `items` / `unread-count` / `v-model:open` and reacts to
|
|
7
|
+
* the feed's events, which the bell passes on.
|
|
8
|
+
*
|
|
9
|
+
* Everything the feed takes, the bell takes too: rows with their own buttons
|
|
10
|
+
* (`item.actions`), senders (`item.actor`), tabs (`categories`, `v-model:filter`),
|
|
11
|
+
* Load more, a link to the whole feed, a settings button.
|
|
10
12
|
*/
|
|
11
13
|
import { computed, getCurrentInstance } from "vue"
|
|
12
14
|
import { useLabels } from "../composables/useLpConfig"
|
|
13
|
-
import { useNow } from "../composables/useNow"
|
|
14
15
|
import LpBadge from "./LpBadge.vue"
|
|
15
16
|
import LpButton from "./LpButton.vue"
|
|
16
17
|
import LpContextMenu, { type ContextMenuItemDef } from "./LpContextMenu.vue"
|
|
17
|
-
import LpEmptyState from "./LpEmptyState.vue"
|
|
18
18
|
import LpIcon from "./LpIcon.vue"
|
|
19
|
+
import LpNotificationFeed from "./LpNotificationFeed.vue"
|
|
19
20
|
import LpPopover from "./LpPopover.vue"
|
|
20
|
-
import LpRelativeTime from "./LpRelativeTime.vue"
|
|
21
21
|
import LpRollingText from "./LpRollingText.vue"
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
* the one place a theme cannot follow them.
|
|
40
|
-
*/
|
|
41
|
-
tone?: ToneName
|
|
42
|
-
/** Optional deep link; the row becomes clickable when set. */
|
|
43
|
-
link?: string
|
|
44
|
-
/** ISO-8601 creation time; rendered as a relative label. */
|
|
45
|
-
createdAt?: string
|
|
46
|
-
/** Whether the notification has been read. */
|
|
47
|
-
read?: boolean
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Every user-facing string the bell renders on its own. Apps with i18n pass
|
|
52
|
-
* their translations in; the English defaults keep the component usable bare.
|
|
53
|
-
* Without this the header/row menus would be hardcoded English in every locale.
|
|
54
|
-
*/
|
|
55
|
-
export interface NotificationLabels {
|
|
56
|
-
title?: string
|
|
57
|
-
markAllRead?: string
|
|
58
|
-
clear?: string
|
|
59
|
-
markAsRead?: string
|
|
60
|
-
markAsUnread?: string
|
|
61
|
-
open?: string
|
|
62
|
-
dismiss?: string
|
|
63
|
-
empty?: string
|
|
64
|
-
ariaLabel?: string
|
|
65
|
-
/** Appended to the button's name while there is unread news; `{count}` is filled in. */
|
|
66
|
-
unread?: string
|
|
67
|
-
/** Screen-reader marker on an unread row. */
|
|
68
|
-
unreadItem?: string
|
|
69
|
-
/**
|
|
70
|
-
* No longer read: times are worded by Intl in the app's locale. Still
|
|
71
|
-
* accepted so existing label objects keep type-checking.
|
|
72
|
-
* @deprecated
|
|
73
|
-
*/
|
|
74
|
-
justNow?: string
|
|
75
|
-
/** @deprecated See `justNow`. */
|
|
76
|
-
minutes?: string
|
|
77
|
-
/** @deprecated See `justNow`. */
|
|
78
|
-
hours?: string
|
|
79
|
-
/** @deprecated See `justNow`. */
|
|
80
|
-
days?: string
|
|
81
|
-
}
|
|
22
|
+
import {
|
|
23
|
+
NOTIFICATION_LABELS,
|
|
24
|
+
type NotificationAction,
|
|
25
|
+
type NotificationCategory,
|
|
26
|
+
type NotificationItem,
|
|
27
|
+
type NotificationLabels,
|
|
28
|
+
type NotificationMenu,
|
|
29
|
+
} from "./notifications"
|
|
30
|
+
|
|
31
|
+
export type {
|
|
32
|
+
NotificationAction,
|
|
33
|
+
NotificationActor,
|
|
34
|
+
NotificationCategory,
|
|
35
|
+
NotificationItem,
|
|
36
|
+
NotificationLabels,
|
|
37
|
+
NotificationMenu,
|
|
38
|
+
} from "./notifications"
|
|
82
39
|
|
|
83
40
|
const props = withDefaults(
|
|
84
41
|
defineProps<{
|
|
@@ -90,166 +47,101 @@ const props = withDefaults(
|
|
|
90
47
|
title?: string
|
|
91
48
|
/** Cap the badge display (e.g. "9+"). */
|
|
92
49
|
maxBadge?: number
|
|
93
|
-
/** Right-click menu
|
|
50
|
+
/** Right-click menu on the bell; overrides the built-in quick actions. */
|
|
94
51
|
menuItems?: ContextMenuItemDef[]
|
|
95
|
-
/**
|
|
52
|
+
/** Extra right-click entries for a feed row. */
|
|
53
|
+
itemMenu?: NotificationMenu
|
|
54
|
+
/** Tabs after All and Unread, matched against `item.category`. */
|
|
55
|
+
categories?: NotificationCategory[]
|
|
56
|
+
/** Head runs of rows with Today / Yesterday / Earlier. Default true. */
|
|
57
|
+
groupByDay?: boolean
|
|
58
|
+
/** More is waiting: the feed offers Load more (`@load-more`). */
|
|
59
|
+
hasMore?: boolean
|
|
60
|
+
loadingMore?: boolean
|
|
61
|
+
/** The feed's footer link to the whole list ("View all notifications"). */
|
|
62
|
+
allLink?: string
|
|
63
|
+
/** Translations for the strings the bell and its feed render themselves. */
|
|
96
64
|
labels?: NotificationLabels
|
|
97
|
-
/**
|
|
98
|
-
* Formats `item.createdAt` for display. Defaults to a narrow relative time
|
|
99
|
-
* in the app's locale ("now", "5m ago", "3h ago"); pass your own to word
|
|
100
|
-
* it differently.
|
|
101
|
-
*/
|
|
65
|
+
/** Formats `item.createdAt`; defaults to a narrow relative time in the app's locale. */
|
|
102
66
|
formatTime?: (iso: string) => string
|
|
103
67
|
}>(),
|
|
104
|
-
{ items: () => [], maxBadge: 9 },
|
|
68
|
+
{ items: () => [], maxBadge: 9, groupByDay: true },
|
|
105
69
|
)
|
|
106
70
|
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
const text = useLabels(
|
|
110
|
-
"notifications",
|
|
111
|
-
{
|
|
112
|
-
title: "Notifications",
|
|
113
|
-
markAllRead: "Mark all read",
|
|
114
|
-
clear: "Clear",
|
|
115
|
-
markAsRead: "Mark as read",
|
|
116
|
-
markAsUnread: "Mark as unread",
|
|
117
|
-
open: "Open",
|
|
118
|
-
dismiss: "Dismiss",
|
|
119
|
-
empty: "No notifications",
|
|
120
|
-
ariaLabel: "",
|
|
121
|
-
unread: "{count} unread",
|
|
122
|
-
unreadItem: "Unread",
|
|
123
|
-
},
|
|
124
|
-
() => props.labels,
|
|
125
|
-
)
|
|
126
|
-
const l = computed(() => ({
|
|
127
|
-
...text.value,
|
|
128
|
-
title: props.title ?? text.value.title,
|
|
129
|
-
empty: props.emptyLabel ?? text.value.empty,
|
|
130
|
-
ariaLabel: text.value.ariaLabel || text.value.title,
|
|
131
|
-
}))
|
|
132
|
-
|
|
133
|
-
// Popover open state. A defineModel (not a plain `open` prop) gives a local
|
|
134
|
-
// fallback, so the bell still opens when the consumer only listens to
|
|
135
|
-
// @update:open without binding :open — otherwise the popover is stuck in
|
|
136
|
-
// controlled-but-uncontrolled limbo and never opens.
|
|
71
|
+
// A defineModel (not a plain prop) gives a local fallback, so the bell still
|
|
72
|
+
// opens when the consumer only listens to @update:open without binding :open.
|
|
137
73
|
const open = defineModel<boolean>("open", { default: false })
|
|
74
|
+
const filter = defineModel<string>("filter", { default: "all" })
|
|
138
75
|
|
|
139
76
|
const emit = defineEmits<{
|
|
140
77
|
(e: "markRead", id: string): void
|
|
141
78
|
(e: "markUnread", id: string): void
|
|
142
79
|
(e: "markAllRead"): void
|
|
143
|
-
/** Empty the whole feed.
|
|
80
|
+
/** Empty the whole feed. Only offered when this is bound. */
|
|
144
81
|
(e: "clear"): void
|
|
145
82
|
(e: "dismiss", id: string): void
|
|
146
83
|
(e: "select", item: NotificationItem): void
|
|
84
|
+
(e: "action", item: NotificationItem, action: NotificationAction): void
|
|
85
|
+
/** The feed's gear. Only drawn when this is bound. */
|
|
86
|
+
(e: "settings"): void
|
|
87
|
+
(e: "loadMore"): void
|
|
147
88
|
}>()
|
|
148
89
|
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
90
|
+
const text = useLabels("notifications", NOTIFICATION_LABELS, () => props.labels as Partial<typeof NOTIFICATION_LABELS>)
|
|
91
|
+
const l = computed(() => {
|
|
92
|
+
const title = props.title ?? text.value.title
|
|
93
|
+
return { ...text.value, title, ariaLabel: text.value.ariaLabel || title }
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const unread = computed(() => props.unreadCount ?? props.items.filter((n) => !n.read).length)
|
|
155
97
|
const hasUnread = computed(() => unread.value > 0)
|
|
98
|
+
const badgeLabel = computed(() => (unread.value > props.maxBadge ? `${props.maxBadge}+` : String(unread.value)))
|
|
156
99
|
|
|
157
100
|
// The badge is drawn, not read: its count goes into the button's own name.
|
|
158
101
|
const buttonLabel = computed(() =>
|
|
159
102
|
hasUnread.value ? `${l.value.ariaLabel}, ${l.value.unread.replace("{count}", String(unread.value))}` : l.value.ariaLabel,
|
|
160
103
|
)
|
|
161
104
|
|
|
162
|
-
|
|
163
105
|
/*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
* NOT `useAttrs()`: a listener for a DECLARED emit is removed from `$attrs`, so
|
|
168
|
-
* an `@clear` handler never appears there. The raw vnode props are where it
|
|
169
|
-
* survives.
|
|
106
|
+
* The optional events the consumer handles. NOT `useAttrs()`: a listener for a
|
|
107
|
+
* DECLARED emit is removed from `$attrs`, so it is read off the raw vnode
|
|
108
|
+
* props. Only those are passed on, so the feed draws no button nobody answers.
|
|
170
109
|
*/
|
|
171
110
|
const instance = getCurrentInstance()
|
|
172
111
|
const handlesClear = computed(() => Boolean(instance?.vnode.props?.onClear))
|
|
112
|
+
const optional = computed(() => ({
|
|
113
|
+
...(handlesClear.value ? { clear: () => emit("clear") } : {}),
|
|
114
|
+
...(instance?.vnode.props?.onSettings
|
|
115
|
+
? {
|
|
116
|
+
settings: () => {
|
|
117
|
+
open.value = false
|
|
118
|
+
emit("settings")
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
: {}),
|
|
122
|
+
}))
|
|
173
123
|
|
|
174
|
-
// Right-click quick actions on the bell. "Open" is
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
// button to also host LpContextMenu without the two fighting over the trigger.
|
|
124
|
+
// Right-click quick actions on the bell. "Open" is left out: a left-click
|
|
125
|
+
// already opens the feed. The popover's trigger and the context menu's both
|
|
126
|
+
// pass their handlers down to the one button (they forward attributes), so
|
|
127
|
+
// the button toggles nothing itself: a click of its own toggled twice.
|
|
179
128
|
const contextMenu = computed<ContextMenuItemDef[]>(() => {
|
|
180
129
|
if (props.menuItems) return props.menuItems
|
|
181
130
|
const items: ContextMenuItemDef[] = [
|
|
182
|
-
{
|
|
183
|
-
label: l.value.markAllRead,
|
|
184
|
-
icon: "lucide:check-check",
|
|
185
|
-
disabled: !hasUnread.value,
|
|
186
|
-
onSelect: () => emit("markAllRead"),
|
|
187
|
-
},
|
|
131
|
+
{ label: l.value.markAllRead, icon: "lucide:check-check", disabled: !hasUnread.value, onSelect: () => emit("markAllRead") },
|
|
188
132
|
]
|
|
189
|
-
// Same rule as the header button: offered only when the caller can act on it.
|
|
190
133
|
if (handlesClear.value) {
|
|
191
|
-
items.push({
|
|
192
|
-
label: l.value.clear,
|
|
193
|
-
icon: "lucide:trash-2",
|
|
194
|
-
disabled: !props.items.length,
|
|
195
|
-
onSelect: () => emit("clear"),
|
|
196
|
-
})
|
|
134
|
+
items.push({ label: l.value.clear, icon: "lucide:trash-2", disabled: !props.items.length, onSelect: () => emit("clear") })
|
|
197
135
|
}
|
|
198
136
|
return items
|
|
199
137
|
})
|
|
200
138
|
|
|
201
|
-
// Picking an item goes somewhere (a page, a dialog
|
|
202
|
-
//
|
|
139
|
+
// Picking an item goes somewhere (a page, a dialog), so the feed steps out of
|
|
140
|
+
// the way: left open it would cover what the item just opened.
|
|
203
141
|
function onSelect(item: NotificationItem) {
|
|
204
|
-
if (!item.read) emit("markRead", item.id)
|
|
205
142
|
open.value = false
|
|
206
143
|
emit("select", item)
|
|
207
144
|
}
|
|
208
|
-
|
|
209
|
-
// Per-row right-click actions: toggle read state, open a deep link, dismiss.
|
|
210
|
-
// Producers wire the matching @mark-read / @mark-unread / @dismiss handlers; an
|
|
211
|
-
// action only appears when it makes sense for that row.
|
|
212
|
-
function rowMenu(item: NotificationItem): ContextMenuItemDef[] {
|
|
213
|
-
const actions: ContextMenuItemDef[] = []
|
|
214
|
-
if (item.read) {
|
|
215
|
-
actions.push({
|
|
216
|
-
label: l.value.markAsUnread,
|
|
217
|
-
icon: "lucide:dot",
|
|
218
|
-
onSelect: () => emit("markUnread", item.id),
|
|
219
|
-
})
|
|
220
|
-
} else {
|
|
221
|
-
actions.push({
|
|
222
|
-
label: l.value.markAsRead,
|
|
223
|
-
icon: "lucide:check",
|
|
224
|
-
onSelect: () => emit("markRead", item.id),
|
|
225
|
-
})
|
|
226
|
-
}
|
|
227
|
-
if (item.link) {
|
|
228
|
-
actions.push({
|
|
229
|
-
label: l.value.open,
|
|
230
|
-
icon: "lucide:external-link",
|
|
231
|
-
onSelect: () => onSelect(item),
|
|
232
|
-
})
|
|
233
|
-
}
|
|
234
|
-
actions.push({
|
|
235
|
-
label: l.value.dismiss,
|
|
236
|
-
icon: "lucide:x",
|
|
237
|
-
danger: true,
|
|
238
|
-
separatorBefore: true,
|
|
239
|
-
onSelect: () => emit("dismiss", item.id),
|
|
240
|
-
})
|
|
241
|
-
return actions
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// A `formatTime` prop takes over the row's time entirely; it is re-asked twice
|
|
245
|
-
// a minute so its "5m" keeps moving. Without one, the row renders
|
|
246
|
-
// LpRelativeTime, which words the time through Intl in the app's locale and
|
|
247
|
-
// wakes only when its own text would change.
|
|
248
|
-
const customNow = useNow(() => !!props.formatTime, { nextIn: () => 30_000 })
|
|
249
|
-
function customTime(iso: string): string {
|
|
250
|
-
void customNow.value
|
|
251
|
-
return props.formatTime ? props.formatTime(iso) : ""
|
|
252
|
-
}
|
|
253
145
|
</script>
|
|
254
146
|
|
|
255
147
|
<template>
|
|
@@ -258,12 +150,8 @@ function customTime(iso: string): string {
|
|
|
258
150
|
side="bottom"
|
|
259
151
|
align="end"
|
|
260
152
|
:side-offset="8"
|
|
261
|
-
panel-class="w-
|
|
153
|
+
panel-class="w-[min(24rem,calc(100vw-1rem))] p-0"
|
|
262
154
|
>
|
|
263
|
-
<!-- `open` is controlled via defineModel, so the popover trigger is just an
|
|
264
|
-
anchor: we toggle open on click ourselves. That frees the button to
|
|
265
|
-
also be wrapped in LpContextMenu for the right-click quick-actions menu
|
|
266
|
-
(left-click → feed, right-click → menu) without two triggers clashing. -->
|
|
267
155
|
<template #trigger>
|
|
268
156
|
<LpContextMenu :items="contextMenu">
|
|
269
157
|
<LpButton
|
|
@@ -271,7 +159,6 @@ function customTime(iso: string): string {
|
|
|
271
159
|
size="sm"
|
|
272
160
|
class="relative"
|
|
273
161
|
:aria-label="buttonLabel"
|
|
274
|
-
@click="open = !open"
|
|
275
162
|
>
|
|
276
163
|
<LpIcon name="lucide:bell" :size="18" />
|
|
277
164
|
<!-- The badge pops in and out, and its count rolls rather than
|
|
@@ -293,90 +180,29 @@ function customTime(iso: string): string {
|
|
|
293
180
|
</LpContextMenu>
|
|
294
181
|
</template>
|
|
295
182
|
|
|
296
|
-
<
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
</header>
|
|
321
|
-
|
|
322
|
-
<!-- Skeleton → empty → feed are three different heights, so the panel
|
|
323
|
-
jumped as the list loaded and again whenever it emptied. Easing the
|
|
324
|
-
swap keeps the popover from snapping under the bell. -->
|
|
325
|
-
<LpShift axis="height">
|
|
326
|
-
<div v-if="loading" class="space-y-1.5 px-2 pb-2">
|
|
327
|
-
<LpSkeleton v-for="i in 4" :key="i" class="h-12" />
|
|
328
|
-
</div>
|
|
329
|
-
|
|
330
|
-
<LpEmptyState
|
|
331
|
-
v-else-if="!items.length"
|
|
332
|
-
icon="lucide:bell-off"
|
|
333
|
-
:title="l.empty"
|
|
334
|
-
class="px-3 py-10"
|
|
335
|
-
/>
|
|
336
|
-
|
|
337
|
-
<!-- The feed sits in a thin gutter; rows are self-contained tiles (rounded,
|
|
338
|
-
hover-lit) instead of full-width strips with hard dividers — lighter and
|
|
339
|
-
easier to scan. Each row carries its own right-click action menu. -->
|
|
340
|
-
<LpScrollArea v-else class="max-h-96" content-class="flex flex-col gap-0.5 px-2 pb-2">
|
|
341
|
-
<LpContextMenu v-for="item in items" :key="item.id" :items="rowMenu(item)">
|
|
342
|
-
<component
|
|
343
|
-
:is="item.link ? 'a' : 'button'"
|
|
344
|
-
:href="item.link || undefined"
|
|
345
|
-
:type="item.link ? undefined : 'button'"
|
|
346
|
-
class="group/notif flex w-full items-start gap-2.5 rounded-control px-2 py-2 text-left outline-none transition-colors hover:bg-surface-soft focus-visible:bg-surface-soft"
|
|
347
|
-
:class="item.read ? '' : 'bg-brand-soft/35'"
|
|
348
|
-
@click="onSelect(item)"
|
|
349
|
-
>
|
|
350
|
-
<!-- A read notification is always muted: the tone says what kind of
|
|
351
|
-
thing it is, and that only needs to compete for attention while
|
|
352
|
-
it is still unread. -->
|
|
353
|
-
<span
|
|
354
|
-
class="grid size-7 shrink-0 place-items-center rounded-control"
|
|
355
|
-
:class="item.read ? 'bg-surface-soft text-muted' : toneLook(item.tone, 'brand').chip"
|
|
356
|
-
>
|
|
357
|
-
<LpIcon :name="item.icon || 'lucide:bell'" :size="15" />
|
|
358
|
-
</span>
|
|
359
|
-
<span class="min-w-0 flex-1">
|
|
360
|
-
<span class="flex items-center gap-1.5">
|
|
361
|
-
<span class="truncate text-sm font-medium" :class="item.read ? 'text-muted-strong' : 'text-ink'">
|
|
362
|
-
{{ item.title }}
|
|
363
|
-
</span>
|
|
364
|
-
<span v-if="!item.read" class="size-1.5 shrink-0 rounded-full bg-brand" />
|
|
365
|
-
<span v-if="!item.read" class="sr-only">{{ l.unreadItem }}</span>
|
|
366
|
-
</span>
|
|
367
|
-
<span v-if="item.body" class="mt-0.5 line-clamp-2 block text-xs text-muted">
|
|
368
|
-
{{ item.body }}
|
|
369
|
-
</span>
|
|
370
|
-
<span v-if="item.createdAt" class="mt-0.5 block text-[11px] text-muted/70">
|
|
371
|
-
<template v-if="formatTime">{{ customTime(item.createdAt) }}</template>
|
|
372
|
-
<!-- The row is itself a button or link, so the exact date goes
|
|
373
|
-
to the native title rather than a focusable tooltip. -->
|
|
374
|
-
<LpRelativeTime v-else :date="item.createdAt" format="narrow" :tooltip="false" />
|
|
375
|
-
</span>
|
|
376
|
-
</span>
|
|
377
|
-
</component>
|
|
378
|
-
</LpContextMenu>
|
|
379
|
-
</LpScrollArea>
|
|
380
|
-
</LpShift>
|
|
183
|
+
<LpNotificationFeed
|
|
184
|
+
v-model:filter="filter"
|
|
185
|
+
:items="items"
|
|
186
|
+
:unread-count="unreadCount"
|
|
187
|
+
:loading="loading"
|
|
188
|
+
:title="title"
|
|
189
|
+
:empty-label="emptyLabel"
|
|
190
|
+
:categories="categories"
|
|
191
|
+
:group-by-day="groupByDay"
|
|
192
|
+
:has-more="hasMore"
|
|
193
|
+
:loading-more="loadingMore"
|
|
194
|
+
:all-link="allLink"
|
|
195
|
+
:item-menu="itemMenu"
|
|
196
|
+
:labels="labels"
|
|
197
|
+
:format-time="formatTime"
|
|
198
|
+
v-on="optional"
|
|
199
|
+
@mark-read="emit('markRead', $event)"
|
|
200
|
+
@mark-unread="emit('markUnread', $event)"
|
|
201
|
+
@mark-all-read="emit('markAllRead')"
|
|
202
|
+
@dismiss="emit('dismiss', $event)"
|
|
203
|
+
@select="onSelect"
|
|
204
|
+
@action="(item, action) => emit('action', item, action)"
|
|
205
|
+
@load-more="emit('loadMore')"
|
|
206
|
+
/>
|
|
381
207
|
</LpPopover>
|
|
382
208
|
</template>
|