beez-ui 0.8.2 → 0.9.1
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/CHANGELOG.md +29 -0
- package/README.md +12 -10
- package/dist/components/account-menu.d.ts +10 -0
- package/dist/components/account-menu.js +139 -118
- package/dist/components/file-upload.d.ts +14 -4
- package/dist/components/file-upload.js +304 -206
- package/dist/components/filter-presets-bar.d.ts +73 -0
- package/dist/components/filter-presets-bar.js +510 -0
- package/dist/components/reaction-button.d.ts +7 -2
- package/dist/components/reaction-button.js +59 -48
- package/dist/hooks.d.ts +10 -0
- package/dist/hooks.js +10 -0
- package/dist/index.d.ts +1 -23
- package/dist/index.js +1 -23
- package/dist/styles.css +1 -1
- package/package.json +11 -126
- package/dist/components/bouncing-dots-loader.d.ts +0 -19
- package/dist/components/bouncing-dots-loader.js +0 -47
- package/dist/components/confirm-delete-button.d.ts +0 -29
- package/dist/components/confirm-delete-button.js +0 -254
- package/dist/components/empty-state.d.ts +0 -24
- package/dist/components/empty-state.js +0 -37
- package/dist/components/error-state.d.ts +0 -20
- package/dist/components/error-state.js +0 -145
- package/dist/components/external-browser-handoff.d.ts +0 -27
- package/dist/components/external-browser-handoff.js +0 -306
- package/dist/components/info-popover.d.ts +0 -23
- package/dist/components/info-popover.js +0 -158
- package/dist/components/month-calendar-header.d.ts +0 -65
- package/dist/components/month-calendar-header.js +0 -337
- package/dist/components/month-grid.d.ts +0 -59
- package/dist/components/month-grid.js +0 -301
- package/dist/components/notification-bell.d.ts +0 -50
- package/dist/components/notification-bell.js +0 -338
- package/dist/components/notification-panel.d.ts +0 -56
- package/dist/components/notification-panel.js +0 -281
- package/dist/components/open-in-browser-cta.d.ts +0 -22
- package/dist/components/open-in-browser-cta.js +0 -61
- package/dist/components/pwa-update-control.d.ts +0 -18
- package/dist/components/pwa-update-control.js +0 -119
- package/dist/emoji-picker.d.ts +0 -27
- package/dist/emoji-picker.js +0 -283
- package/dist/hooks/use-minute-clock.d.ts +0 -23
- package/dist/hooks/use-minute-clock.js +0 -147
- package/dist/hooks/use-month-transition-direction.d.ts +0 -21
- package/dist/hooks/use-month-transition-direction.js +0 -110
- package/dist/lib/browser-navigation.d.ts +0 -21
- package/dist/lib/browser-navigation.js +0 -42
- package/dist/lib/in-app-browser.d.ts +0 -35
- package/dist/lib/in-app-browser.js +0 -80
- package/dist/lib/month-grid.d.ts +0 -42
- package/dist/lib/month-grid.js +0 -74
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Content of a notification inbox: header with "mark all as read", the list, and the loading,
|
|
3
|
-
* error and empty states. Presentational: items, statuses and callbacks come from the caller.
|
|
4
|
-
*/
|
|
5
|
-
import type { ReactNode } from "react";
|
|
6
|
-
export declare const NOTIFICATION_PANEL_STATUS: {
|
|
7
|
-
readonly error: "error";
|
|
8
|
-
readonly idle: "idle";
|
|
9
|
-
readonly loaded: "loaded";
|
|
10
|
-
readonly loading: "loading";
|
|
11
|
-
};
|
|
12
|
-
export type NotificationPanelStatus = (typeof NOTIFICATION_PANEL_STATUS)[keyof typeof NOTIFICATION_PANEL_STATUS];
|
|
13
|
-
/** Presentation of one inbox item, already resolved by the application. */
|
|
14
|
-
export interface NotificationPanelItem {
|
|
15
|
-
id: string;
|
|
16
|
-
title: string;
|
|
17
|
-
/** Secondary line, such as when or where. */
|
|
18
|
-
detail?: string;
|
|
19
|
-
/** Short creation stamp, such as "12 may · 18:00". */
|
|
20
|
-
sentAtLabel?: string;
|
|
21
|
-
/** In-app destination; navigates through the configured router adapter. */
|
|
22
|
-
href: string;
|
|
23
|
-
isUnread: boolean;
|
|
24
|
-
}
|
|
25
|
-
export interface NotificationPanelLabels {
|
|
26
|
-
markAllRead: string;
|
|
27
|
-
loading: string;
|
|
28
|
-
error: string;
|
|
29
|
-
retry: string;
|
|
30
|
-
empty: string;
|
|
31
|
-
emptyHint: string;
|
|
32
|
-
/** Visually hidden prefix of unread items. */
|
|
33
|
-
unreadPrefix: string;
|
|
34
|
-
}
|
|
35
|
-
export declare const NOTIFICATION_PANEL_DEFAULT_LABELS: NotificationPanelLabels;
|
|
36
|
-
export interface NotificationPanelProps {
|
|
37
|
-
notifications: readonly NotificationPanelItem[];
|
|
38
|
-
listStatus: NotificationPanelStatus;
|
|
39
|
-
unreadCount: number;
|
|
40
|
-
isMarkingAll: boolean;
|
|
41
|
-
onMarkAllRead: () => void;
|
|
42
|
-
onRetry: () => void;
|
|
43
|
-
onSelectNotification: (notificationId: string) => void;
|
|
44
|
-
/** Heading rendered by the surface (popover or sheet title). */
|
|
45
|
-
titleSlot: ReactNode;
|
|
46
|
-
/** Leaves room for a sheet close button at the end of the header. */
|
|
47
|
-
reservesCloseButtonSpace?: boolean;
|
|
48
|
-
labels?: Partial<NotificationPanelLabels>;
|
|
49
|
-
className?: string;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Renders the inbox. Unread items keep a dot that fades out when they are read.
|
|
53
|
-
* @param props - Items, load status, callbacks, title slot and optional labels.
|
|
54
|
-
* @returns The panel.
|
|
55
|
-
*/
|
|
56
|
-
export declare function NotificationPanel({ notifications, listStatus, unreadCount, isMarkingAll, onMarkAllRead, onRetry, onSelectNotification, titleSlot, reservesCloseButtonSpace, labels, className, }: NotificationPanelProps): import("react").JSX.Element;
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { c as _c } from "react/compiler-runtime";
|
|
3
|
-
import { BellOffIcon, CheckCheckIcon } from "lucide-react";
|
|
4
|
-
import { AnimatePresence, motion } from "motion/react";
|
|
5
|
-
import { cn } from "../lib/utils.js";
|
|
6
|
-
import { MOTION_EASE, MOTION_TIMING } from "../motion/tokens.js";
|
|
7
|
-
import { AnimatedListItem } from "./animated-list-item.js";
|
|
8
|
-
import { Button } from "./button.js";
|
|
9
|
-
import { Link } from "./link.js";
|
|
10
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
-
export const NOTIFICATION_PANEL_STATUS = {
|
|
12
|
-
error: "error",
|
|
13
|
-
idle: "idle",
|
|
14
|
-
loaded: "loaded",
|
|
15
|
-
loading: "loading"
|
|
16
|
-
};
|
|
17
|
-
export const NOTIFICATION_PANEL_DEFAULT_LABELS = {
|
|
18
|
-
markAllRead: "Marcar todas como leídas",
|
|
19
|
-
loading: "Cargando notificaciones…",
|
|
20
|
-
error: "No pudimos cargar tus notificaciones.",
|
|
21
|
-
retry: "Reintentar",
|
|
22
|
-
empty: "No tenés notificaciones.",
|
|
23
|
-
emptyHint: "Te avisamos acá cuando cambie algo que te importa.",
|
|
24
|
-
unreadPrefix: "No leída: "
|
|
25
|
-
};
|
|
26
|
-
/** Body regions fade in when they mount, so the new state is in the DOM at once. */
|
|
27
|
-
const REGION_FADE_CLASS_NAME = "animate-in fade-in duration-180";
|
|
28
|
-
const MARK_ALL_FADE = {
|
|
29
|
-
animate: { opacity: 1 },
|
|
30
|
-
exit: { opacity: 0 },
|
|
31
|
-
initial: { opacity: 0 },
|
|
32
|
-
transition: {
|
|
33
|
-
duration: MOTION_TIMING.enter,
|
|
34
|
-
ease: MOTION_EASE
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
/**
|
|
38
|
-
* Picks the body region: the list whenever there are items (a background refresh never hides
|
|
39
|
-
* them), otherwise the state of the last load.
|
|
40
|
-
* @param hasNotifications - Whether the inbox has at least one item.
|
|
41
|
-
* @param listStatus - Status of the latest list load.
|
|
42
|
-
* @returns The region to render.
|
|
43
|
-
*/
|
|
44
|
-
function resolvePanelView(hasNotifications, listStatus) {
|
|
45
|
-
if (hasNotifications) return "list";
|
|
46
|
-
if (listStatus === NOTIFICATION_PANEL_STATUS.loading) return "loading";
|
|
47
|
-
if (listStatus === NOTIFICATION_PANEL_STATUS.error) return "error";
|
|
48
|
-
if (listStatus === NOTIFICATION_PANEL_STATUS.loaded) return "empty";
|
|
49
|
-
return "idle";
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Renders the inbox. Unread items keep a dot that fades out when they are read.
|
|
53
|
-
* @param props - Items, load status, callbacks, title slot and optional labels.
|
|
54
|
-
* @returns The panel.
|
|
55
|
-
*/
|
|
56
|
-
export function NotificationPanel(t0) {
|
|
57
|
-
const $ = _c(42);
|
|
58
|
-
const { notifications, listStatus, unreadCount, isMarkingAll, onMarkAllRead, onRetry, onSelectNotification, titleSlot, reservesCloseButtonSpace: t1, labels, className } = t0;
|
|
59
|
-
const reservesCloseButtonSpace = t1 === undefined ? false : t1;
|
|
60
|
-
let t2;
|
|
61
|
-
if ($[0] !== labels) {
|
|
62
|
-
t2 = {
|
|
63
|
-
...NOTIFICATION_PANEL_DEFAULT_LABELS,
|
|
64
|
-
...labels
|
|
65
|
-
};
|
|
66
|
-
$[0] = labels;
|
|
67
|
-
$[1] = t2;
|
|
68
|
-
} else {
|
|
69
|
-
t2 = $[1];
|
|
70
|
-
}
|
|
71
|
-
const resolvedLabels = t2;
|
|
72
|
-
const t3 = notifications.length > 0;
|
|
73
|
-
let t4;
|
|
74
|
-
if ($[2] !== listStatus || $[3] !== t3) {
|
|
75
|
-
t4 = resolvePanelView(t3, listStatus);
|
|
76
|
-
$[2] = listStatus;
|
|
77
|
-
$[3] = t3;
|
|
78
|
-
$[4] = t4;
|
|
79
|
-
} else {
|
|
80
|
-
t4 = $[4];
|
|
81
|
-
}
|
|
82
|
-
const panelView = t4;
|
|
83
|
-
let t5;
|
|
84
|
-
if ($[5] !== className) {
|
|
85
|
-
t5 = cn("flex min-h-0 min-w-0 flex-col", className);
|
|
86
|
-
$[5] = className;
|
|
87
|
-
$[6] = t5;
|
|
88
|
-
} else {
|
|
89
|
-
t5 = $[6];
|
|
90
|
-
}
|
|
91
|
-
const t6 = reservesCloseButtonSpace && "pr-12";
|
|
92
|
-
let t7;
|
|
93
|
-
if ($[7] !== t6) {
|
|
94
|
-
t7 = cn("flex flex-wrap items-center justify-between gap-2 border-b border-border px-4 py-3", t6);
|
|
95
|
-
$[7] = t6;
|
|
96
|
-
$[8] = t7;
|
|
97
|
-
} else {
|
|
98
|
-
t7 = $[8];
|
|
99
|
-
}
|
|
100
|
-
let t8;
|
|
101
|
-
if ($[9] !== isMarkingAll || $[10] !== onMarkAllRead || $[11] !== resolvedLabels || $[12] !== unreadCount) {
|
|
102
|
-
t8 = unreadCount > 0 ? /* @__PURE__ */ _jsx(motion.span, {
|
|
103
|
-
className: "-mr-2 inline-flex",
|
|
104
|
-
...MARK_ALL_FADE,
|
|
105
|
-
children: /* @__PURE__ */ _jsxs(Button, {
|
|
106
|
-
className: "text-[0.8rem] text-muted-foreground",
|
|
107
|
-
disabled: isMarkingAll,
|
|
108
|
-
onClick: onMarkAllRead,
|
|
109
|
-
size: "sm",
|
|
110
|
-
type: "button",
|
|
111
|
-
variant: "ghost",
|
|
112
|
-
children: [/* @__PURE__ */ _jsx(CheckCheckIcon, { "aria-hidden": "true" }), resolvedLabels.markAllRead]
|
|
113
|
-
})
|
|
114
|
-
}, "mark-all") : null;
|
|
115
|
-
$[9] = isMarkingAll;
|
|
116
|
-
$[10] = onMarkAllRead;
|
|
117
|
-
$[11] = resolvedLabels;
|
|
118
|
-
$[12] = unreadCount;
|
|
119
|
-
$[13] = t8;
|
|
120
|
-
} else {
|
|
121
|
-
t8 = $[13];
|
|
122
|
-
}
|
|
123
|
-
let t9;
|
|
124
|
-
if ($[14] !== t8) {
|
|
125
|
-
t9 = /* @__PURE__ */ _jsx(AnimatePresence, {
|
|
126
|
-
initial: false,
|
|
127
|
-
children: t8
|
|
128
|
-
});
|
|
129
|
-
$[14] = t8;
|
|
130
|
-
$[15] = t9;
|
|
131
|
-
} else {
|
|
132
|
-
t9 = $[15];
|
|
133
|
-
}
|
|
134
|
-
let t10;
|
|
135
|
-
if ($[16] !== t7 || $[17] !== t9 || $[18] !== titleSlot) {
|
|
136
|
-
t10 = /* @__PURE__ */ _jsxs("div", {
|
|
137
|
-
className: t7,
|
|
138
|
-
children: [titleSlot, t9]
|
|
139
|
-
});
|
|
140
|
-
$[16] = t7;
|
|
141
|
-
$[17] = t9;
|
|
142
|
-
$[18] = titleSlot;
|
|
143
|
-
$[19] = t10;
|
|
144
|
-
} else {
|
|
145
|
-
t10 = $[19];
|
|
146
|
-
}
|
|
147
|
-
let t11;
|
|
148
|
-
if ($[20] !== panelView || $[21] !== resolvedLabels) {
|
|
149
|
-
t11 = panelView === "loading" ? /* @__PURE__ */ _jsx("p", {
|
|
150
|
-
className: cn("m-0 grid justify-items-start gap-3 p-4 text-[0.9rem] text-muted-foreground", REGION_FADE_CLASS_NAME),
|
|
151
|
-
role: "status",
|
|
152
|
-
children: resolvedLabels.loading
|
|
153
|
-
}) : null;
|
|
154
|
-
$[20] = panelView;
|
|
155
|
-
$[21] = resolvedLabels;
|
|
156
|
-
$[22] = t11;
|
|
157
|
-
} else {
|
|
158
|
-
t11 = $[22];
|
|
159
|
-
}
|
|
160
|
-
let t12;
|
|
161
|
-
if ($[23] !== onRetry || $[24] !== panelView || $[25] !== resolvedLabels) {
|
|
162
|
-
t12 = panelView === "error" ? /* @__PURE__ */ _jsxs("div", {
|
|
163
|
-
className: cn("grid justify-items-start gap-3 p-4 text-[0.9rem] text-muted-foreground", REGION_FADE_CLASS_NAME),
|
|
164
|
-
role: "alert",
|
|
165
|
-
children: [/* @__PURE__ */ _jsx("p", {
|
|
166
|
-
className: "m-0",
|
|
167
|
-
children: resolvedLabels.error
|
|
168
|
-
}), /* @__PURE__ */ _jsx(Button, {
|
|
169
|
-
onClick: onRetry,
|
|
170
|
-
size: "sm",
|
|
171
|
-
type: "button",
|
|
172
|
-
variant: "outline",
|
|
173
|
-
children: resolvedLabels.retry
|
|
174
|
-
})]
|
|
175
|
-
}) : null;
|
|
176
|
-
$[23] = onRetry;
|
|
177
|
-
$[24] = panelView;
|
|
178
|
-
$[25] = resolvedLabels;
|
|
179
|
-
$[26] = t12;
|
|
180
|
-
} else {
|
|
181
|
-
t12 = $[26];
|
|
182
|
-
}
|
|
183
|
-
let t13;
|
|
184
|
-
if ($[27] !== panelView || $[28] !== resolvedLabels) {
|
|
185
|
-
t13 = panelView === "empty" ? /* @__PURE__ */ _jsxs("div", {
|
|
186
|
-
className: cn("grid justify-items-center gap-[0.35rem] px-5 py-8 text-center text-[0.9rem] text-muted-foreground", REGION_FADE_CLASS_NAME),
|
|
187
|
-
children: [
|
|
188
|
-
/* @__PURE__ */ _jsx(BellOffIcon, {
|
|
189
|
-
"aria-hidden": "true",
|
|
190
|
-
className: "mb-1 size-6 opacity-80"
|
|
191
|
-
}),
|
|
192
|
-
/* @__PURE__ */ _jsx("p", {
|
|
193
|
-
className: "m-0",
|
|
194
|
-
children: resolvedLabels.empty
|
|
195
|
-
}),
|
|
196
|
-
/* @__PURE__ */ _jsx("p", {
|
|
197
|
-
className: "m-0 max-w-72 text-[0.8rem]",
|
|
198
|
-
children: resolvedLabels.emptyHint
|
|
199
|
-
})
|
|
200
|
-
]
|
|
201
|
-
}) : null;
|
|
202
|
-
$[27] = panelView;
|
|
203
|
-
$[28] = resolvedLabels;
|
|
204
|
-
$[29] = t13;
|
|
205
|
-
} else {
|
|
206
|
-
t13 = $[29];
|
|
207
|
-
}
|
|
208
|
-
let t14;
|
|
209
|
-
if ($[30] !== notifications || $[31] !== onSelectNotification || $[32] !== panelView || $[33] !== resolvedLabels) {
|
|
210
|
-
t14 = panelView === "list" ? /* @__PURE__ */ _jsx("ul", {
|
|
211
|
-
className: cn("m-0 min-h-0 list-none overflow-y-auto overscroll-contain py-1", REGION_FADE_CLASS_NAME),
|
|
212
|
-
children: /* @__PURE__ */ _jsx(AnimatePresence, {
|
|
213
|
-
initial: false,
|
|
214
|
-
children: notifications.map((notification) => /* @__PURE__ */ _jsx(AnimatedListItem, {
|
|
215
|
-
className: "[&+&]:border-t [&+&]:border-border/60",
|
|
216
|
-
children: /* @__PURE__ */ _jsxs(Link, {
|
|
217
|
-
className: cn("grid grid-cols-[0.5rem_minmax(0,1fr)] items-start gap-3 px-4 py-3 text-foreground no-underline outline-none [-webkit-tap-highlight-color:transparent] transition-[background-color,box-shadow] focus-visible:bg-accent focus-visible:shadow-[inset_0_0_0_2px_var(--ring)] active:bg-accent [@media(hover:hover)]:hover:bg-accent", notification.isUnread && "bg-primary/6"),
|
|
218
|
-
href: notification.href,
|
|
219
|
-
onClick: () => onSelectNotification(notification.id),
|
|
220
|
-
children: [/* @__PURE__ */ _jsx("span", {
|
|
221
|
-
"aria-hidden": "true",
|
|
222
|
-
"data-unread": notification.isUnread || undefined,
|
|
223
|
-
className: "mt-[0.4rem] size-2 scale-50 rounded-full bg-primary opacity-0 transition-[opacity,scale] data-unread:scale-100 data-unread:opacity-100"
|
|
224
|
-
}), /* @__PURE__ */ _jsxs("span", {
|
|
225
|
-
className: "grid min-w-0 gap-[0.15rem]",
|
|
226
|
-
children: [
|
|
227
|
-
notification.isUnread ? /* @__PURE__ */ _jsx("span", {
|
|
228
|
-
className: "sr-only",
|
|
229
|
-
children: resolvedLabels.unreadPrefix
|
|
230
|
-
}) : null,
|
|
231
|
-
/* @__PURE__ */ _jsx("span", {
|
|
232
|
-
className: "text-[0.9rem] leading-[1.35] font-medium [overflow-wrap:anywhere]",
|
|
233
|
-
children: notification.title
|
|
234
|
-
}),
|
|
235
|
-
notification.detail ? /* @__PURE__ */ _jsx("span", {
|
|
236
|
-
className: "text-[0.8rem] leading-[1.4] text-muted-foreground [overflow-wrap:anywhere]",
|
|
237
|
-
children: notification.detail
|
|
238
|
-
}) : null,
|
|
239
|
-
notification.sentAtLabel ? /* @__PURE__ */ _jsx("span", {
|
|
240
|
-
className: "text-xs text-muted-foreground",
|
|
241
|
-
children: notification.sentAtLabel
|
|
242
|
-
}) : null
|
|
243
|
-
]
|
|
244
|
-
})]
|
|
245
|
-
})
|
|
246
|
-
}, notification.id))
|
|
247
|
-
})
|
|
248
|
-
}) : null;
|
|
249
|
-
$[30] = notifications;
|
|
250
|
-
$[31] = onSelectNotification;
|
|
251
|
-
$[32] = panelView;
|
|
252
|
-
$[33] = resolvedLabels;
|
|
253
|
-
$[34] = t14;
|
|
254
|
-
} else {
|
|
255
|
-
t14 = $[34];
|
|
256
|
-
}
|
|
257
|
-
let t15;
|
|
258
|
-
if ($[35] !== t10 || $[36] !== t11 || $[37] !== t12 || $[38] !== t13 || $[39] !== t14 || $[40] !== t5) {
|
|
259
|
-
t15 = /* @__PURE__ */ _jsxs("div", {
|
|
260
|
-
"data-slot": "notification-panel",
|
|
261
|
-
className: t5,
|
|
262
|
-
children: [
|
|
263
|
-
t10,
|
|
264
|
-
t11,
|
|
265
|
-
t12,
|
|
266
|
-
t13,
|
|
267
|
-
t14
|
|
268
|
-
]
|
|
269
|
-
});
|
|
270
|
-
$[35] = t10;
|
|
271
|
-
$[36] = t11;
|
|
272
|
-
$[37] = t12;
|
|
273
|
-
$[38] = t13;
|
|
274
|
-
$[39] = t14;
|
|
275
|
-
$[40] = t5;
|
|
276
|
-
$[41] = t15;
|
|
277
|
-
} else {
|
|
278
|
-
t15 = $[41];
|
|
279
|
-
}
|
|
280
|
-
return t15;
|
|
281
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
export interface OpenInBrowserCtaCopy {
|
|
2
|
-
title: string;
|
|
3
|
-
description: string;
|
|
4
|
-
iosButtonLabel: string;
|
|
5
|
-
iosHint: string;
|
|
6
|
-
manualHint: string;
|
|
7
|
-
}
|
|
8
|
-
export interface OpenInBrowserCtaProps {
|
|
9
|
-
/** Absolute https URL to continue with, such as the sign-in page. */
|
|
10
|
-
url: string;
|
|
11
|
-
/** iOS gets a Safari deep link; other platforms get manual steps. */
|
|
12
|
-
isIos: boolean;
|
|
13
|
-
copy?: Partial<OpenInBrowserCtaCopy>;
|
|
14
|
-
className?: string;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Renders the handoff section. The URL is selectable with one tap, since copying is the only
|
|
18
|
-
* universal escape from embedded browsers.
|
|
19
|
-
* @param props - Destination URL, platform and optional copy.
|
|
20
|
-
* @returns The call to action.
|
|
21
|
-
*/
|
|
22
|
-
export declare function OpenInBrowserCta({ url, isIos, copy, className }: OpenInBrowserCtaProps): import("react").JSX.Element;
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Asks people inside an in-app browser to continue in their regular browser: a Safari deep link
|
|
3
|
-
* on iOS, manual steps elsewhere, and the destination URL ready to copy. Server-safe.
|
|
4
|
-
*/
|
|
5
|
-
import { cn } from "../lib/utils.js";
|
|
6
|
-
import { EXTERNAL_BROWSER_PLATFORM, buildExternalBrowserUrl } from "../lib/in-app-browser.js";
|
|
7
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
-
const ENTER_CLASS_NAME = "animate-in fade-in slide-in-from-bottom-1.5 fill-mode-both duration-300";
|
|
9
|
-
const OPEN_IN_BROWSER_DEFAULT_COPY = {
|
|
10
|
-
title: "Abrí este sitio en tu navegador",
|
|
11
|
-
description: "Estás dentro del navegador interno de la app que te trajo hasta acá. Para continuar, abrí este sitio en tu navegador habitual.",
|
|
12
|
-
iosButtonLabel: "Abrir en Safari",
|
|
13
|
-
iosHint: "Tocá el botón para abrir el sitio en Safari y continuá desde ahí.",
|
|
14
|
-
manualHint: "Tocá los tres puntos arriba a la derecha y elegí “Abrir en el navegador”, o copiá este link y pegalo en tu navegador."
|
|
15
|
-
};
|
|
16
|
-
/**
|
|
17
|
-
* Renders the handoff section. The URL is selectable with one tap, since copying is the only
|
|
18
|
-
* universal escape from embedded browsers.
|
|
19
|
-
* @param props - Destination URL, platform and optional copy.
|
|
20
|
-
* @returns The call to action.
|
|
21
|
-
*/
|
|
22
|
-
export function OpenInBrowserCta({ url, isIos, copy, className }) {
|
|
23
|
-
const resolvedCopy = {
|
|
24
|
-
...OPEN_IN_BROWSER_DEFAULT_COPY,
|
|
25
|
-
...copy
|
|
26
|
-
};
|
|
27
|
-
const safariUrl = buildExternalBrowserUrl({
|
|
28
|
-
platform: EXTERNAL_BROWSER_PLATFORM.ios,
|
|
29
|
-
targetHttpsUrl: url
|
|
30
|
-
}) ?? url;
|
|
31
|
-
return /* @__PURE__ */ _jsxs("section", {
|
|
32
|
-
"data-slot": "open-in-browser-cta",
|
|
33
|
-
className: cn("mt-6 flex flex-col gap-4", className),
|
|
34
|
-
children: [
|
|
35
|
-
/* @__PURE__ */ _jsx("h2", {
|
|
36
|
-
className: cn("m-0 text-lg font-semibold text-foreground", ENTER_CLASS_NAME),
|
|
37
|
-
children: resolvedCopy.title
|
|
38
|
-
}),
|
|
39
|
-
/* @__PURE__ */ _jsx("p", {
|
|
40
|
-
className: cn("m-0 text-sm leading-6 text-muted-foreground", ENTER_CLASS_NAME, "delay-35"),
|
|
41
|
-
children: resolvedCopy.description
|
|
42
|
-
}),
|
|
43
|
-
isIos ? /* @__PURE__ */ _jsxs(_Fragment, { children: [/* @__PURE__ */ _jsx("a", {
|
|
44
|
-
className: cn("inline-flex h-10 items-center justify-center self-stretch rounded-lg bg-primary px-4 text-sm font-medium text-primary-foreground no-underline [-webkit-tap-highlight-color:transparent] transition-[background-color,box-shadow,scale] outline-none active:scale-[0.98] focus-visible:shadow-[0_0_0_2px_var(--background),0_0_0_4px_var(--ring)] [@media(hover:hover)]:hover:bg-primary/80", ENTER_CLASS_NAME, "delay-70"),
|
|
45
|
-
href: safariUrl,
|
|
46
|
-
rel: "noopener noreferrer",
|
|
47
|
-
children: resolvedCopy.iosButtonLabel
|
|
48
|
-
}), /* @__PURE__ */ _jsx("p", {
|
|
49
|
-
className: cn("m-0 text-[0.8125rem] leading-5 text-muted-foreground", ENTER_CLASS_NAME, "delay-105"),
|
|
50
|
-
children: resolvedCopy.iosHint
|
|
51
|
-
})] }) : /* @__PURE__ */ _jsx("p", {
|
|
52
|
-
className: cn("m-0 text-[0.8125rem] leading-5 text-muted-foreground", ENTER_CLASS_NAME, "delay-70"),
|
|
53
|
-
children: resolvedCopy.manualHint
|
|
54
|
-
}),
|
|
55
|
-
/* @__PURE__ */ _jsx("p", {
|
|
56
|
-
className: cn("m-0 rounded-lg border border-border bg-muted/50 p-3 font-mono text-xs leading-[1.125rem] break-all text-muted-foreground select-all", ENTER_CLASS_NAME, "delay-140"),
|
|
57
|
-
children: url
|
|
58
|
-
})
|
|
59
|
-
]
|
|
60
|
-
});
|
|
61
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export interface PwaUpdateControlLabels {
|
|
2
|
-
/** Short notice next to the button; hidden on narrow screens. */
|
|
3
|
-
badge?: string;
|
|
4
|
-
action?: string;
|
|
5
|
-
}
|
|
6
|
-
export interface PwaUpdateControlProps {
|
|
7
|
-
labels?: PwaUpdateControlLabels;
|
|
8
|
-
/** Message posted to the waiting worker so it activates. */
|
|
9
|
-
skipWaitingMessage?: unknown;
|
|
10
|
-
className?: string;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Renders the update notice and action once an update is waiting; nothing otherwise, including
|
|
14
|
-
* during server rendering and hydration.
|
|
15
|
-
* @param props - Optional labels, activation message and class name.
|
|
16
|
-
* @returns The update control, or nothing.
|
|
17
|
-
*/
|
|
18
|
-
export declare function PwaUpdateControl({ labels, skipWaitingMessage, className }: PwaUpdateControlProps): import("react").JSX.Element | null;
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
/**
|
|
3
|
-
* Offers to apply a waiting service worker update. It stays hidden until the browser reports a
|
|
4
|
-
* waiting worker, asks it to activate on demand and reloads once the new worker takes control.
|
|
5
|
-
*/
|
|
6
|
-
import { useEffect, useRef, useState } from "react";
|
|
7
|
-
import { RefreshCw } from "lucide-react";
|
|
8
|
-
import { useIsHydrated } from "../hooks/use-is-hydrated.js";
|
|
9
|
-
import { cn } from "../lib/utils.js";
|
|
10
|
-
import { Button } from "./button.js";
|
|
11
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
12
|
-
/** Message understood by Workbox and most hand-written workers to call `skipWaiting()`. */
|
|
13
|
-
const DEFAULT_SKIP_WAITING_MESSAGE = { type: "SKIP_WAITING" };
|
|
14
|
-
const INSTALLED_WORKER_STATE = "installed";
|
|
15
|
-
const PWA_UPDATE_DEFAULT_LABELS = {
|
|
16
|
-
badge: "Hay una nueva versión",
|
|
17
|
-
action: "Actualizar app"
|
|
18
|
-
};
|
|
19
|
-
/**
|
|
20
|
-
* Tells whether the current page can talk to service workers.
|
|
21
|
-
* @returns Whether the Service Worker API is available.
|
|
22
|
-
*/
|
|
23
|
-
function canUseServiceWorker() {
|
|
24
|
-
return typeof window !== "undefined" && "serviceWorker" in navigator;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Reports a waiting worker now, or as soon as a newly found one finishes installing.
|
|
28
|
-
* @param registration - Registration of the current page.
|
|
29
|
-
* @param onWaiting - Called when an update is ready to activate.
|
|
30
|
-
* @returns A cleanup that stops watching.
|
|
31
|
-
*/
|
|
32
|
-
function watchForWaitingWorker(registration, onWaiting) {
|
|
33
|
-
if (registration.waiting) onWaiting();
|
|
34
|
-
const handleUpdateFound = () => {
|
|
35
|
-
const installingWorker = registration.installing;
|
|
36
|
-
if (!installingWorker) return;
|
|
37
|
-
installingWorker.addEventListener("statechange", () => {
|
|
38
|
-
// Without a controller this is the first install, not an update.
|
|
39
|
-
if (installingWorker.state === INSTALLED_WORKER_STATE && navigator.serviceWorker.controller) onWaiting();
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
registration.addEventListener("updatefound", handleUpdateFound);
|
|
43
|
-
return () => registration.removeEventListener("updatefound", handleUpdateFound);
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Renders the update notice and action once an update is waiting; nothing otherwise, including
|
|
47
|
-
* during server rendering and hydration.
|
|
48
|
-
* @param props - Optional labels, activation message and class name.
|
|
49
|
-
* @returns The update control, or nothing.
|
|
50
|
-
*/
|
|
51
|
-
export function PwaUpdateControl({ labels, skipWaitingMessage = DEFAULT_SKIP_WAITING_MESSAGE, className }) {
|
|
52
|
-
const resolvedLabels = {
|
|
53
|
-
...PWA_UPDATE_DEFAULT_LABELS,
|
|
54
|
-
...labels
|
|
55
|
-
};
|
|
56
|
-
const isHydrated = useIsHydrated();
|
|
57
|
-
const [hasUpdateReady, setHasUpdateReady] = useState(false);
|
|
58
|
-
const [isApplyingUpdate, setIsApplyingUpdate] = useState(false);
|
|
59
|
-
const hasReloadedForControllerChange = useRef(false);
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (!isHydrated || !canUseServiceWorker()) return;
|
|
62
|
-
let stopWatchingRegistration = () => {};
|
|
63
|
-
let isDisposed = false;
|
|
64
|
-
const markUpdateReady = () => {
|
|
65
|
-
if (!isDisposed) setHasUpdateReady(true);
|
|
66
|
-
};
|
|
67
|
-
const handleControllerChange = () => {
|
|
68
|
-
if (hasReloadedForControllerChange.current) return;
|
|
69
|
-
hasReloadedForControllerChange.current = true;
|
|
70
|
-
window.location.reload();
|
|
71
|
-
};
|
|
72
|
-
navigator.serviceWorker.addEventListener("controllerchange", handleControllerChange);
|
|
73
|
-
const attachUpdateWatcher = async () => {
|
|
74
|
-
const registration = await navigator.serviceWorker.getRegistration();
|
|
75
|
-
if (!registration || isDisposed) return;
|
|
76
|
-
stopWatchingRegistration = watchForWaitingWorker(registration, markUpdateReady);
|
|
77
|
-
// A failed check (offline, server error) keeps the current version without blocking the page.
|
|
78
|
-
await registration.update().catch(() => undefined);
|
|
79
|
-
if (registration.waiting) markUpdateReady();
|
|
80
|
-
};
|
|
81
|
-
void attachUpdateWatcher();
|
|
82
|
-
return () => {
|
|
83
|
-
isDisposed = true;
|
|
84
|
-
stopWatchingRegistration();
|
|
85
|
-
navigator.serviceWorker.removeEventListener("controllerchange", handleControllerChange);
|
|
86
|
-
};
|
|
87
|
-
}, [isHydrated]);
|
|
88
|
-
const applyUpdate = async () => {
|
|
89
|
-
setIsApplyingUpdate(true);
|
|
90
|
-
try {
|
|
91
|
-
const registration = await navigator.serviceWorker.getRegistration();
|
|
92
|
-
if (!registration) return;
|
|
93
|
-
if (!registration.waiting) await registration.update();
|
|
94
|
-
registration.waiting?.postMessage(skipWaitingMessage);
|
|
95
|
-
} catch (updateError) {
|
|
96
|
-
console.error("PwaUpdateControl:applyUpdate failed to activate the waiting service worker", updateError);
|
|
97
|
-
} finally {
|
|
98
|
-
setIsApplyingUpdate(false);
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
if (!isHydrated || !canUseServiceWorker() || !hasUpdateReady) return null;
|
|
102
|
-
return /* @__PURE__ */ _jsxs("div", {
|
|
103
|
-
"data-slot": "pwa-update-control",
|
|
104
|
-
className: cn("inline-flex items-center gap-[0.45rem]", className),
|
|
105
|
-
children: [/* @__PURE__ */ _jsx("span", {
|
|
106
|
-
className: "hidden items-center rounded-full border border-[color-mix(in_srgb,var(--border)_84%,var(--primary)_16%)] bg-[color-mix(in_srgb,var(--muted)_90%,var(--primary)_10%)] px-2 py-[0.1rem] text-[0.72rem] leading-tight text-[color-mix(in_srgb,var(--foreground)_78%,var(--primary)_22%)] sm:inline-flex",
|
|
107
|
-
children: resolvedLabels.badge
|
|
108
|
-
}), /* @__PURE__ */ _jsxs(Button, {
|
|
109
|
-
disabled: isApplyingUpdate,
|
|
110
|
-
size: "sm",
|
|
111
|
-
type: "button",
|
|
112
|
-
onClick: () => void applyUpdate(),
|
|
113
|
-
children: [/* @__PURE__ */ _jsx(RefreshCw, {
|
|
114
|
-
"aria-hidden": "true",
|
|
115
|
-
className: cn(isApplyingUpdate && "animate-spin")
|
|
116
|
-
}), resolvedLabels.action]
|
|
117
|
-
})]
|
|
118
|
-
});
|
|
119
|
-
}
|
package/dist/emoji-picker.d.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export interface EmojiPickerLabels {
|
|
2
|
-
/** Accessible name of the trigger. */
|
|
3
|
-
chooseEmoji: string;
|
|
4
|
-
/** Accessible name of the popover. */
|
|
5
|
-
picker: string;
|
|
6
|
-
searchPlaceholder: string;
|
|
7
|
-
loading: string;
|
|
8
|
-
}
|
|
9
|
-
export interface EmojiPickerProps {
|
|
10
|
-
/** Currently selected emoji; empty shows a placeholder icon. */
|
|
11
|
-
value: string;
|
|
12
|
-
onChange: (emoji: string) => void;
|
|
13
|
-
/** Visible label of the field. */
|
|
14
|
-
label: string;
|
|
15
|
-
/** Keeps the label for assistive technology but hides it visually. */
|
|
16
|
-
isLabelVisuallyHidden?: boolean;
|
|
17
|
-
disabled?: boolean;
|
|
18
|
-
labels?: Partial<EmojiPickerLabels>;
|
|
19
|
-
className?: string;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Renders an outline trigger showing the current emoji that opens a searchable emoji popover. The
|
|
23
|
-
* chosen emoji swaps in with a short cross-fade; the picker follows the resolved color theme.
|
|
24
|
-
* @param props - Current value, change handler, label and optional copy.
|
|
25
|
-
* @returns The labelled emoji picker.
|
|
26
|
-
*/
|
|
27
|
-
export declare function EmojiPicker({ value, onChange, label, isLabelVisuallyHidden, disabled, labels, className, }: EmojiPickerProps): import("react").JSX.Element;
|