@2londres/shareable-preview 1.0.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/README.md +72 -0
- package/dist/index.d.mts +159 -0
- package/dist/index.d.ts +159 -0
- package/dist/index.js +1184 -0
- package/dist/index.mjs +1155 -0
- package/package.json +42 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1155 @@
|
|
|
1
|
+
import * as React5 from 'react';
|
|
2
|
+
import React5__default, { forwardRef, useState, useMemo, useEffect } from 'react';
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
6
|
+
import { cva } from 'class-variance-authority';
|
|
7
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
|
+
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
9
|
+
import { X, CheckCircle, ChevronRight, Eye, Bug, Code, Copy, Share2, Download, ChevronUp, ChevronDown, XCircle, Trash2, Pencil } from 'lucide-react';
|
|
10
|
+
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
11
|
+
|
|
12
|
+
// src/ShareablePreview.tsx
|
|
13
|
+
function cn(...inputs) {
|
|
14
|
+
return twMerge(clsx(inputs));
|
|
15
|
+
}
|
|
16
|
+
var buttonVariants = cva(
|
|
17
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
|
18
|
+
{
|
|
19
|
+
variants: {
|
|
20
|
+
variant: {
|
|
21
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
22
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
23
|
+
success: "bg-green-600 text-destructive-foreground hover:bg-green-900/90",
|
|
24
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
25
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
26
|
+
warning: "bg-yellow-500 text-white hover:bg-yellow-500/80",
|
|
27
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
28
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
29
|
+
},
|
|
30
|
+
size: {
|
|
31
|
+
default: "h-10 px-4 py-2",
|
|
32
|
+
sm: "h-9 rounded-md px-3",
|
|
33
|
+
lg: "h-11 rounded-md px-8",
|
|
34
|
+
icon: "h-10 w-10"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
defaultVariants: {
|
|
38
|
+
variant: "default",
|
|
39
|
+
size: "default"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
var Button = React5.forwardRef(
|
|
44
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
45
|
+
const Comp = asChild ? Slot : "button";
|
|
46
|
+
return /* @__PURE__ */ jsx(
|
|
47
|
+
Comp,
|
|
48
|
+
{
|
|
49
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
50
|
+
ref,
|
|
51
|
+
...props
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
Button.displayName = "Button";
|
|
57
|
+
var Card = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
58
|
+
"div",
|
|
59
|
+
{
|
|
60
|
+
ref,
|
|
61
|
+
className: cn(
|
|
62
|
+
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
|
63
|
+
className
|
|
64
|
+
),
|
|
65
|
+
...props
|
|
66
|
+
}
|
|
67
|
+
));
|
|
68
|
+
Card.displayName = "Card";
|
|
69
|
+
var Dialog = DialogPrimitive.Root;
|
|
70
|
+
var DialogTrigger = DialogPrimitive.Trigger;
|
|
71
|
+
var DialogPortal = DialogPrimitive.Portal;
|
|
72
|
+
var DialogOverlay = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
73
|
+
DialogPrimitive.Overlay,
|
|
74
|
+
{
|
|
75
|
+
ref,
|
|
76
|
+
className: cn(
|
|
77
|
+
"fixed inset-0 z-50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 bg-black/70 backdrop-blur-sm",
|
|
78
|
+
className
|
|
79
|
+
),
|
|
80
|
+
...props
|
|
81
|
+
}
|
|
82
|
+
));
|
|
83
|
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName ?? "DialogOverlay";
|
|
84
|
+
var DialogContent = React5.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs(DialogPortal, { children: [
|
|
85
|
+
/* @__PURE__ */ jsx(DialogOverlay, {}),
|
|
86
|
+
/* @__PURE__ */ jsxs(
|
|
87
|
+
DialogPrimitive.Content,
|
|
88
|
+
{
|
|
89
|
+
ref,
|
|
90
|
+
className: cn(
|
|
91
|
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
|
92
|
+
className
|
|
93
|
+
),
|
|
94
|
+
...props,
|
|
95
|
+
children: [
|
|
96
|
+
children,
|
|
97
|
+
/* @__PURE__ */ jsxs(DialogPrimitive.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground", children: [
|
|
98
|
+
/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }),
|
|
99
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
|
|
100
|
+
] })
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
] }));
|
|
105
|
+
DialogContent.displayName = DialogPrimitive.Content.displayName ?? "DialogContent";
|
|
106
|
+
var Tabs = TabsPrimitive.Root;
|
|
107
|
+
var TabsList = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
108
|
+
TabsPrimitive.List,
|
|
109
|
+
{
|
|
110
|
+
ref,
|
|
111
|
+
className: cn(
|
|
112
|
+
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
|
113
|
+
className
|
|
114
|
+
),
|
|
115
|
+
...props
|
|
116
|
+
}
|
|
117
|
+
));
|
|
118
|
+
TabsList.displayName = TabsPrimitive.List.displayName;
|
|
119
|
+
var TabsTrigger = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
120
|
+
TabsPrimitive.Trigger,
|
|
121
|
+
{
|
|
122
|
+
ref,
|
|
123
|
+
className: cn(
|
|
124
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
|
125
|
+
className
|
|
126
|
+
),
|
|
127
|
+
...props
|
|
128
|
+
}
|
|
129
|
+
));
|
|
130
|
+
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
|
131
|
+
var TabsContent = React5.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
132
|
+
TabsPrimitive.Content,
|
|
133
|
+
{
|
|
134
|
+
ref,
|
|
135
|
+
className: cn(
|
|
136
|
+
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
137
|
+
className
|
|
138
|
+
),
|
|
139
|
+
...props
|
|
140
|
+
}
|
|
141
|
+
));
|
|
142
|
+
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
|
143
|
+
var gradientVariants = {
|
|
144
|
+
green: {
|
|
145
|
+
base: "from-green-600 to-green-700",
|
|
146
|
+
hover: "hover:from-green-700 hover:to-green-800",
|
|
147
|
+
shadow: "hover:shadow-green-400/50"
|
|
148
|
+
},
|
|
149
|
+
blue: {
|
|
150
|
+
base: "from-blue-600 to-blue-700",
|
|
151
|
+
hover: "hover:from-blue-700 hover:to-blue-800",
|
|
152
|
+
shadow: "hover:shadow-blue-400/50"
|
|
153
|
+
},
|
|
154
|
+
purple: {
|
|
155
|
+
base: "from-purple-600 to-purple-700",
|
|
156
|
+
hover: "hover:from-purple-700 hover:to-purple-800",
|
|
157
|
+
shadow: "hover:shadow-purple-400/50"
|
|
158
|
+
},
|
|
159
|
+
orange: {
|
|
160
|
+
base: "from-orange-600 to-orange-700",
|
|
161
|
+
hover: "hover:from-orange-700 hover:to-orange-800",
|
|
162
|
+
shadow: "hover:shadow-orange-400/50"
|
|
163
|
+
},
|
|
164
|
+
red: {
|
|
165
|
+
base: "from-red-600 to-red-700",
|
|
166
|
+
hover: "hover:from-red-700 hover:to-red-800",
|
|
167
|
+
shadow: "hover:shadow-red-400/50"
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
var ValidationButton = forwardRef(
|
|
171
|
+
({
|
|
172
|
+
children,
|
|
173
|
+
className,
|
|
174
|
+
withPulse = false,
|
|
175
|
+
withIndicator = true,
|
|
176
|
+
variant = "green",
|
|
177
|
+
icon: Icon = CheckCircle,
|
|
178
|
+
...props
|
|
179
|
+
}, ref) => {
|
|
180
|
+
const gradients = gradientVariants[variant];
|
|
181
|
+
return /* @__PURE__ */ jsxs(
|
|
182
|
+
Button,
|
|
183
|
+
{
|
|
184
|
+
ref,
|
|
185
|
+
size: "lg",
|
|
186
|
+
className: cn(
|
|
187
|
+
"px-8 py-4 font-bold text-white border-0 shadow-xl transition-all duration-300 relative",
|
|
188
|
+
`bg-gradient-to-r ${gradients.base}`,
|
|
189
|
+
`${gradients.hover} hover:scale-105 hover:shadow-2xl ${gradients.shadow}`,
|
|
190
|
+
withPulse && "animate-pulse",
|
|
191
|
+
"focus:animate-none hover:animate-none",
|
|
192
|
+
className
|
|
193
|
+
),
|
|
194
|
+
...props,
|
|
195
|
+
children: [
|
|
196
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-center items-center mr-3 w-6 h-6 rounded-full bg-white/20", children: /* @__PURE__ */ jsx(Icon, { className: "w-4 h-4" }) }),
|
|
197
|
+
children,
|
|
198
|
+
withIndicator && /* @__PURE__ */ jsx("div", { className: "absolute -top-1 -right-1 w-3 h-3 bg-yellow-400 rounded-full animate-ping" })
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
ValidationButton.displayName = "ValidationButton";
|
|
205
|
+
function getValueType(value) {
|
|
206
|
+
if (value === null) return "null";
|
|
207
|
+
if (Array.isArray(value)) return "array";
|
|
208
|
+
if (typeof value === "object") return "object";
|
|
209
|
+
if (typeof value === "boolean") return "boolean";
|
|
210
|
+
if (typeof value === "number") return "number";
|
|
211
|
+
if (typeof value === "string") return "string";
|
|
212
|
+
return typeof value;
|
|
213
|
+
}
|
|
214
|
+
function isExpandable(value) {
|
|
215
|
+
if (value === null || value === void 0) return false;
|
|
216
|
+
if (typeof value === "object")
|
|
217
|
+
return Object.keys(value).length > 0;
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
function formatPrimitive(value) {
|
|
221
|
+
if (value === null) return "null";
|
|
222
|
+
if (typeof value === "string") return `"${value}"`;
|
|
223
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
224
|
+
if (typeof value === "number") return String(value);
|
|
225
|
+
return String(value);
|
|
226
|
+
}
|
|
227
|
+
function DataTreeNode({
|
|
228
|
+
nodeKey,
|
|
229
|
+
value,
|
|
230
|
+
level = 0,
|
|
231
|
+
maxLevel = 20,
|
|
232
|
+
maxDepthLabel = "... (max depth)"
|
|
233
|
+
}) {
|
|
234
|
+
const [isExpanded, setIsExpanded] = useState(level < 2);
|
|
235
|
+
const expandable = isExpandable(value);
|
|
236
|
+
const valueType = getValueType(value);
|
|
237
|
+
const isArray = Array.isArray(value);
|
|
238
|
+
const entries = expandable ? isArray ? value.map((v, i) => [String(i), v]) : value !== null && value !== void 0 && typeof value === "object" ? Object.entries(value) : [] : [];
|
|
239
|
+
if (level > maxLevel) {
|
|
240
|
+
return /* @__PURE__ */ jsxs("div", { className: "text-xs italic text-muted-foreground", children: [
|
|
241
|
+
nodeKey,
|
|
242
|
+
": ",
|
|
243
|
+
maxDepthLabel
|
|
244
|
+
] });
|
|
245
|
+
}
|
|
246
|
+
return /* @__PURE__ */ jsxs("div", { className: "font-mono text-sm", children: [
|
|
247
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-1 items-start", children: [
|
|
248
|
+
expandable ? /* @__PURE__ */ jsx(
|
|
249
|
+
"button",
|
|
250
|
+
{
|
|
251
|
+
onClick: () => setIsExpanded(!isExpanded),
|
|
252
|
+
className: "flex-shrink-0 p-0 transition-colors text-muted-foreground hover:text-foreground",
|
|
253
|
+
"aria-label": isExpanded ? "Collapse" : "Expand",
|
|
254
|
+
children: /* @__PURE__ */ jsx(
|
|
255
|
+
ChevronRight,
|
|
256
|
+
{
|
|
257
|
+
className: cn(
|
|
258
|
+
"h-4 w-4 transition-transform",
|
|
259
|
+
isExpanded && "rotate-90"
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
) : /* @__PURE__ */ jsx("div", { className: "w-4" }),
|
|
265
|
+
/* @__PURE__ */ jsxs("span", { className: "flex-shrink-0 font-semibold text-primary", children: [
|
|
266
|
+
nodeKey,
|
|
267
|
+
":"
|
|
268
|
+
] }),
|
|
269
|
+
!expandable && /* @__PURE__ */ jsx(
|
|
270
|
+
"span",
|
|
271
|
+
{
|
|
272
|
+
className: cn(
|
|
273
|
+
"text-foreground",
|
|
274
|
+
valueType === "string" && "text-green-600"
|
|
275
|
+
),
|
|
276
|
+
children: formatPrimitive(value)
|
|
277
|
+
}
|
|
278
|
+
),
|
|
279
|
+
expandable && !isExpanded && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: isArray ? `[${entries.length}]` : `{${entries.length}}` })
|
|
280
|
+
] }),
|
|
281
|
+
expandable && isExpanded && /* @__PURE__ */ jsx("div", { className: "ml-4 border-l border-border/30 pl-2 mt-1 space-y-0.5", children: entries.map(([key, val]) => /* @__PURE__ */ jsx(
|
|
282
|
+
DataTreeNode,
|
|
283
|
+
{
|
|
284
|
+
nodeKey: key,
|
|
285
|
+
value: val,
|
|
286
|
+
level: level + 1,
|
|
287
|
+
maxLevel,
|
|
288
|
+
maxDepthLabel
|
|
289
|
+
},
|
|
290
|
+
`${nodeKey}-${key}`
|
|
291
|
+
)) })
|
|
292
|
+
] });
|
|
293
|
+
}
|
|
294
|
+
function DebugView({
|
|
295
|
+
data,
|
|
296
|
+
className,
|
|
297
|
+
maxDepthLabel = "... (max depth)"
|
|
298
|
+
}) {
|
|
299
|
+
if (data === null || data === void 0) {
|
|
300
|
+
return /* @__PURE__ */ jsx("div", { className: cn("overflow-auto p-4 font-mono text-xs", className), children: /* @__PURE__ */ jsx("div", { className: "text-muted-foreground", children: "null / undefined" }) });
|
|
301
|
+
}
|
|
302
|
+
const isArray = Array.isArray(data);
|
|
303
|
+
const entries = isArray ? data.map((v, i) => [String(i), v]) : typeof data === "object" ? Object.entries(data) : [];
|
|
304
|
+
return /* @__PURE__ */ jsx(
|
|
305
|
+
"div",
|
|
306
|
+
{
|
|
307
|
+
className: cn(
|
|
308
|
+
"overflow-auto p-4 font-mono text-xs rounded-lg border bg-muted border-border/30",
|
|
309
|
+
className
|
|
310
|
+
),
|
|
311
|
+
children: /* @__PURE__ */ jsx("div", { className: "space-y-1", children: entries.map(([key, value]) => /* @__PURE__ */ jsx(
|
|
312
|
+
DataTreeNode,
|
|
313
|
+
{
|
|
314
|
+
nodeKey: key,
|
|
315
|
+
value,
|
|
316
|
+
level: 0,
|
|
317
|
+
maxDepthLabel
|
|
318
|
+
},
|
|
319
|
+
key
|
|
320
|
+
)) })
|
|
321
|
+
}
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// src/utils/filterDataByKeys.ts
|
|
326
|
+
var isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
327
|
+
var pathMatchesIncluded = (currentPath, paths) => paths.has(currentPath) || Array.from(paths).some((p) => p.startsWith(`${currentPath}.`));
|
|
328
|
+
var pathMatchesExcluded = (currentPath, paths) => paths.has(currentPath) || Array.from(paths).some(
|
|
329
|
+
(p) => currentPath.startsWith(`${p}.`) || currentPath === p
|
|
330
|
+
);
|
|
331
|
+
function filterObjectRecursive(obj, options, currentPath, depth) {
|
|
332
|
+
const {
|
|
333
|
+
includedKeys,
|
|
334
|
+
excludedKeys,
|
|
335
|
+
includedNestedKeys,
|
|
336
|
+
excludedNestedKeys,
|
|
337
|
+
maxDepth = 20
|
|
338
|
+
} = options;
|
|
339
|
+
const incSet = includedKeys?.length ? new Set(includedKeys) : null;
|
|
340
|
+
const excSet = excludedKeys?.length ? new Set(excludedKeys) : null;
|
|
341
|
+
const incNestedSet = includedNestedKeys?.length ? new Set(includedNestedKeys) : null;
|
|
342
|
+
const excNestedSet = excludedNestedKeys?.length ? new Set(excludedNestedKeys) : null;
|
|
343
|
+
const shouldIncludeTopLevel = (key) => {
|
|
344
|
+
if (incSet) return incSet.has(key);
|
|
345
|
+
if (excSet && excSet.has(key)) return false;
|
|
346
|
+
return true;
|
|
347
|
+
};
|
|
348
|
+
const shouldIncludeNested = (path) => {
|
|
349
|
+
if (incNestedSet && incNestedSet.size > 0)
|
|
350
|
+
return pathMatchesIncluded(path, incNestedSet);
|
|
351
|
+
if (excNestedSet && pathMatchesExcluded(path, excNestedSet)) return false;
|
|
352
|
+
return true;
|
|
353
|
+
};
|
|
354
|
+
const result = {};
|
|
355
|
+
const entries = Object.entries(obj);
|
|
356
|
+
const isTopLevel = !currentPath;
|
|
357
|
+
for (const [key, value] of entries) {
|
|
358
|
+
const childPath = currentPath ? `${currentPath}.${key}` : key;
|
|
359
|
+
if (isTopLevel) {
|
|
360
|
+
if (incNestedSet?.size ? !shouldIncludeNested(childPath) : !shouldIncludeTopLevel(key))
|
|
361
|
+
continue;
|
|
362
|
+
} else if (!shouldIncludeNested(childPath)) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (depth >= maxDepth) {
|
|
366
|
+
result[key] = value;
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (Array.isArray(value)) {
|
|
370
|
+
result[key] = value.map(
|
|
371
|
+
(item, i) => isPlainObject(item) ? filterObjectRecursive(item, options, `${childPath}[${i}]`, depth + 1) : item
|
|
372
|
+
);
|
|
373
|
+
} else if (isPlainObject(value)) {
|
|
374
|
+
result[key] = filterObjectRecursive(value, options, childPath, depth + 1);
|
|
375
|
+
} else {
|
|
376
|
+
result[key] = value;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return result;
|
|
380
|
+
}
|
|
381
|
+
function filterDataByKeys(data, options) {
|
|
382
|
+
if (!options || !options.includedKeys?.length && !options.excludedKeys?.length && !options.includedNestedKeys?.length && !options.excludedNestedKeys?.length) {
|
|
383
|
+
return data;
|
|
384
|
+
}
|
|
385
|
+
if (Array.isArray(data)) {
|
|
386
|
+
return data.map(
|
|
387
|
+
(item, i) => isPlainObject(item) ? filterObjectRecursive(
|
|
388
|
+
item,
|
|
389
|
+
options,
|
|
390
|
+
`[${i}]`,
|
|
391
|
+
0
|
|
392
|
+
) : item
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
if (isPlainObject(data)) {
|
|
396
|
+
return filterObjectRecursive(
|
|
397
|
+
data,
|
|
398
|
+
options,
|
|
399
|
+
"",
|
|
400
|
+
0
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
return data;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// src/i18n/defaultLabels.ts
|
|
407
|
+
var DEFAULT_LABELS = {
|
|
408
|
+
yes: "Yes",
|
|
409
|
+
no: "No",
|
|
410
|
+
shareablePreviewCopySuccess: "Data copied to the clipboard.",
|
|
411
|
+
shareablePreviewShareLinkCopied: "The share link has been copied.",
|
|
412
|
+
shareablePreviewShareUnavailable: "Sharing is unavailable for this preview.",
|
|
413
|
+
shareablePreviewDownloadSuccess: "Data has been downloaded.",
|
|
414
|
+
shareablePreviewClipboardError: "Unable to copy data, please try again.",
|
|
415
|
+
shareablePreviewClipboardUnsupported: "Clipboard copy is not supported on this device.",
|
|
416
|
+
shareablePreviewDownloadUnavailable: "Download is unavailable on this device.",
|
|
417
|
+
shareablePreviewDefaultTitle: "Preview",
|
|
418
|
+
shareablePreviewOpen: "Open preview",
|
|
419
|
+
shareablePreviewDebugMode: "Debug",
|
|
420
|
+
shareablePreviewUserMode: "User",
|
|
421
|
+
shareablePreviewSwitchToUser: "Switch to user view",
|
|
422
|
+
shareablePreviewSwitchToDebug: "Switch to debug view",
|
|
423
|
+
shareablePreviewCopyData: "Copy data",
|
|
424
|
+
shareablePreviewCopyShareLink: "Copy share link",
|
|
425
|
+
shareablePreviewDownloadData: "Download data",
|
|
426
|
+
shareablePreviewCollapseContent: "Collapse content",
|
|
427
|
+
shareablePreviewExpandContent: "Expand content",
|
|
428
|
+
shareablePreviewDataTypeArray: "Array",
|
|
429
|
+
shareablePreviewDataTypeObject: "Object",
|
|
430
|
+
shareablePreviewDataTypeHtml: "HTML",
|
|
431
|
+
shareablePreviewDataTypeText: "Text",
|
|
432
|
+
shareablePreviewDataTypeString: "String",
|
|
433
|
+
shareablePreviewDataTypeNumber: "Number",
|
|
434
|
+
shareablePreviewDataTypeBoolean: "Boolean",
|
|
435
|
+
shareablePreviewDataTypeUndefined: "Undefined",
|
|
436
|
+
shareablePreviewDataTypeFunction: "Function",
|
|
437
|
+
shareablePreviewDataTypeSymbol: "Symbol",
|
|
438
|
+
shareablePreviewDataTypeBigint: "BigInt",
|
|
439
|
+
shareablePreviewEmptyList: "Empty list",
|
|
440
|
+
shareablePreviewEmptyObject: "Empty object",
|
|
441
|
+
shareablePreviewMaxDepth: "Maximum depth reached",
|
|
442
|
+
shareablePreviewItemLabel: "Item {{index}}",
|
|
443
|
+
shareablePreviewEntriesCount: "{{count}} field(s)",
|
|
444
|
+
shareablePreviewArrayLabel: "{{count}} item(s)",
|
|
445
|
+
shareablePreviewNestedObjectLabel: "Nested content",
|
|
446
|
+
shareablePreviewNoValue: "No value",
|
|
447
|
+
shareablePreviewGeneralInfo: "General information"
|
|
448
|
+
};
|
|
449
|
+
function interpolate(template, params) {
|
|
450
|
+
return Object.entries(params).reduce(
|
|
451
|
+
(acc, [key, value]) => acc.replace(`{{${key}}}`, String(value)),
|
|
452
|
+
template
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
var MAX_RENDER_DEPTH = 6;
|
|
456
|
+
var isPlainObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
457
|
+
var isPrimitive = (value) => ["string", "number", "boolean"].includes(typeof value);
|
|
458
|
+
var formatKey = (key) => key.replace(/[_-]/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/\s+/g, " ").trim();
|
|
459
|
+
var stringifyValue = (value) => {
|
|
460
|
+
if (typeof value === "string") return value;
|
|
461
|
+
try {
|
|
462
|
+
return JSON.stringify(value, null, 2);
|
|
463
|
+
} catch {
|
|
464
|
+
return String(value);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
function UserView({
|
|
468
|
+
data,
|
|
469
|
+
className,
|
|
470
|
+
labels: labelsOverride = {},
|
|
471
|
+
includedKeys,
|
|
472
|
+
excludedKeys,
|
|
473
|
+
includedNestedKeys,
|
|
474
|
+
excludedNestedKeys,
|
|
475
|
+
maxDepth
|
|
476
|
+
}) {
|
|
477
|
+
const displayData = useMemo(() => {
|
|
478
|
+
const opts = {
|
|
479
|
+
includedKeys,
|
|
480
|
+
excludedKeys,
|
|
481
|
+
includedNestedKeys,
|
|
482
|
+
excludedNestedKeys,
|
|
483
|
+
maxDepth
|
|
484
|
+
};
|
|
485
|
+
return filterDataByKeys(data, opts);
|
|
486
|
+
}, [
|
|
487
|
+
data,
|
|
488
|
+
includedKeys,
|
|
489
|
+
excludedKeys,
|
|
490
|
+
includedNestedKeys,
|
|
491
|
+
excludedNestedKeys,
|
|
492
|
+
maxDepth
|
|
493
|
+
]);
|
|
494
|
+
const labels = {
|
|
495
|
+
yes: labelsOverride.yes ?? "Yes",
|
|
496
|
+
no: labelsOverride.no ?? "No",
|
|
497
|
+
shareablePreviewEmptyList: labelsOverride.shareablePreviewEmptyList ?? "Empty list",
|
|
498
|
+
shareablePreviewEmptyObject: labelsOverride.shareablePreviewEmptyObject ?? "Empty object",
|
|
499
|
+
shareablePreviewNoValue: labelsOverride.shareablePreviewNoValue ?? "No value",
|
|
500
|
+
shareablePreviewItemLabel: labelsOverride.shareablePreviewItemLabel ?? "Item {{index}}",
|
|
501
|
+
shareablePreviewEntriesCount: labelsOverride.shareablePreviewEntriesCount ?? "{{count}} field(s)",
|
|
502
|
+
shareablePreviewArrayLabel: labelsOverride.shareablePreviewArrayLabel ?? "{{count}} item(s)",
|
|
503
|
+
shareablePreviewNestedObjectLabel: labelsOverride.shareablePreviewNestedObjectLabel ?? "Nested content"
|
|
504
|
+
};
|
|
505
|
+
const t = (key, params) => params ? interpolate(labels[key] ?? key, params) : labels[key] ?? key;
|
|
506
|
+
const renderPrimitive = (value) => {
|
|
507
|
+
if (typeof value === "boolean") {
|
|
508
|
+
return /* @__PURE__ */ jsx(
|
|
509
|
+
"span",
|
|
510
|
+
{
|
|
511
|
+
className: cn(
|
|
512
|
+
"inline-flex items-center px-3 py-1 text-xs font-semibold tracking-wide uppercase rounded-full",
|
|
513
|
+
value ? "text-emerald-700 bg-emerald-100 dark:bg-emerald-400/20 dark:text-emerald-100" : "bg-slate-100 text-slate-700 dark:bg-slate-400/20 dark:text-slate-100"
|
|
514
|
+
),
|
|
515
|
+
children: t(value ? "yes" : "no")
|
|
516
|
+
}
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
if (typeof value === "number") {
|
|
520
|
+
const safeNum = value == null || Number.isNaN(value) ? "\u2014" : value.toLocaleString();
|
|
521
|
+
return /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold tracking-tight text-foreground", children: safeNum });
|
|
522
|
+
}
|
|
523
|
+
return /* @__PURE__ */ jsx("p", { className: "text-sm leading-relaxed whitespace-pre-wrap break-words text-foreground", children: value });
|
|
524
|
+
};
|
|
525
|
+
function renderArray(value, depth, parentKey, inline = false) {
|
|
526
|
+
if (value.length === 0) {
|
|
527
|
+
return /* @__PURE__ */ jsx("span", { className: "text-sm italic text-muted-foreground", children: t("shareablePreviewEmptyList") });
|
|
528
|
+
}
|
|
529
|
+
if (value.every((item) => isPrimitive(item))) {
|
|
530
|
+
return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: value.map((item, index) => /* @__PURE__ */ jsx(
|
|
531
|
+
"span",
|
|
532
|
+
{
|
|
533
|
+
className: "inline-flex items-center gap-2 rounded-full border border-primary/30 bg-primary/15 px-3 py-1.5 text-xs font-medium text-primary shadow-sm backdrop-blur-sm",
|
|
534
|
+
children: typeof item === "boolean" ? t(item ? "yes" : "no") : typeof item === "number" ? item == null || Number.isNaN(item) ? "\u2014" : item.toLocaleString() : String(item)
|
|
535
|
+
},
|
|
536
|
+
`${parentKey}-primitive-${index}`
|
|
537
|
+
)) });
|
|
538
|
+
}
|
|
539
|
+
const containsStructuredContent = value.some((item) => isPlainObject2(item)) || value.some((item) => Array.isArray(item));
|
|
540
|
+
if (containsStructuredContent) {
|
|
541
|
+
if (inline) {
|
|
542
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-3", children: value.map((item, index) => /* @__PURE__ */ jsxs(
|
|
543
|
+
"div",
|
|
544
|
+
{
|
|
545
|
+
className: "px-3 py-2 bg-white rounded-lg ring-1 shadow-sm ring-border/40 shadow-foreground/10 dark:bg-slate-900",
|
|
546
|
+
children: [
|
|
547
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center mb-2 text-xs font-semibold tracking-wide uppercase text-muted-foreground", children: [
|
|
548
|
+
/* @__PURE__ */ jsx("span", { children: t("shareablePreviewItemLabel", { index: index + 1 }) }),
|
|
549
|
+
isPlainObject2(item) && /* @__PURE__ */ jsx("span", { className: "font-medium lowercase text-muted-foreground", children: t("shareablePreviewEntriesCount", {
|
|
550
|
+
count: Object.keys(
|
|
551
|
+
item
|
|
552
|
+
).length
|
|
553
|
+
}) })
|
|
554
|
+
] }),
|
|
555
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2", children: renderValue(item, depth + 1, `${parentKey}[${index}]`, true) })
|
|
556
|
+
]
|
|
557
|
+
},
|
|
558
|
+
`${parentKey}-object-inline-${index}`
|
|
559
|
+
)) });
|
|
560
|
+
}
|
|
561
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-4", children: value.map((item, index) => /* @__PURE__ */ jsxs(
|
|
562
|
+
"div",
|
|
563
|
+
{
|
|
564
|
+
className: "bg-white rounded-xl border shadow-xl border-border/40 shadow-foreground/10 dark:bg-slate-900",
|
|
565
|
+
children: [
|
|
566
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-between items-center px-4 py-2 text-xs font-medium tracking-wide uppercase border-b border-border/40 bg-muted/40 text-muted-foreground", children: [
|
|
567
|
+
/* @__PURE__ */ jsx("span", { children: t("shareablePreviewItemLabel", { index: index + 1 }) }),
|
|
568
|
+
isPlainObject2(item) && /* @__PURE__ */ jsx("span", { children: t("shareablePreviewEntriesCount", {
|
|
569
|
+
count: Object.keys(
|
|
570
|
+
item
|
|
571
|
+
).length
|
|
572
|
+
}) })
|
|
573
|
+
] }),
|
|
574
|
+
/* @__PURE__ */ jsx("div", { className: "px-4 py-3 space-y-2", children: renderValue(item, depth + 1, `${parentKey}[${index}]`, true) })
|
|
575
|
+
]
|
|
576
|
+
},
|
|
577
|
+
`${parentKey}-object-${index}`
|
|
578
|
+
)) });
|
|
579
|
+
}
|
|
580
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-2", children: value.map((item, index) => /* @__PURE__ */ jsx(
|
|
581
|
+
"div",
|
|
582
|
+
{
|
|
583
|
+
className: "px-3 py-2 text-xs bg-white rounded-lg border shadow-sm border-border/30 text-muted-foreground shadow-foreground/10 dark:bg-slate-900",
|
|
584
|
+
children: renderValue(item, depth + 1, `${parentKey}[${index}]`, true)
|
|
585
|
+
},
|
|
586
|
+
`${parentKey}-fallback-${index}`
|
|
587
|
+
)) });
|
|
588
|
+
}
|
|
589
|
+
function renderObject(value, depth, parentKey, inline = false) {
|
|
590
|
+
const entries = Object.entries(value);
|
|
591
|
+
if (entries.length === 0) {
|
|
592
|
+
return /* @__PURE__ */ jsx("span", { className: "text-sm italic text-muted-foreground", children: t("shareablePreviewEmptyObject") });
|
|
593
|
+
}
|
|
594
|
+
if (inline) {
|
|
595
|
+
return /* @__PURE__ */ jsx("div", { className: "space-y-2", children: entries.map(([key, val]) => /* @__PURE__ */ jsxs(
|
|
596
|
+
"div",
|
|
597
|
+
{
|
|
598
|
+
className: "px-3 py-2 bg-white rounded-lg ring-1 shadow-sm ring-border/30 shadow-foreground/10 dark:bg-slate-900/90",
|
|
599
|
+
children: [
|
|
600
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-semibold tracking-wide uppercase text-muted-foreground", children: formatKey(key) }),
|
|
601
|
+
/* @__PURE__ */ jsx("div", { className: "mt-1 text-sm text-foreground", children: renderValue(val, depth + 1, `${parentKey}.${key}`, true) })
|
|
602
|
+
]
|
|
603
|
+
},
|
|
604
|
+
`${parentKey}-${key}`
|
|
605
|
+
)) });
|
|
606
|
+
}
|
|
607
|
+
const gridClass = depth === 0 ? "grid gap-4 md:grid-cols-2" : depth === 1 ? "grid gap-3 md:grid-cols-1" : "space-y-3";
|
|
608
|
+
return /* @__PURE__ */ jsx("div", { className: gridClass, children: entries.map(([key, val]) => /* @__PURE__ */ jsxs(
|
|
609
|
+
"div",
|
|
610
|
+
{
|
|
611
|
+
className: cn(
|
|
612
|
+
"bg-white rounded-xl border shadow-xl border-border/30 shadow-foreground/10 dark:bg-slate-900"
|
|
613
|
+
),
|
|
614
|
+
children: [
|
|
615
|
+
/* @__PURE__ */ jsx("div", { className: "flex justify-between items-center px-4 py-2 border-b border-border/30 bg-muted/30", children: /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold tracking-wide uppercase text-muted-foreground", children: formatKey(key) }) }),
|
|
616
|
+
/* @__PURE__ */ jsx("div", { className: "px-4 py-3 space-y-2", children: renderValue(val, depth + 1, `${parentKey}.${key}`, true) })
|
|
617
|
+
]
|
|
618
|
+
},
|
|
619
|
+
`${parentKey}-${key}`
|
|
620
|
+
)) });
|
|
621
|
+
}
|
|
622
|
+
function renderValue(value, depth, parentKey, inline = false) {
|
|
623
|
+
if (depth > MAX_RENDER_DEPTH) {
|
|
624
|
+
return /* @__PURE__ */ jsx("pre", { className: "overflow-auto px-3 py-2 max-h-48 text-xs bg-white rounded-md border shadow-inner border-border/30 text-muted-foreground shadow-foreground/5 dark:bg-slate-900", children: stringifyValue(value) });
|
|
625
|
+
}
|
|
626
|
+
if (value === null || value === void 0) {
|
|
627
|
+
return /* @__PURE__ */ jsx("span", { className: "text-sm italic text-muted-foreground", children: t("shareablePreviewNoValue") });
|
|
628
|
+
}
|
|
629
|
+
if (isPrimitive(value)) {
|
|
630
|
+
return renderPrimitive(value);
|
|
631
|
+
}
|
|
632
|
+
if (Array.isArray(value)) {
|
|
633
|
+
return renderArray(value, depth, parentKey, inline);
|
|
634
|
+
}
|
|
635
|
+
if (isPlainObject2(value)) {
|
|
636
|
+
return renderObject(value, depth, parentKey, inline);
|
|
637
|
+
}
|
|
638
|
+
return /* @__PURE__ */ jsx("pre", { className: "px-3 py-2 text-xs bg-white rounded-md border shadow-inner border-border/30 text-muted-foreground shadow-foreground/5 dark:bg-slate-900", children: stringifyValue(value) });
|
|
639
|
+
}
|
|
640
|
+
return /* @__PURE__ */ jsx("div", { className: cn("space-y-4", className), children: renderValue(displayData, 0, "root") });
|
|
641
|
+
}
|
|
642
|
+
var getDataType = (value) => {
|
|
643
|
+
if (value === null) return "null";
|
|
644
|
+
if (value === void 0) return "undefined";
|
|
645
|
+
if (typeof value === "string") {
|
|
646
|
+
return value.startsWith("<") && value.includes(">") ? "HTML" : "Text";
|
|
647
|
+
}
|
|
648
|
+
if (Array.isArray(value)) return "Array";
|
|
649
|
+
if (typeof value === "object") return "Object";
|
|
650
|
+
return typeof value;
|
|
651
|
+
};
|
|
652
|
+
var sanitizeFileName = (value) => value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)+/g, "").concat(".txt");
|
|
653
|
+
var normalizeActionKey = (value) => value ? value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().trim() : "";
|
|
654
|
+
var ACTION_ICON_MAP = {
|
|
655
|
+
modifier: Pencil,
|
|
656
|
+
edit: Pencil,
|
|
657
|
+
update: Pencil,
|
|
658
|
+
"mise a jour": Pencil,
|
|
659
|
+
maj: Pencil,
|
|
660
|
+
supprimer: Trash2,
|
|
661
|
+
delete: Trash2,
|
|
662
|
+
remove: Trash2,
|
|
663
|
+
retirer: Trash2,
|
|
664
|
+
destroy: Trash2,
|
|
665
|
+
effacer: Trash2,
|
|
666
|
+
partager: Share2,
|
|
667
|
+
share: Share2,
|
|
668
|
+
envoyer: Share2,
|
|
669
|
+
telecharger: Download,
|
|
670
|
+
download: Download,
|
|
671
|
+
exporter: Download,
|
|
672
|
+
voir: Eye,
|
|
673
|
+
afficher: Eye,
|
|
674
|
+
visualiser: Eye,
|
|
675
|
+
preview: Eye,
|
|
676
|
+
annuler: XCircle,
|
|
677
|
+
cancel: XCircle,
|
|
678
|
+
retour: XCircle,
|
|
679
|
+
back: XCircle,
|
|
680
|
+
confirmer: CheckCircle,
|
|
681
|
+
valider: CheckCircle,
|
|
682
|
+
confirm: CheckCircle,
|
|
683
|
+
validate: CheckCircle
|
|
684
|
+
};
|
|
685
|
+
var DANGER_KEYWORDS = [
|
|
686
|
+
"supprimer",
|
|
687
|
+
"delete",
|
|
688
|
+
"remove",
|
|
689
|
+
"retirer",
|
|
690
|
+
"destroy",
|
|
691
|
+
"effacer",
|
|
692
|
+
"annuler",
|
|
693
|
+
"cancel",
|
|
694
|
+
"retour",
|
|
695
|
+
"abort",
|
|
696
|
+
"stop"
|
|
697
|
+
];
|
|
698
|
+
var SUCCESS_KEYWORDS = [
|
|
699
|
+
"confirmer",
|
|
700
|
+
"valider",
|
|
701
|
+
"confirm",
|
|
702
|
+
"validate",
|
|
703
|
+
"approve",
|
|
704
|
+
"approuver"
|
|
705
|
+
];
|
|
706
|
+
var DEFAULT_ACTION_VARIANT = "blue";
|
|
707
|
+
var findIconByKeyword = (key) => {
|
|
708
|
+
if (!key) return void 0;
|
|
709
|
+
if (ACTION_ICON_MAP[key]) return ACTION_ICON_MAP[key];
|
|
710
|
+
const entry = Object.entries(ACTION_ICON_MAP).find(
|
|
711
|
+
([keyword]) => key.includes(keyword)
|
|
712
|
+
);
|
|
713
|
+
return entry?.[1];
|
|
714
|
+
};
|
|
715
|
+
function getDefaultEyeDevMode() {
|
|
716
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV === "development") {
|
|
717
|
+
return "development";
|
|
718
|
+
}
|
|
719
|
+
return "production";
|
|
720
|
+
}
|
|
721
|
+
function ShareablePreview({
|
|
722
|
+
data,
|
|
723
|
+
title,
|
|
724
|
+
description,
|
|
725
|
+
actions = [],
|
|
726
|
+
shareUrl,
|
|
727
|
+
className,
|
|
728
|
+
maxHeight = "500px",
|
|
729
|
+
mode = "debug",
|
|
730
|
+
expandByDefault = true,
|
|
731
|
+
trigger,
|
|
732
|
+
triggerLabel,
|
|
733
|
+
triggerClassName,
|
|
734
|
+
defaultOpen = false,
|
|
735
|
+
onOpenChange,
|
|
736
|
+
eyeDevMode = getDefaultEyeDevMode(),
|
|
737
|
+
customView,
|
|
738
|
+
customViewProps = {},
|
|
739
|
+
customViews,
|
|
740
|
+
inline = false,
|
|
741
|
+
labels: labelsOverride = {},
|
|
742
|
+
onNotify
|
|
743
|
+
}) {
|
|
744
|
+
const mergedLabels = useMemo(
|
|
745
|
+
() => ({ ...DEFAULT_LABELS, ...labelsOverride }),
|
|
746
|
+
[labelsOverride]
|
|
747
|
+
);
|
|
748
|
+
const t = (key) => mergedLabels[key] ?? key;
|
|
749
|
+
const notify = (type, key) => {
|
|
750
|
+
if (onNotify) {
|
|
751
|
+
onNotify(type, key);
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
const [isExpanded, setIsExpanded] = useState(expandByDefault);
|
|
755
|
+
const [currentMode, setCurrentMode] = useState(mode);
|
|
756
|
+
const [isDialogOpen, setIsDialogOpen] = useState(defaultOpen);
|
|
757
|
+
const hasCustomViews = customViews && customViews.length > 0;
|
|
758
|
+
const firstViewId = customViews?.[0]?.id;
|
|
759
|
+
const [activeViewId, setActiveViewId] = useState(firstViewId ?? "");
|
|
760
|
+
useEffect(() => {
|
|
761
|
+
if (hasCustomViews && firstViewId) setActiveViewId(firstViewId);
|
|
762
|
+
}, [hasCustomViews, firstViewId]);
|
|
763
|
+
const effectiveActions = useMemo(() => {
|
|
764
|
+
const list = hasCustomViews && customViews.find((vc) => vc.id === activeViewId)?.actions?.length ? customViews.find((vc) => vc.id === activeViewId).actions : actions;
|
|
765
|
+
return [...list].sort(
|
|
766
|
+
(a, b) => (a.order ?? 999) - (b.order ?? 999)
|
|
767
|
+
);
|
|
768
|
+
}, [hasCustomViews, customViews, activeViewId, actions]);
|
|
769
|
+
useEffect(() => {
|
|
770
|
+
setIsExpanded(expandByDefault);
|
|
771
|
+
}, [expandByDefault]);
|
|
772
|
+
useEffect(() => {
|
|
773
|
+
setCurrentMode(mode);
|
|
774
|
+
}, [mode]);
|
|
775
|
+
useEffect(() => {
|
|
776
|
+
setIsDialogOpen(defaultOpen);
|
|
777
|
+
}, [defaultOpen]);
|
|
778
|
+
useEffect(() => {
|
|
779
|
+
if (!isDialogOpen && !inline) {
|
|
780
|
+
setIsExpanded(expandByDefault);
|
|
781
|
+
setCurrentMode(mode);
|
|
782
|
+
}
|
|
783
|
+
}, [expandByDefault, isDialogOpen, mode, inline]);
|
|
784
|
+
const formatData = useMemo(
|
|
785
|
+
() => (value) => {
|
|
786
|
+
if (value === null || value === void 0) {
|
|
787
|
+
return String(value);
|
|
788
|
+
}
|
|
789
|
+
if (typeof value === "string") {
|
|
790
|
+
try {
|
|
791
|
+
const parsed = JSON.parse(value);
|
|
792
|
+
return JSON.stringify(parsed, null, 2);
|
|
793
|
+
} catch {
|
|
794
|
+
return value;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
if (typeof value === "object") {
|
|
798
|
+
try {
|
|
799
|
+
return JSON.stringify(value, null, 2);
|
|
800
|
+
} catch {
|
|
801
|
+
return String(value);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return String(value);
|
|
805
|
+
},
|
|
806
|
+
[]
|
|
807
|
+
);
|
|
808
|
+
const dataType = useMemo(() => getDataType(data), [data]);
|
|
809
|
+
const dataTypeLabel = useMemo(() => {
|
|
810
|
+
const labels = {
|
|
811
|
+
Array: t("shareablePreviewDataTypeArray"),
|
|
812
|
+
Object: t("shareablePreviewDataTypeObject"),
|
|
813
|
+
HTML: t("shareablePreviewDataTypeHtml"),
|
|
814
|
+
Text: t("shareablePreviewDataTypeText"),
|
|
815
|
+
string: t("shareablePreviewDataTypeString"),
|
|
816
|
+
number: t("shareablePreviewDataTypeNumber"),
|
|
817
|
+
boolean: t("shareablePreviewDataTypeBoolean"),
|
|
818
|
+
undefined: t("shareablePreviewDataTypeUndefined"),
|
|
819
|
+
function: t("shareablePreviewDataTypeFunction"),
|
|
820
|
+
symbol: t("shareablePreviewDataTypeSymbol"),
|
|
821
|
+
bigint: t("shareablePreviewDataTypeBigint")
|
|
822
|
+
};
|
|
823
|
+
return labels[dataType] ?? dataType;
|
|
824
|
+
}, [dataType, t]);
|
|
825
|
+
const handleDialogOpenChange = (nextOpen) => {
|
|
826
|
+
setIsDialogOpen(nextOpen);
|
|
827
|
+
onOpenChange?.(nextOpen);
|
|
828
|
+
};
|
|
829
|
+
const handleCopy = async () => {
|
|
830
|
+
if (!("clipboard" in navigator) || !navigator.clipboard?.writeText) {
|
|
831
|
+
notify("error", "shareablePreviewClipboardUnsupported");
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
try {
|
|
835
|
+
const textToCopy = formatData(data);
|
|
836
|
+
await navigator.clipboard.writeText(textToCopy);
|
|
837
|
+
notify("success", "shareablePreviewCopySuccess");
|
|
838
|
+
} catch {
|
|
839
|
+
notify("error", "shareablePreviewClipboardError");
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
const handleShare = async () => {
|
|
843
|
+
if (!("clipboard" in navigator) || !navigator.clipboard?.writeText) {
|
|
844
|
+
notify("error", "shareablePreviewClipboardUnsupported");
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
try {
|
|
848
|
+
if (shareUrl) {
|
|
849
|
+
await navigator.clipboard.writeText(shareUrl);
|
|
850
|
+
notify("success", "shareablePreviewShareLinkCopied");
|
|
851
|
+
} else {
|
|
852
|
+
notify("error", "shareablePreviewShareUnavailable");
|
|
853
|
+
}
|
|
854
|
+
} catch {
|
|
855
|
+
notify("error", "shareablePreviewClipboardError");
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
const handleDownload = () => {
|
|
859
|
+
const content = formatData(data);
|
|
860
|
+
if (typeof document === "undefined") {
|
|
861
|
+
notify("error", "shareablePreviewDownloadUnavailable");
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
const element = document.createElement("a");
|
|
865
|
+
element.setAttribute(
|
|
866
|
+
"href",
|
|
867
|
+
`data:text/plain;charset=utf-8,${encodeURIComponent(content)}`
|
|
868
|
+
);
|
|
869
|
+
element.setAttribute(
|
|
870
|
+
"download",
|
|
871
|
+
sanitizeFileName(
|
|
872
|
+
title && title.trim().length > 0 ? title : t("shareablePreviewDefaultTitle")
|
|
873
|
+
)
|
|
874
|
+
);
|
|
875
|
+
element.style.display = "none";
|
|
876
|
+
document.body.appendChild(element);
|
|
877
|
+
element.click();
|
|
878
|
+
document.body.removeChild(element);
|
|
879
|
+
notify("success", "shareablePreviewDownloadSuccess");
|
|
880
|
+
};
|
|
881
|
+
const computedTitle = title && title.trim().length > 0 ? title : t("shareablePreviewDefaultTitle");
|
|
882
|
+
const dialogTrigger = trigger ?? /* @__PURE__ */ jsxs(
|
|
883
|
+
Button,
|
|
884
|
+
{
|
|
885
|
+
variant: "outline",
|
|
886
|
+
size: "sm",
|
|
887
|
+
className: cn(
|
|
888
|
+
"flex items-center gap-2 rounded-full border-primary/40 bg-background/80 px-4 py-2 text-sm font-medium text-primary shadow-sm transition-all hover:-translate-y-0.5 hover:border-primary hover:bg-background",
|
|
889
|
+
triggerClassName
|
|
890
|
+
),
|
|
891
|
+
children: [
|
|
892
|
+
/* @__PURE__ */ jsx(Eye, { className: "w-4 h-4" }),
|
|
893
|
+
/* @__PURE__ */ jsx("span", { children: triggerLabel ?? t("shareablePreviewOpen") })
|
|
894
|
+
]
|
|
895
|
+
}
|
|
896
|
+
);
|
|
897
|
+
const shouldShowTrigger = trigger !== void 0 || !onOpenChange && !defaultOpen;
|
|
898
|
+
const userViewLabels = {
|
|
899
|
+
yes: mergedLabels.yes,
|
|
900
|
+
no: mergedLabels.no,
|
|
901
|
+
shareablePreviewEmptyList: mergedLabels.shareablePreviewEmptyList,
|
|
902
|
+
shareablePreviewEmptyObject: mergedLabels.shareablePreviewEmptyObject,
|
|
903
|
+
shareablePreviewNoValue: mergedLabels.shareablePreviewNoValue,
|
|
904
|
+
shareablePreviewItemLabel: mergedLabels.shareablePreviewItemLabel,
|
|
905
|
+
shareablePreviewEntriesCount: mergedLabels.shareablePreviewEntriesCount,
|
|
906
|
+
shareablePreviewArrayLabel: mergedLabels.shareablePreviewArrayLabel,
|
|
907
|
+
shareablePreviewNestedObjectLabel: mergedLabels.shareablePreviewNestedObjectLabel
|
|
908
|
+
};
|
|
909
|
+
const previewContent = /* @__PURE__ */ jsxs(
|
|
910
|
+
Card,
|
|
911
|
+
{
|
|
912
|
+
className: cn(
|
|
913
|
+
"flex overflow-hidden flex-col border shadow-2xl transition-shadow max-h-[90vh] border-border/70 bg-slate-50 shadow-foreground/10 dark:bg-slate-900",
|
|
914
|
+
className,
|
|
915
|
+
inline && "shadow-sm max-h-none h-full"
|
|
916
|
+
),
|
|
917
|
+
children: [
|
|
918
|
+
/* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-b border-border/50 bg-slate-100 sm:px-6 sm:py-4 dark:bg-slate-900/90", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 justify-between items-start sm:flex-row sm:items-center", children: [
|
|
919
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
920
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center", children: [
|
|
921
|
+
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold truncate text-foreground", children: computedTitle }),
|
|
922
|
+
eyeDevMode === "development" && /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap rounded-full bg-primary/20 px-2 py-0.5 text-xs font-mono text-primary", children: dataTypeLabel }),
|
|
923
|
+
eyeDevMode === "development" && /* @__PURE__ */ jsx(
|
|
924
|
+
"span",
|
|
925
|
+
{
|
|
926
|
+
className: cn(
|
|
927
|
+
"whitespace-nowrap rounded-full px-2 py-0.5 text-xs font-mono font-medium",
|
|
928
|
+
currentMode === "debug" ? "bg-orange-500/20 text-orange-600" : "bg-blue-500/20 text-blue-600"
|
|
929
|
+
),
|
|
930
|
+
children: currentMode === "debug" ? t("shareablePreviewDebugMode") : t("shareablePreviewUserMode")
|
|
931
|
+
}
|
|
932
|
+
)
|
|
933
|
+
] }),
|
|
934
|
+
description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground sm:text-sm", children: description })
|
|
935
|
+
] }),
|
|
936
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2 items-center", children: [
|
|
937
|
+
eyeDevMode === "development" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
938
|
+
/* @__PURE__ */ jsx(
|
|
939
|
+
Button,
|
|
940
|
+
{
|
|
941
|
+
variant: "ghost",
|
|
942
|
+
size: "sm",
|
|
943
|
+
onClick: () => setCurrentMode(currentMode === "debug" ? "user" : "debug"),
|
|
944
|
+
title: currentMode === "debug" ? t("shareablePreviewSwitchToUser") : t("shareablePreviewSwitchToDebug"),
|
|
945
|
+
className: cn(
|
|
946
|
+
currentMode === "debug" ? "text-orange-600" : "text-blue-600"
|
|
947
|
+
),
|
|
948
|
+
children: currentMode === "debug" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
949
|
+
/* @__PURE__ */ jsx(Bug, { className: "w-4 h-4" }),
|
|
950
|
+
/* @__PURE__ */ jsx("span", { className: "hidden ml-1 text-xs sm:inline", children: t("shareablePreviewDebugMode") })
|
|
951
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
952
|
+
/* @__PURE__ */ jsx(Code, { className: "w-4 h-4" }),
|
|
953
|
+
/* @__PURE__ */ jsx("span", { className: "hidden ml-1 text-xs sm:inline", children: t("shareablePreviewUserMode") })
|
|
954
|
+
] })
|
|
955
|
+
}
|
|
956
|
+
),
|
|
957
|
+
/* @__PURE__ */ jsx(
|
|
958
|
+
Button,
|
|
959
|
+
{
|
|
960
|
+
variant: "ghost",
|
|
961
|
+
size: "sm",
|
|
962
|
+
onClick: handleCopy,
|
|
963
|
+
title: t("shareablePreviewCopyData"),
|
|
964
|
+
children: /* @__PURE__ */ jsx(Copy, { className: "w-4 h-4" })
|
|
965
|
+
}
|
|
966
|
+
)
|
|
967
|
+
] }),
|
|
968
|
+
shareUrl && /* @__PURE__ */ jsx(
|
|
969
|
+
Button,
|
|
970
|
+
{
|
|
971
|
+
variant: "ghost",
|
|
972
|
+
size: "sm",
|
|
973
|
+
onClick: handleShare,
|
|
974
|
+
title: t("shareablePreviewCopyShareLink"),
|
|
975
|
+
children: /* @__PURE__ */ jsx(Share2, { className: "w-4 h-4" })
|
|
976
|
+
}
|
|
977
|
+
),
|
|
978
|
+
eyeDevMode === "development" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
979
|
+
/* @__PURE__ */ jsx(
|
|
980
|
+
Button,
|
|
981
|
+
{
|
|
982
|
+
variant: "ghost",
|
|
983
|
+
size: "sm",
|
|
984
|
+
onClick: handleDownload,
|
|
985
|
+
title: t("shareablePreviewDownloadData"),
|
|
986
|
+
children: /* @__PURE__ */ jsx(Download, { className: "w-4 h-4" })
|
|
987
|
+
}
|
|
988
|
+
),
|
|
989
|
+
/* @__PURE__ */ jsx(
|
|
990
|
+
Button,
|
|
991
|
+
{
|
|
992
|
+
variant: "ghost",
|
|
993
|
+
size: "sm",
|
|
994
|
+
onClick: () => setIsExpanded((prev) => !prev),
|
|
995
|
+
className: "ml-auto sm:ml-0",
|
|
996
|
+
title: isExpanded ? t("shareablePreviewCollapseContent") : t("shareablePreviewExpandContent"),
|
|
997
|
+
children: isExpanded ? /* @__PURE__ */ jsx(ChevronUp, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx(ChevronDown, { className: "w-4 h-4" })
|
|
998
|
+
}
|
|
999
|
+
)
|
|
1000
|
+
] })
|
|
1001
|
+
] })
|
|
1002
|
+
] }) }),
|
|
1003
|
+
isExpanded && /* @__PURE__ */ jsxs(
|
|
1004
|
+
"div",
|
|
1005
|
+
{
|
|
1006
|
+
className: "overflow-auto flex-1 p-4 bg-slate-50 sm:p-6 dark:bg-slate-900",
|
|
1007
|
+
style: { maxHeight: inline ? void 0 : maxHeight },
|
|
1008
|
+
children: [
|
|
1009
|
+
/* @__PURE__ */ jsx("div", { className: cn(currentMode === "debug" ? "block" : "hidden"), children: /* @__PURE__ */ jsx(
|
|
1010
|
+
DebugView,
|
|
1011
|
+
{
|
|
1012
|
+
data,
|
|
1013
|
+
className: "overflow-visible p-0 text-xs bg-transparent border-none",
|
|
1014
|
+
maxDepthLabel: mergedLabels.shareablePreviewMaxDepth
|
|
1015
|
+
}
|
|
1016
|
+
) }),
|
|
1017
|
+
/* @__PURE__ */ jsx("div", { className: cn(currentMode === "user" ? "block" : "hidden"), children: hasCustomViews ? /* @__PURE__ */ jsxs(
|
|
1018
|
+
Tabs,
|
|
1019
|
+
{
|
|
1020
|
+
value: activeViewId,
|
|
1021
|
+
onValueChange: setActiveViewId,
|
|
1022
|
+
className: "w-full",
|
|
1023
|
+
children: [
|
|
1024
|
+
/* @__PURE__ */ jsx(TabsList, { className: "mb-4 w-full justify-start overflow-x-auto", children: customViews.map((vc) => /* @__PURE__ */ jsx(TabsTrigger, { value: vc.id, children: vc.label }, vc.id)) }),
|
|
1025
|
+
customViews.map((vc) => /* @__PURE__ */ jsx(TabsContent, { value: vc.id, className: "mt-0", children: React5__default.createElement(vc.view, {
|
|
1026
|
+
data,
|
|
1027
|
+
expandByDefault,
|
|
1028
|
+
...vc.viewProps
|
|
1029
|
+
}) }, vc.id))
|
|
1030
|
+
]
|
|
1031
|
+
}
|
|
1032
|
+
) : customView ? React5__default.createElement(customView, {
|
|
1033
|
+
data,
|
|
1034
|
+
expandByDefault,
|
|
1035
|
+
...customViewProps
|
|
1036
|
+
}) : /* @__PURE__ */ jsx(
|
|
1037
|
+
UserView,
|
|
1038
|
+
{
|
|
1039
|
+
data,
|
|
1040
|
+
expandByDefault,
|
|
1041
|
+
labels: userViewLabels,
|
|
1042
|
+
...customViewProps
|
|
1043
|
+
}
|
|
1044
|
+
) })
|
|
1045
|
+
]
|
|
1046
|
+
}
|
|
1047
|
+
),
|
|
1048
|
+
effectiveActions.length > 0 && /* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-t border-border/50 bg-slate-100 sm:px-6 dark:bg-slate-900/90", children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-3", children: effectiveActions.map((action, idx) => {
|
|
1049
|
+
const {
|
|
1050
|
+
label,
|
|
1051
|
+
icon,
|
|
1052
|
+
onClick,
|
|
1053
|
+
variant,
|
|
1054
|
+
withPulse,
|
|
1055
|
+
withIndicator,
|
|
1056
|
+
buttonClassName,
|
|
1057
|
+
disabled
|
|
1058
|
+
} = action;
|
|
1059
|
+
const normalizedLabel = normalizeActionKey(label);
|
|
1060
|
+
const normalizedIconHint = typeof icon === "string" ? normalizeActionKey(icon) : "";
|
|
1061
|
+
const isDangerAction = DANGER_KEYWORDS.some(
|
|
1062
|
+
(keyword) => normalizedLabel.includes(keyword) || normalizedIconHint.includes(keyword)
|
|
1063
|
+
);
|
|
1064
|
+
const isSuccessAction = SUCCESS_KEYWORDS.some(
|
|
1065
|
+
(keyword) => normalizedLabel.includes(keyword) || normalizedIconHint.includes(keyword)
|
|
1066
|
+
);
|
|
1067
|
+
const resolvedVariant = variant ?? (isDangerAction ? "red" : isSuccessAction ? "green" : DEFAULT_ACTION_VARIANT);
|
|
1068
|
+
let resolvedIcon;
|
|
1069
|
+
if (typeof icon === "function") {
|
|
1070
|
+
resolvedIcon = icon;
|
|
1071
|
+
} else if (React5__default.isValidElement(icon)) {
|
|
1072
|
+
const elementWithClassName = icon;
|
|
1073
|
+
resolvedIcon = ({ className: cls }) => React5__default.cloneElement(elementWithClassName, {
|
|
1074
|
+
className: cn(
|
|
1075
|
+
"w-4 h-4",
|
|
1076
|
+
cls,
|
|
1077
|
+
elementWithClassName.props.className
|
|
1078
|
+
)
|
|
1079
|
+
});
|
|
1080
|
+
} else {
|
|
1081
|
+
resolvedIcon = findIconByKeyword(normalizedIconHint) ?? findIconByKeyword(normalizedLabel);
|
|
1082
|
+
}
|
|
1083
|
+
return /* @__PURE__ */ jsx(
|
|
1084
|
+
ValidationButton,
|
|
1085
|
+
{
|
|
1086
|
+
onClick,
|
|
1087
|
+
disabled,
|
|
1088
|
+
icon: resolvedIcon,
|
|
1089
|
+
variant: resolvedVariant,
|
|
1090
|
+
withPulse: withPulse ?? false,
|
|
1091
|
+
withIndicator: withIndicator ?? false,
|
|
1092
|
+
className: cn(
|
|
1093
|
+
"justify-center px-5 py-2 text-sm font-semibold tracking-wide uppercase min-w-[150px]",
|
|
1094
|
+
buttonClassName,
|
|
1095
|
+
isDangerAction ? "shadow-red-400/30" : isSuccessAction ? "shadow-green-400/30" : "shadow-primary/20"
|
|
1096
|
+
),
|
|
1097
|
+
children: label
|
|
1098
|
+
},
|
|
1099
|
+
`${label}-${idx}`
|
|
1100
|
+
);
|
|
1101
|
+
}) }) })
|
|
1102
|
+
]
|
|
1103
|
+
}
|
|
1104
|
+
);
|
|
1105
|
+
if (inline) {
|
|
1106
|
+
return previewContent;
|
|
1107
|
+
}
|
|
1108
|
+
return /* @__PURE__ */ jsxs(Dialog, { open: isDialogOpen, onOpenChange: handleDialogOpenChange, children: [
|
|
1109
|
+
shouldShowTrigger && /* @__PURE__ */ jsx(DialogTrigger, { asChild: true, children: dialogTrigger }),
|
|
1110
|
+
/* @__PURE__ */ jsx(DialogContent, { className: "w-full max-w-[95vw] overflow-hidden border-none bg-transparent p-0 sm:max-w-4xl lg:max-w-5xl xl:max-w-6xl", children: previewContent })
|
|
1111
|
+
] });
|
|
1112
|
+
}
|
|
1113
|
+
function createPreviewActions(options) {
|
|
1114
|
+
const {
|
|
1115
|
+
onConfirm,
|
|
1116
|
+
onReject,
|
|
1117
|
+
onCancel,
|
|
1118
|
+
labels = {},
|
|
1119
|
+
withIndicatorOnConfirm = true,
|
|
1120
|
+
withPulseOnConfirm = false,
|
|
1121
|
+
order = "confirm-first"
|
|
1122
|
+
} = options;
|
|
1123
|
+
const confirmLabel = labels.confirm ?? "Confirmer";
|
|
1124
|
+
const rejectLabel = labels.reject ?? "Rejeter";
|
|
1125
|
+
const cancelLabel = labels.cancel ?? "Annuler";
|
|
1126
|
+
const base = order === "cancel-first" ? { cancel: 0, confirm: 1, reject: 2 } : { confirm: 1, reject: 2, cancel: 3 };
|
|
1127
|
+
const confirmAction = {
|
|
1128
|
+
label: confirmLabel,
|
|
1129
|
+
icon: CheckCircle,
|
|
1130
|
+
variant: "green",
|
|
1131
|
+
onClick: onConfirm,
|
|
1132
|
+
withIndicator: withIndicatorOnConfirm,
|
|
1133
|
+
withPulse: withPulseOnConfirm,
|
|
1134
|
+
order: base.confirm
|
|
1135
|
+
};
|
|
1136
|
+
const rejectAction = onReject ? {
|
|
1137
|
+
label: rejectLabel,
|
|
1138
|
+
icon: XCircle,
|
|
1139
|
+
variant: "red",
|
|
1140
|
+
onClick: onReject,
|
|
1141
|
+
order: base.reject
|
|
1142
|
+
} : null;
|
|
1143
|
+
const cancelAction = onCancel ? {
|
|
1144
|
+
label: cancelLabel,
|
|
1145
|
+
icon: XCircle,
|
|
1146
|
+
variant: "blue",
|
|
1147
|
+
onClick: onCancel,
|
|
1148
|
+
order: base.cancel
|
|
1149
|
+
} : null;
|
|
1150
|
+
return [confirmAction, rejectAction, cancelAction].filter(
|
|
1151
|
+
(a) => a !== null
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
export { DEFAULT_LABELS, DataTreeNode, DebugView, ShareablePreview, UserView, createPreviewActions, filterDataByKeys };
|