@cyguin/notify 0.1.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.
Files changed (48) hide show
  1. package/.github/workflows/publish.yml +27 -0
  2. package/LICENSE +7 -0
  3. package/README.md +152 -0
  4. package/dist/adapters/postgres.cjs +7 -0
  5. package/dist/adapters/postgres.d.cts +5 -0
  6. package/dist/adapters/postgres.d.ts +5 -0
  7. package/dist/adapters/postgres.js +7 -0
  8. package/dist/adapters/sqlite.cjs +9 -0
  9. package/dist/adapters/sqlite.d.cts +6 -0
  10. package/dist/adapters/sqlite.d.ts +6 -0
  11. package/dist/adapters/sqlite.js +9 -0
  12. package/dist/chunk-4SP667TN.js +33 -0
  13. package/dist/chunk-DBFBHOZI.cjs +82 -0
  14. package/dist/chunk-HW23IB3R.cjs +59 -0
  15. package/dist/chunk-N3OMUVHL.cjs +33 -0
  16. package/dist/chunk-NJEVZBJ7.cjs +67 -0
  17. package/dist/chunk-QHPFQN2C.js +82 -0
  18. package/dist/chunk-VKPJWS2D.js +59 -0
  19. package/dist/chunk-WTNWBHMC.js +255 -0
  20. package/dist/chunk-WZ4RNT3A.js +67 -0
  21. package/dist/chunk-YWA2XDPM.cjs +255 -0
  22. package/dist/index.cjs +32 -0
  23. package/dist/index.d.cts +16 -0
  24. package/dist/index.d.ts +16 -0
  25. package/dist/index.js +32 -0
  26. package/dist/next.cjs +6 -0
  27. package/dist/next.d.cts +25 -0
  28. package/dist/next.d.ts +25 -0
  29. package/dist/next.js +6 -0
  30. package/dist/react.cjs +6 -0
  31. package/dist/react.d.cts +21 -0
  32. package/dist/react.d.ts +21 -0
  33. package/dist/react.js +6 -0
  34. package/dist/types-Q62lBJZ-.d.cts +25 -0
  35. package/dist/types-Q62lBJZ-.d.ts +25 -0
  36. package/package.json +71 -0
  37. package/src/adapters/index.ts +2 -0
  38. package/src/adapters/postgres.ts +60 -0
  39. package/src/adapters/sqlite.ts +82 -0
  40. package/src/components/NotificationBell.tsx +292 -0
  41. package/src/components/index.ts +2 -0
  42. package/src/di.ts +19 -0
  43. package/src/handlers/route.ts +52 -0
  44. package/src/index.ts +8 -0
  45. package/src/notify.ts +7 -0
  46. package/src/types.ts +22 -0
  47. package/tsconfig.json +22 -0
  48. package/tsup.config.ts +16 -0
@@ -0,0 +1,255 @@
1
+ // src/components/NotificationBell.tsx
2
+ import { useEffect, useState, useRef, useCallback } from "react";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ function NotificationBell({
5
+ userId,
6
+ pollInterval = 3e4,
7
+ maxVisible = 10,
8
+ className = "",
9
+ onToggle
10
+ }) {
11
+ const [notifications, setNotifications] = useState([]);
12
+ const [unreadCount, setUnreadCount] = useState(0);
13
+ const [isOpen, setIsOpen] = useState(false);
14
+ const [loading, setLoading] = useState(true);
15
+ const dropdownRef = useRef(null);
16
+ const fetchNotifications = useCallback(async () => {
17
+ if (!userId) return;
18
+ try {
19
+ const res = await fetch(`/api/notify?userId=${encodeURIComponent(userId)}&limit=${maxVisible}`);
20
+ const data = await res.json();
21
+ setNotifications(data.notifications ?? []);
22
+ setUnreadCount(data.notifications?.filter((n) => !n.readAt).length ?? 0);
23
+ } catch (err) {
24
+ console.error("[NotificationBell] fetch failed:", err);
25
+ } finally {
26
+ setLoading(false);
27
+ }
28
+ }, [userId, maxVisible]);
29
+ useEffect(() => {
30
+ fetchNotifications();
31
+ if (pollInterval > 0) {
32
+ const id = setInterval(fetchNotifications, pollInterval);
33
+ return () => clearInterval(id);
34
+ }
35
+ }, [fetchNotifications, pollInterval]);
36
+ useEffect(() => {
37
+ function handleClickOutside(e) {
38
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
39
+ setIsOpen(false);
40
+ onToggle?.(false);
41
+ }
42
+ }
43
+ if (isOpen) {
44
+ document.addEventListener("mousedown", handleClickOutside);
45
+ return () => document.removeEventListener("mousedown", handleClickOutside);
46
+ }
47
+ }, [isOpen, onToggle]);
48
+ const handleNotificationClick = async (notification) => {
49
+ if (notification.href) {
50
+ window.location.href = notification.href;
51
+ }
52
+ if (!notification.readAt && userId) {
53
+ try {
54
+ await fetch(`/api/notify/${notification.id}/read?userId=${encodeURIComponent(userId)}`, {
55
+ method: "PATCH"
56
+ });
57
+ setNotifications(
58
+ (prev) => prev.map((n) => n.id === notification.id ? { ...n, readAt: Date.now() } : n)
59
+ );
60
+ setUnreadCount((prev) => Math.max(0, prev - 1));
61
+ } catch (err) {
62
+ console.error("[NotificationBell] mark read failed:", err);
63
+ }
64
+ }
65
+ setIsOpen(false);
66
+ onToggle?.(false);
67
+ };
68
+ const handleBellClick = () => {
69
+ setIsOpen((prev) => {
70
+ onToggle?.(!prev);
71
+ return !prev;
72
+ });
73
+ };
74
+ const badgeDisplay = unreadCount > 99 ? "99+" : unreadCount;
75
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
76
+ /* @__PURE__ */ jsx("style", { children: `
77
+ @keyframes cyguin-notify-spin {
78
+ to { transform: rotate(360deg); }
79
+ }
80
+ @keyframes cyguin-notify-fade-in {
81
+ from { opacity: 0; transform: translateY(-4px); }
82
+ to { opacity: 1; transform: translateY(0); }
83
+ }
84
+ .cyguin-notify-bell {
85
+ --cyguin-bg: #ffffff;
86
+ --cyguin-bg-subtle: #f5f5f5;
87
+ --cyguin-border: #e5e5e5;
88
+ --cyguin-border-focus: #f5a800;
89
+ --cyguin-fg: #0a0a0a;
90
+ --cyguin-fg-muted: #888888;
91
+ --cyguin-accent: #f5a800;
92
+ --cyguin-accent-dark: #c47f00;
93
+ --cyguin-accent-fg: #0a0a0a;
94
+ --cyguin-radius: 6px;
95
+ --cyguin-shadow: 0 1px 4px rgba(0,0,0,0.08);
96
+ position: relative;
97
+ display: inline-flex;
98
+ font-family: system-ui, -apple-system, sans-serif;
99
+ }
100
+ .cyguin-notify-bell[data-theme="dark"] {
101
+ --cyguin-bg: #0a0a0a;
102
+ --cyguin-bg-subtle: #1a1a1a;
103
+ --cyguin-border: #2a2a2a;
104
+ --cyguin-fg: #f5f5f5;
105
+ --cyguin-fg-muted: #888888;
106
+ --cyguin-shadow: 0 1px 4px rgba(0,0,0,0.4);
107
+ }
108
+ .cyguin-notify-bell-btn {
109
+ background: transparent;
110
+ border: 1px solid var(--cyguin-border);
111
+ border-radius: var(--cyguin-radius);
112
+ padding: 8px 10px;
113
+ cursor: pointer;
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ color: var(--cyguin-fg);
118
+ transition: border-color 0.15s, background-color 0.15s;
119
+ position: relative;
120
+ }
121
+ .cyguin-notify-bell-btn:hover {
122
+ border-color: var(--cyguin-border-focus);
123
+ background-color: var(--cyguin-bg-subtle);
124
+ }
125
+ .cyguin-notify-badge {
126
+ position: absolute;
127
+ top: -4px;
128
+ right: -4px;
129
+ background-color: var(--cyguin-accent);
130
+ color: var(--cyguin-accent-fg);
131
+ font-size: 10px;
132
+ font-weight: 700;
133
+ min-width: 16px;
134
+ height: 16px;
135
+ border-radius: 8px;
136
+ display: flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ padding: 0 4px;
140
+ box-sizing: border-box;
141
+ }
142
+ .cyguin-notify-dropdown {
143
+ position: absolute;
144
+ top: calc(100% + 6px);
145
+ right: 0;
146
+ width: 320px;
147
+ max-height: 420px;
148
+ overflow-y: auto;
149
+ background-color: var(--cyguin-bg);
150
+ border: 1px solid var(--cyguin-border);
151
+ border-radius: var(--cyguin-radius);
152
+ box-shadow: var(--cyguin-shadow);
153
+ animation: cyguin-notify-fade-in 0.15s ease-out;
154
+ z-index: 1000;
155
+ }
156
+ .cyguin-notify-dropdown-header {
157
+ padding: 10px 14px;
158
+ border-bottom: 1px solid var(--cyguin-border);
159
+ font-size: 13px;
160
+ font-weight: 600;
161
+ color: var(--cyguin-fg);
162
+ }
163
+ .cyguin-notify-item {
164
+ display: flex;
165
+ flex-direction: column;
166
+ gap: 2px;
167
+ padding: 10px 14px;
168
+ border-bottom: 1px solid var(--cyguin-border);
169
+ cursor: pointer;
170
+ transition: background-color 0.1s;
171
+ }
172
+ .cyguin-notify-item:last-child {
173
+ border-bottom: none;
174
+ }
175
+ .cyguin-notify-item:hover {
176
+ background-color: var(--cyguin-bg-subtle);
177
+ }
178
+ .cyguin-notify-item.unread {
179
+ background-color: color-mix(in srgb, var(--cyguin-accent) 8%, transparent);
180
+ }
181
+ .cyguin-notify-item.unread:hover {
182
+ background-color: color-mix(in srgb, var(--cyguin-accent) 12%, transparent);
183
+ }
184
+ .cyguin-notify-item-title {
185
+ font-size: 13px;
186
+ font-weight: 500;
187
+ color: var(--cyguin-fg);
188
+ }
189
+ .cyguin-notify-item-body {
190
+ font-size: 12px;
191
+ color: var(--cyguin-fg-muted);
192
+ line-height: 1.4;
193
+ }
194
+ .cyguin-notify-empty {
195
+ padding: 24px 14px;
196
+ text-align: center;
197
+ font-size: 13px;
198
+ color: var(--cyguin-fg-muted);
199
+ }
200
+ .cyguin-notify-spinner {
201
+ width: 16px;
202
+ height: 16px;
203
+ border: 2px solid var(--cyguin-border);
204
+ border-top-color: var(--cyguin-accent);
205
+ border-radius: 50%;
206
+ animation: cyguin-notify-spin 0.6s linear infinite;
207
+ }
208
+ .cyguin-notify-loading {
209
+ display: flex;
210
+ align-items: center;
211
+ justify-content: center;
212
+ padding: 24px;
213
+ }
214
+ ` }),
215
+ /* @__PURE__ */ jsxs("div", { className: `cyguin-notify-bell ${className}`, "data-theme": "light", ref: dropdownRef, children: [
216
+ /* @__PURE__ */ jsxs(
217
+ "button",
218
+ {
219
+ className: "cyguin-notify-bell-btn",
220
+ onClick: handleBellClick,
221
+ "aria-label": `Notifications${unreadCount > 0 ? ` (${unreadCount} unread)` : ""}`,
222
+ "aria-expanded": isOpen,
223
+ "aria-haspopup": "true",
224
+ children: [
225
+ /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
226
+ /* @__PURE__ */ jsx("path", { d: "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" }),
227
+ /* @__PURE__ */ jsx("path", { d: "M13.73 21a2 2 0 0 1-3.46 0" })
228
+ ] }),
229
+ unreadCount > 0 && /* @__PURE__ */ jsx("span", { className: "cyguin-notify-badge", children: badgeDisplay })
230
+ ]
231
+ }
232
+ ),
233
+ isOpen && /* @__PURE__ */ jsx("div", { className: "cyguin-notify-dropdown", role: "menu", children: loading ? /* @__PURE__ */ jsx("div", { className: "cyguin-notify-loading", children: /* @__PURE__ */ jsx("div", { className: "cyguin-notify-spinner" }) }) : notifications.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cyguin-notify-empty", children: "No notifications" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
234
+ /* @__PURE__ */ jsx("div", { className: "cyguin-notify-dropdown-header", children: "Notifications" }),
235
+ notifications.map((n) => /* @__PURE__ */ jsxs(
236
+ "div",
237
+ {
238
+ className: `cyguin-notify-item${!n.readAt ? " unread" : ""}`,
239
+ onClick: () => handleNotificationClick(n),
240
+ role: "menuitem",
241
+ children: [
242
+ /* @__PURE__ */ jsx("span", { className: "cyguin-notify-item-title", children: n.title }),
243
+ /* @__PURE__ */ jsx("span", { className: "cyguin-notify-item-body", children: n.body })
244
+ ]
245
+ },
246
+ n.id
247
+ ))
248
+ ] }) })
249
+ ] })
250
+ ] });
251
+ }
252
+
253
+ export {
254
+ NotificationBell
255
+ };
@@ -0,0 +1,67 @@
1
+ // src/handlers/route.ts
2
+ import { NextResponse } from "next/server";
3
+
4
+ // src/di.ts
5
+ var NOTIFICATION_ADAPTER = /* @__PURE__ */ Symbol.for("@cyguin/notify/NotificationAdapter");
6
+ var _adapter = null;
7
+ function setNotificationAdapter(adapter) {
8
+ _adapter = adapter;
9
+ }
10
+ function getNotificationAdapter() {
11
+ if (!_adapter) {
12
+ throw new Error(
13
+ "[@cyguin/notify] Notification adapter not set. Call setNotificationAdapter() before using notify()."
14
+ );
15
+ }
16
+ return _adapter;
17
+ }
18
+
19
+ // src/handlers/route.ts
20
+ function createNotifyHandler(_options) {
21
+ const adapter = getNotificationAdapter();
22
+ return {
23
+ async GET(request) {
24
+ const url = new URL(request.url);
25
+ const userId = url.searchParams.get("userId");
26
+ if (!userId) {
27
+ return NextResponse.json({ error: "userId is required" }, { status: 400 });
28
+ }
29
+ const limit = Number(url.searchParams.get("limit") ?? 20);
30
+ const offset = Number(url.searchParams.get("offset") ?? 0);
31
+ const notifications = await adapter.findManyByUser(userId, { limit, offset });
32
+ return NextResponse.json({ notifications });
33
+ },
34
+ async POST(request) {
35
+ let body;
36
+ try {
37
+ body = await request.json();
38
+ } catch {
39
+ return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
40
+ }
41
+ const { userId, title, body: notificationBody, href } = body;
42
+ if (!userId || !title || !notificationBody) {
43
+ return NextResponse.json({ error: "userId, title, and body are required" }, { status: 400 });
44
+ }
45
+ const options = { title, body: notificationBody, href };
46
+ const notification = await adapter.create({ userId, ...options });
47
+ return NextResponse.json({ notification }, { status: 201 });
48
+ },
49
+ async PATCH(request) {
50
+ const url = new URL(request.url);
51
+ const id = url.searchParams.get("id");
52
+ const userId = url.searchParams.get("userId");
53
+ if (!id || !userId) {
54
+ return NextResponse.json({ error: "id and userId are required" }, { status: 400 });
55
+ }
56
+ await adapter.markRead(id, userId);
57
+ return NextResponse.json({ ok: true });
58
+ }
59
+ };
60
+ }
61
+
62
+ export {
63
+ NOTIFICATION_ADAPTER,
64
+ setNotificationAdapter,
65
+ getNotificationAdapter,
66
+ createNotifyHandler
67
+ };
@@ -0,0 +1,255 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/components/NotificationBell.tsx
2
+ var _react = require('react');
3
+ var _jsxruntime = require('react/jsx-runtime');
4
+ function NotificationBell({
5
+ userId,
6
+ pollInterval = 3e4,
7
+ maxVisible = 10,
8
+ className = "",
9
+ onToggle
10
+ }) {
11
+ const [notifications, setNotifications] = _react.useState.call(void 0, []);
12
+ const [unreadCount, setUnreadCount] = _react.useState.call(void 0, 0);
13
+ const [isOpen, setIsOpen] = _react.useState.call(void 0, false);
14
+ const [loading, setLoading] = _react.useState.call(void 0, true);
15
+ const dropdownRef = _react.useRef.call(void 0, null);
16
+ const fetchNotifications = _react.useCallback.call(void 0, async () => {
17
+ if (!userId) return;
18
+ try {
19
+ const res = await fetch(`/api/notify?userId=${encodeURIComponent(userId)}&limit=${maxVisible}`);
20
+ const data = await res.json();
21
+ setNotifications(_nullishCoalesce(data.notifications, () => ( [])));
22
+ setUnreadCount(_nullishCoalesce(_optionalChain([data, 'access', _ => _.notifications, 'optionalAccess', _2 => _2.filter, 'call', _3 => _3((n) => !n.readAt), 'access', _4 => _4.length]), () => ( 0)));
23
+ } catch (err) {
24
+ console.error("[NotificationBell] fetch failed:", err);
25
+ } finally {
26
+ setLoading(false);
27
+ }
28
+ }, [userId, maxVisible]);
29
+ _react.useEffect.call(void 0, () => {
30
+ fetchNotifications();
31
+ if (pollInterval > 0) {
32
+ const id = setInterval(fetchNotifications, pollInterval);
33
+ return () => clearInterval(id);
34
+ }
35
+ }, [fetchNotifications, pollInterval]);
36
+ _react.useEffect.call(void 0, () => {
37
+ function handleClickOutside(e) {
38
+ if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
39
+ setIsOpen(false);
40
+ _optionalChain([onToggle, 'optionalCall', _5 => _5(false)]);
41
+ }
42
+ }
43
+ if (isOpen) {
44
+ document.addEventListener("mousedown", handleClickOutside);
45
+ return () => document.removeEventListener("mousedown", handleClickOutside);
46
+ }
47
+ }, [isOpen, onToggle]);
48
+ const handleNotificationClick = async (notification) => {
49
+ if (notification.href) {
50
+ window.location.href = notification.href;
51
+ }
52
+ if (!notification.readAt && userId) {
53
+ try {
54
+ await fetch(`/api/notify/${notification.id}/read?userId=${encodeURIComponent(userId)}`, {
55
+ method: "PATCH"
56
+ });
57
+ setNotifications(
58
+ (prev) => prev.map((n) => n.id === notification.id ? { ...n, readAt: Date.now() } : n)
59
+ );
60
+ setUnreadCount((prev) => Math.max(0, prev - 1));
61
+ } catch (err) {
62
+ console.error("[NotificationBell] mark read failed:", err);
63
+ }
64
+ }
65
+ setIsOpen(false);
66
+ _optionalChain([onToggle, 'optionalCall', _6 => _6(false)]);
67
+ };
68
+ const handleBellClick = () => {
69
+ setIsOpen((prev) => {
70
+ _optionalChain([onToggle, 'optionalCall', _7 => _7(!prev)]);
71
+ return !prev;
72
+ });
73
+ };
74
+ const badgeDisplay = unreadCount > 99 ? "99+" : unreadCount;
75
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
76
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "style", { children: `
77
+ @keyframes cyguin-notify-spin {
78
+ to { transform: rotate(360deg); }
79
+ }
80
+ @keyframes cyguin-notify-fade-in {
81
+ from { opacity: 0; transform: translateY(-4px); }
82
+ to { opacity: 1; transform: translateY(0); }
83
+ }
84
+ .cyguin-notify-bell {
85
+ --cyguin-bg: #ffffff;
86
+ --cyguin-bg-subtle: #f5f5f5;
87
+ --cyguin-border: #e5e5e5;
88
+ --cyguin-border-focus: #f5a800;
89
+ --cyguin-fg: #0a0a0a;
90
+ --cyguin-fg-muted: #888888;
91
+ --cyguin-accent: #f5a800;
92
+ --cyguin-accent-dark: #c47f00;
93
+ --cyguin-accent-fg: #0a0a0a;
94
+ --cyguin-radius: 6px;
95
+ --cyguin-shadow: 0 1px 4px rgba(0,0,0,0.08);
96
+ position: relative;
97
+ display: inline-flex;
98
+ font-family: system-ui, -apple-system, sans-serif;
99
+ }
100
+ .cyguin-notify-bell[data-theme="dark"] {
101
+ --cyguin-bg: #0a0a0a;
102
+ --cyguin-bg-subtle: #1a1a1a;
103
+ --cyguin-border: #2a2a2a;
104
+ --cyguin-fg: #f5f5f5;
105
+ --cyguin-fg-muted: #888888;
106
+ --cyguin-shadow: 0 1px 4px rgba(0,0,0,0.4);
107
+ }
108
+ .cyguin-notify-bell-btn {
109
+ background: transparent;
110
+ border: 1px solid var(--cyguin-border);
111
+ border-radius: var(--cyguin-radius);
112
+ padding: 8px 10px;
113
+ cursor: pointer;
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ color: var(--cyguin-fg);
118
+ transition: border-color 0.15s, background-color 0.15s;
119
+ position: relative;
120
+ }
121
+ .cyguin-notify-bell-btn:hover {
122
+ border-color: var(--cyguin-border-focus);
123
+ background-color: var(--cyguin-bg-subtle);
124
+ }
125
+ .cyguin-notify-badge {
126
+ position: absolute;
127
+ top: -4px;
128
+ right: -4px;
129
+ background-color: var(--cyguin-accent);
130
+ color: var(--cyguin-accent-fg);
131
+ font-size: 10px;
132
+ font-weight: 700;
133
+ min-width: 16px;
134
+ height: 16px;
135
+ border-radius: 8px;
136
+ display: flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ padding: 0 4px;
140
+ box-sizing: border-box;
141
+ }
142
+ .cyguin-notify-dropdown {
143
+ position: absolute;
144
+ top: calc(100% + 6px);
145
+ right: 0;
146
+ width: 320px;
147
+ max-height: 420px;
148
+ overflow-y: auto;
149
+ background-color: var(--cyguin-bg);
150
+ border: 1px solid var(--cyguin-border);
151
+ border-radius: var(--cyguin-radius);
152
+ box-shadow: var(--cyguin-shadow);
153
+ animation: cyguin-notify-fade-in 0.15s ease-out;
154
+ z-index: 1000;
155
+ }
156
+ .cyguin-notify-dropdown-header {
157
+ padding: 10px 14px;
158
+ border-bottom: 1px solid var(--cyguin-border);
159
+ font-size: 13px;
160
+ font-weight: 600;
161
+ color: var(--cyguin-fg);
162
+ }
163
+ .cyguin-notify-item {
164
+ display: flex;
165
+ flex-direction: column;
166
+ gap: 2px;
167
+ padding: 10px 14px;
168
+ border-bottom: 1px solid var(--cyguin-border);
169
+ cursor: pointer;
170
+ transition: background-color 0.1s;
171
+ }
172
+ .cyguin-notify-item:last-child {
173
+ border-bottom: none;
174
+ }
175
+ .cyguin-notify-item:hover {
176
+ background-color: var(--cyguin-bg-subtle);
177
+ }
178
+ .cyguin-notify-item.unread {
179
+ background-color: color-mix(in srgb, var(--cyguin-accent) 8%, transparent);
180
+ }
181
+ .cyguin-notify-item.unread:hover {
182
+ background-color: color-mix(in srgb, var(--cyguin-accent) 12%, transparent);
183
+ }
184
+ .cyguin-notify-item-title {
185
+ font-size: 13px;
186
+ font-weight: 500;
187
+ color: var(--cyguin-fg);
188
+ }
189
+ .cyguin-notify-item-body {
190
+ font-size: 12px;
191
+ color: var(--cyguin-fg-muted);
192
+ line-height: 1.4;
193
+ }
194
+ .cyguin-notify-empty {
195
+ padding: 24px 14px;
196
+ text-align: center;
197
+ font-size: 13px;
198
+ color: var(--cyguin-fg-muted);
199
+ }
200
+ .cyguin-notify-spinner {
201
+ width: 16px;
202
+ height: 16px;
203
+ border: 2px solid var(--cyguin-border);
204
+ border-top-color: var(--cyguin-accent);
205
+ border-radius: 50%;
206
+ animation: cyguin-notify-spin 0.6s linear infinite;
207
+ }
208
+ .cyguin-notify-loading {
209
+ display: flex;
210
+ align-items: center;
211
+ justify-content: center;
212
+ padding: 24px;
213
+ }
214
+ ` }),
215
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: `cyguin-notify-bell ${className}`, "data-theme": "light", ref: dropdownRef, children: [
216
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
217
+ "button",
218
+ {
219
+ className: "cyguin-notify-bell-btn",
220
+ onClick: handleBellClick,
221
+ "aria-label": `Notifications${unreadCount > 0 ? ` (${unreadCount} unread)` : ""}`,
222
+ "aria-expanded": isOpen,
223
+ "aria-haspopup": "true",
224
+ children: [
225
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
226
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" }),
227
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "path", { d: "M13.73 21a2 2 0 0 1-3.46 0" })
228
+ ] }),
229
+ unreadCount > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "cyguin-notify-badge", children: badgeDisplay })
230
+ ]
231
+ }
232
+ ),
233
+ isOpen && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "cyguin-notify-dropdown", role: "menu", children: loading ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "cyguin-notify-loading", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "cyguin-notify-spinner" }) }) : notifications.length === 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "cyguin-notify-empty", children: "No notifications" }) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
234
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "cyguin-notify-dropdown-header", children: "Notifications" }),
235
+ notifications.map((n) => /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
236
+ "div",
237
+ {
238
+ className: `cyguin-notify-item${!n.readAt ? " unread" : ""}`,
239
+ onClick: () => handleNotificationClick(n),
240
+ role: "menuitem",
241
+ children: [
242
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "cyguin-notify-item-title", children: n.title }),
243
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "cyguin-notify-item-body", children: n.body })
244
+ ]
245
+ },
246
+ n.id
247
+ ))
248
+ ] }) })
249
+ ] })
250
+ ] });
251
+ }
252
+
253
+
254
+
255
+ exports.NotificationBell = NotificationBell;
package/dist/index.cjs ADDED
@@ -0,0 +1,32 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+
4
+
5
+
6
+ var _chunkNJEVZBJ7cjs = require('./chunk-NJEVZBJ7.cjs');
7
+
8
+
9
+ var _chunkYWA2XDPMcjs = require('./chunk-YWA2XDPM.cjs');
10
+
11
+
12
+ var _chunkDBFBHOZIcjs = require('./chunk-DBFBHOZI.cjs');
13
+
14
+
15
+ var _chunkHW23IB3Rcjs = require('./chunk-HW23IB3R.cjs');
16
+ require('./chunk-N3OMUVHL.cjs');
17
+
18
+ // src/notify.ts
19
+ async function notify(userId, options) {
20
+ const adapter = _chunkNJEVZBJ7cjs.getNotificationAdapter.call(void 0, );
21
+ await adapter.create({ userId, ...options });
22
+ }
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+ exports.NOTIFICATION_ADAPTER = _chunkNJEVZBJ7cjs.NOTIFICATION_ADAPTER; exports.NotificationBell = _chunkYWA2XDPMcjs.NotificationBell; exports.createNotifyHandler = _chunkNJEVZBJ7cjs.createNotifyHandler; exports.createPostgresAdapter = _chunkHW23IB3Rcjs.createPostgresAdapter; exports.createSQLiteAdapter = _chunkDBFBHOZIcjs.createSQLiteAdapter; exports.getNotificationAdapter = _chunkNJEVZBJ7cjs.getNotificationAdapter; exports.notify = notify; exports.setNotificationAdapter = _chunkNJEVZBJ7cjs.setNotificationAdapter;
@@ -0,0 +1,16 @@
1
+ import { b as NotifyOptions, a as NotificationAdapter } from './types-Q62lBJZ-.cjs';
2
+ export { N as NotificationRecord } from './types-Q62lBJZ-.cjs';
3
+ export { createNotifyHandler } from './next.cjs';
4
+ export { createSQLiteAdapter } from './adapters/sqlite.cjs';
5
+ export { createPostgresAdapter } from './adapters/postgres.cjs';
6
+ export { Notification, NotificationBell, NotificationBellProps } from './react.cjs';
7
+ import 'next/server';
8
+ import 'react/jsx-runtime';
9
+
10
+ declare function notify(userId: string, options: NotifyOptions): Promise<void>;
11
+
12
+ declare const NOTIFICATION_ADAPTER: unique symbol;
13
+ declare function setNotificationAdapter(adapter: NotificationAdapter): void;
14
+ declare function getNotificationAdapter(): NotificationAdapter;
15
+
16
+ export { NOTIFICATION_ADAPTER, NotificationAdapter, NotifyOptions, getNotificationAdapter, notify, setNotificationAdapter };
@@ -0,0 +1,16 @@
1
+ import { b as NotifyOptions, a as NotificationAdapter } from './types-Q62lBJZ-.js';
2
+ export { N as NotificationRecord } from './types-Q62lBJZ-.js';
3
+ export { createNotifyHandler } from './next.js';
4
+ export { createSQLiteAdapter } from './adapters/sqlite.js';
5
+ export { createPostgresAdapter } from './adapters/postgres.js';
6
+ export { Notification, NotificationBell, NotificationBellProps } from './react.js';
7
+ import 'next/server';
8
+ import 'react/jsx-runtime';
9
+
10
+ declare function notify(userId: string, options: NotifyOptions): Promise<void>;
11
+
12
+ declare const NOTIFICATION_ADAPTER: unique symbol;
13
+ declare function setNotificationAdapter(adapter: NotificationAdapter): void;
14
+ declare function getNotificationAdapter(): NotificationAdapter;
15
+
16
+ export { NOTIFICATION_ADAPTER, NotificationAdapter, NotifyOptions, getNotificationAdapter, notify, setNotificationAdapter };
package/dist/index.js ADDED
@@ -0,0 +1,32 @@
1
+ import {
2
+ NOTIFICATION_ADAPTER,
3
+ createNotifyHandler,
4
+ getNotificationAdapter,
5
+ setNotificationAdapter
6
+ } from "./chunk-WZ4RNT3A.js";
7
+ import {
8
+ NotificationBell
9
+ } from "./chunk-WTNWBHMC.js";
10
+ import {
11
+ createSQLiteAdapter
12
+ } from "./chunk-QHPFQN2C.js";
13
+ import {
14
+ createPostgresAdapter
15
+ } from "./chunk-VKPJWS2D.js";
16
+ import "./chunk-4SP667TN.js";
17
+
18
+ // src/notify.ts
19
+ async function notify(userId, options) {
20
+ const adapter = getNotificationAdapter();
21
+ await adapter.create({ userId, ...options });
22
+ }
23
+ export {
24
+ NOTIFICATION_ADAPTER,
25
+ NotificationBell,
26
+ createNotifyHandler,
27
+ createPostgresAdapter,
28
+ createSQLiteAdapter,
29
+ getNotificationAdapter,
30
+ notify,
31
+ setNotificationAdapter
32
+ };
package/dist/next.cjs ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+ var _chunkNJEVZBJ7cjs = require('./chunk-NJEVZBJ7.cjs');
4
+
5
+
6
+ exports.createNotifyHandler = _chunkNJEVZBJ7cjs.createNotifyHandler;