beez-ui 0.7.0 → 0.8.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/CHANGELOG.md +21 -3
- package/README.md +290 -274
- package/dist/components/account-menu.d.ts +53 -0
- package/dist/components/account-menu.js +405 -0
- package/dist/components/animated-collapse.d.ts +19 -0
- package/dist/components/animated-collapse.js +81 -0
- package/dist/components/animated-count.d.ts +13 -0
- package/dist/components/animated-count.js +105 -0
- package/dist/components/animated-list-item.d.ts +25 -0
- package/dist/components/animated-list-item.js +132 -0
- package/dist/components/bouncing-dots-loader.d.ts +19 -0
- package/dist/components/bouncing-dots-loader.js +47 -0
- package/dist/components/confirm-delete-button.d.ts +29 -0
- package/dist/components/confirm-delete-button.js +254 -0
- package/dist/components/empty-state.d.ts +24 -0
- package/dist/components/empty-state.js +37 -0
- package/dist/components/error-state.d.ts +20 -0
- package/dist/components/error-state.js +145 -0
- package/dist/components/external-browser-handoff.d.ts +27 -0
- package/dist/components/external-browser-handoff.js +306 -0
- package/dist/components/file-upload.d.ts +78 -0
- package/dist/components/file-upload.js +678 -0
- package/dist/components/info-popover.d.ts +23 -0
- package/dist/components/info-popover.js +158 -0
- package/dist/components/month-calendar-header.d.ts +65 -0
- package/dist/components/month-calendar-header.js +337 -0
- package/dist/components/month-grid.d.ts +59 -0
- package/dist/components/month-grid.js +301 -0
- package/dist/components/notification-bell.d.ts +50 -0
- package/dist/components/notification-bell.js +338 -0
- package/dist/components/notification-panel.d.ts +56 -0
- package/dist/components/notification-panel.js +281 -0
- package/dist/components/open-in-browser-cta.d.ts +22 -0
- package/dist/components/open-in-browser-cta.js +61 -0
- package/dist/components/presence-swap.d.ts +22 -0
- package/dist/components/presence-swap.js +162 -0
- package/dist/components/progress-ring.d.ts +20 -0
- package/dist/components/progress-ring.js +53 -0
- package/dist/components/pwa-update-control.d.ts +18 -0
- package/dist/components/pwa-update-control.js +119 -0
- package/dist/components/reaction-button.d.ts +26 -0
- package/dist/components/reaction-button.js +114 -0
- package/dist/components/rich-link-editor.d.ts +25 -0
- package/dist/components/rich-link-editor.js +320 -0
- package/dist/components/rich-markdown-content.d.ts +10 -0
- package/dist/components/rich-markdown-content.js +56 -0
- package/dist/components/rich-text-content.d.ts +19 -0
- package/dist/components/rich-text-content.js +26 -0
- package/dist/emoji-picker.d.ts +27 -0
- package/dist/emoji-picker.js +283 -0
- package/dist/hooks/use-horizontal-swipe.d.ts +23 -0
- package/dist/hooks/use-horizontal-swipe.js +80 -0
- package/dist/hooks/use-is-hydrated.d.ts +6 -0
- package/dist/hooks/use-is-hydrated.js +20 -0
- package/dist/hooks/use-minute-clock.d.ts +23 -0
- package/dist/hooks/use-minute-clock.js +147 -0
- package/dist/hooks/use-month-transition-direction.d.ts +21 -0
- package/dist/hooks/use-month-transition-direction.js +110 -0
- package/dist/hooks/use-rich-link-editor.d.ts +55 -0
- package/dist/hooks/use-rich-link-editor.js +515 -0
- package/dist/hooks/use-viewer-time-zone.d.ts +6 -0
- package/dist/hooks/use-viewer-time-zone.js +20 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +43 -0
- package/dist/lib/browser-clipboard.d.ts +13 -0
- package/dist/lib/browser-clipboard.js +48 -0
- package/dist/lib/browser-navigation.d.ts +21 -0
- package/dist/lib/browser-navigation.js +42 -0
- package/dist/lib/file-acceptance.d.ts +43 -0
- package/dist/lib/file-acceptance.js +107 -0
- package/dist/lib/format-file-size.d.ts +16 -0
- package/dist/lib/format-file-size.js +34 -0
- package/dist/lib/fuzzy-search.d.ts +17 -0
- package/dist/lib/fuzzy-search.js +183 -0
- package/dist/lib/horizontal-swipe.d.ts +23 -0
- package/dist/lib/horizontal-swipe.js +29 -0
- package/dist/lib/in-app-browser.d.ts +35 -0
- package/dist/lib/in-app-browser.js +80 -0
- package/dist/lib/month-grid.d.ts +42 -0
- package/dist/lib/month-grid.js +74 -0
- package/dist/lib/name-initials.d.ts +7 -0
- package/dist/lib/name-initials.js +11 -0
- package/dist/lib/rich-text/link-markdown-constants.d.ts +147 -0
- package/dist/lib/rich-text/link-markdown-constants.js +458 -0
- package/dist/lib/rich-text/link-markdown-types.d.ts +53 -0
- package/dist/lib/rich-text/link-markdown-types.js +1 -0
- package/dist/lib/rich-text/link-markdown.d.ts +193 -0
- package/dist/lib/rich-text/link-markdown.js +592 -0
- package/dist/lib/rich-text/rich-markdown.d.ts +41 -0
- package/dist/lib/rich-text/rich-markdown.js +105 -0
- package/dist/lib/rich-text/rich-text-editor-dom.d.ts +26 -0
- package/dist/lib/rich-text/rich-text-editor-dom.js +79 -0
- package/dist/motion/tokens.d.ts +17 -1
- package/dist/motion/tokens.js +17 -1
- package/dist/styles.css +1 -1
- package/package.json +211 -1
|
@@ -0,0 +1,56 @@
|
|
|
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;
|
|
@@ -0,0 +1,281 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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;
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Cross-fades between states of the same region, such as idle, loading and result. */
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
declare const PRESENCE_SWAP_ELEMENTS: {
|
|
4
|
+
readonly div: import("motion/react").ForwardRefComponent<HTMLDivElement, import("motion/react").HTMLMotionProps<"div">>;
|
|
5
|
+
readonly span: import("motion/react").ForwardRefComponent<HTMLSpanElement, import("motion/react").HTMLMotionProps<"span">>;
|
|
6
|
+
};
|
|
7
|
+
export interface PresenceSwapProps {
|
|
8
|
+
/** Changing the key plays the exit of the previous content and the entry of the new one. */
|
|
9
|
+
presenceKey: string;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
as?: keyof typeof PRESENCE_SWAP_ELEMENTS;
|
|
12
|
+
className?: string;
|
|
13
|
+
/** `wait` swaps sequentially; `popLayout` overlaps them for inline content. */
|
|
14
|
+
mode?: "wait" | "popLayout";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Swaps keyed content with a short lift-and-fade; the first render is not animated.
|
|
18
|
+
* @param props - Key identifying the current state and its content.
|
|
19
|
+
* @returns The animated content.
|
|
20
|
+
*/
|
|
21
|
+
export declare function PresenceSwap({ presenceKey, children, as, className, mode }: PresenceSwapProps): import("react").JSX.Element;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { c as _c } from "react/compiler-runtime";
|
|
3
|
+
import { AnimatePresence, motion, useIsPresent } from "motion/react";
|
|
4
|
+
import { usePrefersReducedMotion } from "../hooks/use-prefers-reduced-motion.js";
|
|
5
|
+
import { cn } from "../lib/utils.js";
|
|
6
|
+
import { MOTION_CONTENT_DISTANCE, MOTION_EASE, MOTION_TIMING } from "../motion/tokens.js";
|
|
7
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
8
|
+
const PRESENCE_SWAP_ELEMENTS = {
|
|
9
|
+
div: motion.div,
|
|
10
|
+
span: motion.span
|
|
11
|
+
};
|
|
12
|
+
const INSTANT_TRANSITION = { duration: 0 };
|
|
13
|
+
/**
|
|
14
|
+
* One keyed state of the swap. While it animates out it turns inert and hidden
|
|
15
|
+
* from assistive technology, so stale controls cannot be clicked or announced.
|
|
16
|
+
* @param props - Element type, class name and content.
|
|
17
|
+
* @returns The animated state.
|
|
18
|
+
*/
|
|
19
|
+
function PresenceSwapItem(t0) {
|
|
20
|
+
const $ = _c(20);
|
|
21
|
+
const { as, className, children } = t0;
|
|
22
|
+
const MotionElement = PRESENCE_SWAP_ELEMENTS[as];
|
|
23
|
+
const isPresent = useIsPresent();
|
|
24
|
+
const shouldReduceMotion = usePrefersReducedMotion();
|
|
25
|
+
const distance = shouldReduceMotion ? 0 : MOTION_CONTENT_DISTANCE;
|
|
26
|
+
const t1 = isPresent ? undefined : true;
|
|
27
|
+
const t2 = !isPresent || undefined;
|
|
28
|
+
let t3;
|
|
29
|
+
if ($[0] !== distance) {
|
|
30
|
+
t3 = {
|
|
31
|
+
opacity: 0,
|
|
32
|
+
y: distance
|
|
33
|
+
};
|
|
34
|
+
$[0] = distance;
|
|
35
|
+
$[1] = t3;
|
|
36
|
+
} else {
|
|
37
|
+
t3 = $[1];
|
|
38
|
+
}
|
|
39
|
+
let t4;
|
|
40
|
+
if ($[2] !== shouldReduceMotion) {
|
|
41
|
+
t4 = shouldReduceMotion ? INSTANT_TRANSITION : {
|
|
42
|
+
duration: MOTION_TIMING.enter,
|
|
43
|
+
ease: MOTION_EASE
|
|
44
|
+
};
|
|
45
|
+
$[2] = shouldReduceMotion;
|
|
46
|
+
$[3] = t4;
|
|
47
|
+
} else {
|
|
48
|
+
t4 = $[3];
|
|
49
|
+
}
|
|
50
|
+
let t5;
|
|
51
|
+
if ($[4] !== t4) {
|
|
52
|
+
t5 = {
|
|
53
|
+
opacity: 1,
|
|
54
|
+
y: 0,
|
|
55
|
+
transition: t4
|
|
56
|
+
};
|
|
57
|
+
$[4] = t4;
|
|
58
|
+
$[5] = t5;
|
|
59
|
+
} else {
|
|
60
|
+
t5 = $[5];
|
|
61
|
+
}
|
|
62
|
+
const t6 = -distance;
|
|
63
|
+
let t7;
|
|
64
|
+
if ($[6] !== shouldReduceMotion) {
|
|
65
|
+
t7 = shouldReduceMotion ? INSTANT_TRANSITION : {
|
|
66
|
+
duration: MOTION_TIMING.exit,
|
|
67
|
+
ease: MOTION_EASE
|
|
68
|
+
};
|
|
69
|
+
$[6] = shouldReduceMotion;
|
|
70
|
+
$[7] = t7;
|
|
71
|
+
} else {
|
|
72
|
+
t7 = $[7];
|
|
73
|
+
}
|
|
74
|
+
let t8;
|
|
75
|
+
if ($[8] !== t6 || $[9] !== t7) {
|
|
76
|
+
t8 = {
|
|
77
|
+
opacity: 0,
|
|
78
|
+
y: t6,
|
|
79
|
+
transition: t7
|
|
80
|
+
};
|
|
81
|
+
$[8] = t6;
|
|
82
|
+
$[9] = t7;
|
|
83
|
+
$[10] = t8;
|
|
84
|
+
} else {
|
|
85
|
+
t8 = $[10];
|
|
86
|
+
}
|
|
87
|
+
let t9;
|
|
88
|
+
if ($[11] !== MotionElement || $[12] !== children || $[13] !== className || $[14] !== t1 || $[15] !== t2 || $[16] !== t3 || $[17] !== t5 || $[18] !== t8) {
|
|
89
|
+
t9 = /* @__PURE__ */ _jsx(MotionElement, {
|
|
90
|
+
"data-slot": "presence-swap",
|
|
91
|
+
"aria-hidden": t1,
|
|
92
|
+
inert: t2,
|
|
93
|
+
className,
|
|
94
|
+
initial: t3,
|
|
95
|
+
animate: t5,
|
|
96
|
+
exit: t8,
|
|
97
|
+
children
|
|
98
|
+
});
|
|
99
|
+
$[11] = MotionElement;
|
|
100
|
+
$[12] = children;
|
|
101
|
+
$[13] = className;
|
|
102
|
+
$[14] = t1;
|
|
103
|
+
$[15] = t2;
|
|
104
|
+
$[16] = t3;
|
|
105
|
+
$[17] = t5;
|
|
106
|
+
$[18] = t8;
|
|
107
|
+
$[19] = t9;
|
|
108
|
+
} else {
|
|
109
|
+
t9 = $[19];
|
|
110
|
+
}
|
|
111
|
+
return t9;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Swaps keyed content with a short lift-and-fade; the first render is not animated.
|
|
115
|
+
* @param props - Key identifying the current state and its content.
|
|
116
|
+
* @returns The animated content.
|
|
117
|
+
*/
|
|
118
|
+
export function PresenceSwap(t0) {
|
|
119
|
+
const $ = _c(11);
|
|
120
|
+
const { presenceKey, children, as: t1, className, mode: t2 } = t0;
|
|
121
|
+
const as = t1 === undefined ? "div" : t1;
|
|
122
|
+
const mode = t2 === undefined ? "wait" : t2;
|
|
123
|
+
const t3 = as === "span" && "inline-flex items-center";
|
|
124
|
+
let t4;
|
|
125
|
+
if ($[0] !== className || $[1] !== t3) {
|
|
126
|
+
t4 = cn("min-w-0", t3, className);
|
|
127
|
+
$[0] = className;
|
|
128
|
+
$[1] = t3;
|
|
129
|
+
$[2] = t4;
|
|
130
|
+
} else {
|
|
131
|
+
t4 = $[2];
|
|
132
|
+
}
|
|
133
|
+
let t5;
|
|
134
|
+
if ($[3] !== as || $[4] !== children || $[5] !== presenceKey || $[6] !== t4) {
|
|
135
|
+
t5 = /* @__PURE__ */ _jsx(PresenceSwapItem, {
|
|
136
|
+
as,
|
|
137
|
+
className: t4,
|
|
138
|
+
children
|
|
139
|
+
}, presenceKey);
|
|
140
|
+
$[3] = as;
|
|
141
|
+
$[4] = children;
|
|
142
|
+
$[5] = presenceKey;
|
|
143
|
+
$[6] = t4;
|
|
144
|
+
$[7] = t5;
|
|
145
|
+
} else {
|
|
146
|
+
t5 = $[7];
|
|
147
|
+
}
|
|
148
|
+
let t6;
|
|
149
|
+
if ($[8] !== mode || $[9] !== t5) {
|
|
150
|
+
t6 = /* @__PURE__ */ _jsx(AnimatePresence, {
|
|
151
|
+
mode,
|
|
152
|
+
initial: false,
|
|
153
|
+
children: t5
|
|
154
|
+
});
|
|
155
|
+
$[8] = mode;
|
|
156
|
+
$[9] = t5;
|
|
157
|
+
$[10] = t6;
|
|
158
|
+
} else {
|
|
159
|
+
t6 = $[10];
|
|
160
|
+
}
|
|
161
|
+
return t6;
|
|
162
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Compact circular progress indicator whose arc inherits the surrounding text color. */
|
|
2
|
+
import type { ComponentProps } from "react";
|
|
3
|
+
export interface ProgressRingProps extends Omit<ComponentProps<"svg">, "children"> {
|
|
4
|
+
/** Completion ratio in the `[0, 1]` range; values outside are clamped. */
|
|
5
|
+
fraction: number;
|
|
6
|
+
/** Rendered width and height in pixels. */
|
|
7
|
+
size?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Accessible name. Without it the ring is decorative (`aria-hidden`), which fits when an
|
|
10
|
+
* adjacent label such as `3 / 4` already conveys the value.
|
|
11
|
+
*/
|
|
12
|
+
label?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Draws a ring whose arc covers `fraction` of the circle. The arc uses `currentColor`, so the
|
|
16
|
+
* parent's text color (for example success or warning) styles it without extra props.
|
|
17
|
+
* @param props - Fraction, size, optional accessible name and SVG attributes.
|
|
18
|
+
* @returns The progress ring.
|
|
19
|
+
*/
|
|
20
|
+
export declare function ProgressRing({ fraction, size, label, className, ...svgProps }: ProgressRingProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { cn } from "../lib/utils.js";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
const PROGRESS_RING_VIEWBOX_SIZE = 18;
|
|
4
|
+
const PROGRESS_RING_CENTER = PROGRESS_RING_VIEWBOX_SIZE / 2;
|
|
5
|
+
const PROGRESS_RING_RADIUS = 7;
|
|
6
|
+
const PROGRESS_RING_STROKE_WIDTH = 2.5;
|
|
7
|
+
const PROGRESS_RING_CIRCUMFERENCE = 2 * Math.PI * PROGRESS_RING_RADIUS;
|
|
8
|
+
const DEFAULT_PROGRESS_RING_SIZE_PX = 18;
|
|
9
|
+
/**
|
|
10
|
+
* Draws a ring whose arc covers `fraction` of the circle. The arc uses `currentColor`, so the
|
|
11
|
+
* parent's text color (for example success or warning) styles it without extra props.
|
|
12
|
+
* @param props - Fraction, size, optional accessible name and SVG attributes.
|
|
13
|
+
* @returns The progress ring.
|
|
14
|
+
*/
|
|
15
|
+
export function ProgressRing({ fraction, size = DEFAULT_PROGRESS_RING_SIZE_PX, label, className, ...svgProps }) {
|
|
16
|
+
const clampedFraction = Math.min(Math.max(Number.isFinite(fraction) ? fraction : 0, 0), 1);
|
|
17
|
+
const dashOffset = PROGRESS_RING_CIRCUMFERENCE * (1 - clampedFraction);
|
|
18
|
+
const accessibilityProps = label ? {
|
|
19
|
+
role: "progressbar",
|
|
20
|
+
"aria-label": label,
|
|
21
|
+
"aria-valuemin": 0,
|
|
22
|
+
"aria-valuemax": 100,
|
|
23
|
+
"aria-valuenow": Math.round(clampedFraction * 100)
|
|
24
|
+
} : { "aria-hidden": true };
|
|
25
|
+
return /* @__PURE__ */ _jsxs("svg", {
|
|
26
|
+
"data-slot": "progress-ring",
|
|
27
|
+
...accessibilityProps,
|
|
28
|
+
...svgProps,
|
|
29
|
+
className: cn("shrink-0 -rotate-90", className),
|
|
30
|
+
height: size,
|
|
31
|
+
viewBox: `0 0 ${PROGRESS_RING_VIEWBOX_SIZE} ${PROGRESS_RING_VIEWBOX_SIZE}`,
|
|
32
|
+
width: size,
|
|
33
|
+
children: [/* @__PURE__ */ _jsx("circle", {
|
|
34
|
+
className: "stroke-border/70",
|
|
35
|
+
cx: PROGRESS_RING_CENTER,
|
|
36
|
+
cy: PROGRESS_RING_CENTER,
|
|
37
|
+
fill: "none",
|
|
38
|
+
r: PROGRESS_RING_RADIUS,
|
|
39
|
+
strokeWidth: PROGRESS_RING_STROKE_WIDTH
|
|
40
|
+
}), clampedFraction > 0 ? /* @__PURE__ */ _jsx("circle", {
|
|
41
|
+
"data-slot": "progress-ring-indicator",
|
|
42
|
+
cx: PROGRESS_RING_CENTER,
|
|
43
|
+
cy: PROGRESS_RING_CENTER,
|
|
44
|
+
fill: "none",
|
|
45
|
+
r: PROGRESS_RING_RADIUS,
|
|
46
|
+
stroke: "currentColor",
|
|
47
|
+
strokeDasharray: PROGRESS_RING_CIRCUMFERENCE,
|
|
48
|
+
strokeDashoffset: dashOffset,
|
|
49
|
+
strokeLinecap: "round",
|
|
50
|
+
strokeWidth: PROGRESS_RING_STROKE_WIDTH
|
|
51
|
+
}) : null]
|
|
52
|
+
});
|
|
53
|
+
}
|