@hexdspace/react 0.0.7 → 0.0.9

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/index.mjs DELETED
@@ -1,290 +0,0 @@
1
- // src/util/responsive-query/ui-result.ts
2
- import { v4 as uuid } from "uuid";
3
- var ui = {
4
- success: (value, ...effects) => ({
5
- ok: true,
6
- value,
7
- effects
8
- }),
9
- failure: (error, ...extraEffects) => ({
10
- ok: false,
11
- error,
12
- effects: [
13
- {
14
- type: "notify",
15
- notification: {
16
- id: uuid(),
17
- title: "Error",
18
- content: error,
19
- variant: "error"
20
- }
21
- },
22
- ...extraEffects
23
- ]
24
- })
25
- };
26
-
27
- // src/util/responsive-query/use-responsive-mutation.ts
28
- import { useMutation, useQueryClient } from "@tanstack/react-query";
29
-
30
- // src/feature/notifier/entity/notification-bus.ts
31
- var NotificationBus = class {
32
- channels = /* @__PURE__ */ new Map();
33
- subscribe(channel, listener) {
34
- const listeners = this.ensureChannel(channel);
35
- listeners.add(listener);
36
- return () => {
37
- const set = this.channels.get(channel);
38
- if (!set) return;
39
- set.delete(listener);
40
- if (set.size === 0) {
41
- this.channels.delete(channel);
42
- }
43
- };
44
- }
45
- publish(channel, notification) {
46
- const listeners = this.channels.get(channel);
47
- if (!listeners || listeners.size === 0) return;
48
- for (const listener of listeners) {
49
- try {
50
- listener.notify(notification);
51
- } catch (error) {
52
- console.error("Notification listener failed to handle notification", error);
53
- }
54
- }
55
- }
56
- ensureChannel(channel) {
57
- let listeners = this.channels.get(channel);
58
- if (!listeners) {
59
- listeners = /* @__PURE__ */ new Set();
60
- this.channels.set(channel, listeners);
61
- }
62
- return listeners;
63
- }
64
- };
65
-
66
- // src/feature/notifier/application/use-case/send-notification-use-case.ts
67
- var SendNotificationUseCase = class {
68
- constructor(bus2) {
69
- this.bus = bus2;
70
- }
71
- async execute(channel, notification) {
72
- this.bus.publish(channel, notification);
73
- }
74
- };
75
-
76
- // src/feature/notifier/application/use-case/subscribe-use-case.ts
77
- var SubscribeUseCase = class {
78
- constructor(bus2) {
79
- this.bus = bus2;
80
- }
81
- async execute(channel, observer) {
82
- return this.bus.subscribe(channel, observer);
83
- }
84
- };
85
-
86
- // src/feature/notifier/infra/controllers/notifier-controller.ts
87
- var NotifierController = class {
88
- constructor(sender, subscriber) {
89
- this.sender = sender;
90
- this.subscriber = subscriber;
91
- }
92
- handleSendNotification(channel, notification) {
93
- return this.sender.execute(channel, notification);
94
- }
95
- handleSubscribe(channel, listener) {
96
- return this.subscriber.execute(channel, listener);
97
- }
98
- };
99
- var bus = new NotificationBus();
100
- var sendNotification = new SendNotificationUseCase(bus);
101
- var subscribe = new SubscribeUseCase(bus);
102
- var notifierController = new NotifierController(sendNotification, subscribe);
103
-
104
- // src/feature/notifier/infra/web/react/NotificationHost.tsx
105
- import { useEffect, useMemo as useMemo2 } from "react";
106
- import { ToastContainer, toast } from "react-toastify";
107
- import { AlertTriangleIcon, CheckCircleIcon, InfoIcon, XCircleIcon } from "lucide-react";
108
-
109
- // src/feature/notifier/entity/notification.ts
110
- var DEFAULT_NOTIFICATION_CHANNEL = "app.notifications";
111
-
112
- // src/feature/notifier/infra/web/react/NotifierProvider.tsx
113
- import { createContext, useContext, useMemo } from "react";
114
- import { jsx } from "react/jsx-runtime";
115
- var NotifierContext = createContext(null);
116
- var NotifierProvider = ({ children }) => {
117
- const controller = useMemo(() => notifierController, []);
118
- return /* @__PURE__ */ jsx(NotifierContext.Provider, { value: { controller }, children });
119
- };
120
- function useNotifierController() {
121
- const ctx = useContext(NotifierContext);
122
- if (!ctx) throw new Error("useNotifierController must be used within <NotifierProvider>");
123
- return ctx.controller;
124
- }
125
-
126
- // src/feature/notifier/infra/web/react/CustomToastTransition.tsx
127
- import { cssTransition } from "react-toastify";
128
- var SlideUp = cssTransition({
129
- enter: "slideIn",
130
- exit: "slideOut",
131
- collapseDuration: 300,
132
- appendPosition: false,
133
- collapse: true
134
- });
135
-
136
- // src/feature/notifier/infra/web/react/NotificationHost.tsx
137
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
138
- var NotificationHost = ({ channel = DEFAULT_NOTIFICATION_CHANNEL, isDark }) => {
139
- const controller = useNotifierController();
140
- useEffect(() => {
141
- let unsub;
142
- let disposed = false;
143
- const listener = {
144
- notify(notification) {
145
- renderToast(notification);
146
- }
147
- };
148
- controller.handleSubscribe(channel, listener).then((unsubHandler) => {
149
- if (disposed) {
150
- unsubHandler();
151
- return;
152
- }
153
- unsub = unsubHandler;
154
- }).catch((error) => {
155
- console.error("Failed to subscribe to notifications", error);
156
- });
157
- return () => {
158
- disposed = true;
159
- if (unsub) unsub();
160
- };
161
- }, [channel, controller]);
162
- const theme = useMemo2(() => {
163
- if (!isDark) {
164
- return "light";
165
- }
166
- return isDark() ? "dark" : "light";
167
- }, []);
168
- return /* @__PURE__ */ jsx2(
169
- ToastContainer,
170
- {
171
- position: "bottom-right",
172
- transition: SlideUp,
173
- newestOnTop: true,
174
- draggable: true,
175
- pauseOnFocusLoss: false,
176
- icon: false,
177
- pauseOnHover: true,
178
- hideProgressBar: false,
179
- className: "custom-toast-container",
180
- theme
181
- }
182
- );
183
- };
184
- var DEFAULT_NOTIFICATION_VARIANT = "info";
185
- var TOAST_ICON_BY_VARIANT = {
186
- success: { Icon: CheckCircleIcon, color: "var(--success)" },
187
- warning: { Icon: AlertTriangleIcon, color: "var(--warning)" },
188
- error: { Icon: XCircleIcon, color: "var(--danger)" },
189
- info: { Icon: InfoIcon, color: "var(--accent)" }
190
- };
191
- function renderToast(notification) {
192
- const variant = notification.variant ?? DEFAULT_NOTIFICATION_VARIANT;
193
- toast(/* @__PURE__ */ jsx2(ToastContent, { notification }), {
194
- type: variant,
195
- toastId: notification.id,
196
- icon: false
197
- });
198
- }
199
- var ToastContent = ({ notification }) => {
200
- const { title, content, action } = notification;
201
- const variant = notification.variant ?? DEFAULT_NOTIFICATION_VARIANT;
202
- const { Icon, color } = TOAST_ICON_BY_VARIANT[variant];
203
- const handleActionClick = async () => {
204
- if (!action) return;
205
- try {
206
- await action.onClick?.();
207
- } catch (error) {
208
- console.error("Notification action failed", error);
209
- } finally {
210
- toast.dismiss(notification.id);
211
- }
212
- };
213
- return /* @__PURE__ */ jsxs("div", { className: "toast-content flex w-full flex-col gap-2", children: [
214
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
215
- /* @__PURE__ */ jsx2(
216
- Icon,
217
- {
218
- className: "h-4 w-4 shrink-0",
219
- style: { color },
220
- "aria-hidden": "true"
221
- }
222
- ),
223
- /* @__PURE__ */ jsx2("span", { className: "font-semibold", children: title })
224
- ] }),
225
- (content || action) && /* @__PURE__ */ jsxs("div", { className: "flex w-full items-start gap-3", children: [
226
- content && /* @__PURE__ */ jsx2("span", { className: "flex-1 text-sm text-muted-foreground", children: content }),
227
- action && /* @__PURE__ */ jsx2(
228
- "button",
229
- {
230
- type: "button",
231
- className: "toast-action text-sm font-medium ml-auto shrink-0 self-end",
232
- onClick: handleActionClick,
233
- children: action.label
234
- }
235
- )
236
- ] })
237
- ] });
238
- };
239
-
240
- // src/util/responsive-query/use-responsive-mutation.ts
241
- function useResponsiveMutation(responsiveMutation) {
242
- const { mutationFn, optimistic, onSuccess } = responsiveMutation;
243
- const qc = useQueryClient();
244
- return useMutation({
245
- mutationFn: async (args) => {
246
- const res = await mutationFn(args);
247
- if (!res.ok) throw res;
248
- return res;
249
- },
250
- onMutate: async (args) => {
251
- if (!optimistic) return void 0;
252
- const snapshot = await optimistic(args, { queryClient: qc });
253
- return snapshot ?? void 0;
254
- },
255
- onError: (err, _args, ctx) => {
256
- if (ctx) void ctx.rollback();
257
- executeEffects(err.effects, { queryClient: qc });
258
- },
259
- onSuccess: (res) => {
260
- executeEffects(res.effects, { queryClient: qc });
261
- onSuccess?.(res.value);
262
- }
263
- });
264
- }
265
- var executeEffects = (effects, ctx) => {
266
- effects.forEach((effect) => {
267
- if (effect.type === "invalidate") {
268
- void ctx.queryClient.invalidateQueries({ queryKey: effect.queryKey });
269
- } else if (effect.type === "update") {
270
- ctx.queryClient.setQueryData(effect.queryKey, effect.updater);
271
- } else if (effect.type === "notify") {
272
- const notification = typeof effect.notification === "function" ? effect.notification(ctx) : effect.notification;
273
- void notifierController.handleSendNotification(DEFAULT_NOTIFICATION_CHANNEL, {
274
- ...notification
275
- });
276
- } else if (effect.type === "custom") {
277
- void effect.run(ctx);
278
- }
279
- });
280
- };
281
- export {
282
- DEFAULT_NOTIFICATION_CHANNEL,
283
- NotificationHost,
284
- NotifierController,
285
- NotifierProvider,
286
- notifierController,
287
- ui,
288
- useNotifierController,
289
- useResponsiveMutation
290
- };