@limboai/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +134 -0
- package/dist/icons/CRLogo.d.ts +12 -0
- package/dist/icons/LinkedInIcon.d.ts +13 -0
- package/dist/icons/index.d.ts +4 -0
- package/dist/index.cjs +2169 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.mjs +2147 -0
- package/dist/index.mjs.map +1 -0
- package/dist/shared/hooks/index.d.ts +2 -0
- package/dist/shared/hooks/useTooltipVisibility.d.ts +32 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/types/index.d.ts +59 -0
- package/dist/types/media.d.ts +56 -0
- package/dist/ui/core/Slot.d.ts +33 -0
- package/dist/ui/core/credential/CredentialBadge.d.ts +63 -0
- package/dist/ui/core/credential/CredentialOverlayContent.d.ts +23 -0
- package/dist/ui/core/credential/CredentialOverlayRoot.d.ts +35 -0
- package/dist/ui/core/credential/CredentialOverlayTrigger.d.ts +23 -0
- package/dist/ui/core/credential/context/CredentialBadgeContext.d.ts +8 -0
- package/dist/ui/core/credential/context/CredentialOverlayContext.d.ts +21 -0
- package/dist/ui/core/credential/context/index.d.ts +2 -0
- package/dist/ui/core/credential/hooks/index.d.ts +2 -0
- package/dist/ui/core/credential/hooks/useCredentialBadgeContext.d.ts +15 -0
- package/dist/ui/core/credential/hooks/useCredentialOverlayContext.d.ts +23 -0
- package/dist/ui/core/credential/index.d.ts +36 -0
- package/dist/ui/core/index.d.ts +2 -0
- package/dist/ui/core/passport/PassportActions.d.ts +27 -0
- package/dist/ui/core/passport/PassportCopyButton.d.ts +38 -0
- package/dist/ui/core/passport/PassportField.d.ts +39 -0
- package/dist/ui/core/passport/PassportFooter.d.ts +20 -0
- package/dist/ui/core/passport/PassportHeader.d.ts +19 -0
- package/dist/ui/core/passport/PassportIdentities.d.ts +35 -0
- package/dist/ui/core/passport/PassportLogo.d.ts +25 -0
- package/dist/ui/core/passport/PassportRoot.d.ts +24 -0
- package/dist/ui/core/passport/PassportSigners.d.ts +28 -0
- package/dist/ui/core/passport/PassportTitle.d.ts +30 -0
- package/dist/ui/core/passport/context/PassportContext.d.ts +12 -0
- package/dist/ui/core/passport/context/index.d.ts +1 -0
- package/dist/ui/core/passport/hooks/index.d.ts +2 -0
- package/dist/ui/core/passport/hooks/usePassportContext.d.ts +20 -0
- package/dist/ui/core/passport/hooks/usePassportData.d.ts +22 -0
- package/dist/ui/core/passport/index.d.ts +49 -0
- package/dist/ui/default/LimboBadge.d.ts +22 -0
- package/dist/ui/default/LimboImage.d.ts +66 -0
- package/dist/ui/default/LimboPassport.d.ts +30 -0
- package/dist/ui/default/LimboVideo.d.ts +64 -0
- package/dist/ui/default/constants.d.ts +19 -0
- package/dist/ui/default/index.d.ts +10 -0
- package/dist/ui/index.d.ts +3 -0
- package/dist/ui/media/MediaImage.d.ts +87 -0
- package/dist/ui/media/MediaVideo.d.ts +85 -0
- package/dist/ui/media/context/MediaContext.d.ts +19 -0
- package/dist/ui/media/context/index.d.ts +1 -0
- package/dist/ui/media/hooks/index.d.ts +3 -0
- package/dist/ui/media/hooks/useHeicConversion.d.ts +32 -0
- package/dist/ui/media/hooks/useMediaContext.d.ts +13 -0
- package/dist/ui/media/hooks/useMediaOverlay.d.ts +37 -0
- package/dist/ui/media/index.d.ts +5 -0
- package/package.json +71 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2147 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Children, cloneElement, createContext, forwardRef, isValidElement, useCallback, useContext, useDebugValue, useEffect, useId, useMemo, useRef, useState, useTransition } from "react";
|
|
4
|
+
|
|
5
|
+
//#region src/icons/CRLogo.tsx
|
|
6
|
+
const CR_LOGO_BASE64 = "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";
|
|
7
|
+
/**
|
|
8
|
+
* Content Credentials (CR) Logo
|
|
9
|
+
*
|
|
10
|
+
* Based on the Content Authenticity Initiative logo
|
|
11
|
+
*/
|
|
12
|
+
const CRLogo = ({ size = 20, className, style }) => {
|
|
13
|
+
return /* @__PURE__ */ jsx("img", {
|
|
14
|
+
src: `data:image/png;base64,${CR_LOGO_BASE64}`,
|
|
15
|
+
width: size,
|
|
16
|
+
height: size,
|
|
17
|
+
className,
|
|
18
|
+
style,
|
|
19
|
+
alt: "Content Credentials"
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/icons/LinkedInIcon.tsx
|
|
25
|
+
/**
|
|
26
|
+
* LinkedIn Icon as inline SVG
|
|
27
|
+
*
|
|
28
|
+
* Used for LinkedIn identity verification links
|
|
29
|
+
*/
|
|
30
|
+
const LinkedInIcon = ({ size = 14, color = "#0077B5", className, style }) => {
|
|
31
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
32
|
+
width: size,
|
|
33
|
+
height: size,
|
|
34
|
+
viewBox: "0 0 24 24",
|
|
35
|
+
fill: color,
|
|
36
|
+
className,
|
|
37
|
+
style,
|
|
38
|
+
"aria-hidden": "true",
|
|
39
|
+
children: /* @__PURE__ */ jsx("path", { d: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" })
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/shared/hooks/useTooltipVisibility.ts
|
|
45
|
+
/**
|
|
46
|
+
* Hook for managing tooltip visibility with delayed hide
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```tsx
|
|
50
|
+
* const { isVisible, show, hide, toggle } = useTooltipVisibility({
|
|
51
|
+
* hasCredentials: validation?.has_credentials ?? false,
|
|
52
|
+
* hideDelay: 300,
|
|
53
|
+
* });
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
function useTooltipVisibility({ hasCredentials, initialVisible = false, hideDelay = 300 }) {
|
|
57
|
+
const [isVisible, setIsVisible] = useState(initialVisible);
|
|
58
|
+
const timeoutRef = useRef(null);
|
|
59
|
+
const clearPendingHide = useCallback(() => {
|
|
60
|
+
if (timeoutRef.current !== null) {
|
|
61
|
+
clearTimeout(timeoutRef.current);
|
|
62
|
+
timeoutRef.current = null;
|
|
63
|
+
}
|
|
64
|
+
}, []);
|
|
65
|
+
useEffect(() => clearPendingHide, [clearPendingHide]);
|
|
66
|
+
const show = useCallback(() => {
|
|
67
|
+
clearPendingHide();
|
|
68
|
+
if (hasCredentials) setIsVisible(true);
|
|
69
|
+
}, [hasCredentials, clearPendingHide]);
|
|
70
|
+
const hide = useCallback(() => {
|
|
71
|
+
clearPendingHide();
|
|
72
|
+
timeoutRef.current = setTimeout(() => setIsVisible(false), hideDelay);
|
|
73
|
+
}, [hideDelay, clearPendingHide]);
|
|
74
|
+
const toggle = useCallback(() => {
|
|
75
|
+
clearPendingHide();
|
|
76
|
+
if (hasCredentials) setIsVisible((v) => !v);
|
|
77
|
+
}, [hasCredentials, clearPendingHide]);
|
|
78
|
+
const setVisible = useCallback((visible) => {
|
|
79
|
+
clearPendingHide();
|
|
80
|
+
if (!visible || hasCredentials) setIsVisible(visible);
|
|
81
|
+
}, [hasCredentials, clearPendingHide]);
|
|
82
|
+
useDebugValue({
|
|
83
|
+
isVisible,
|
|
84
|
+
hasCredentials
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
isVisible,
|
|
88
|
+
show,
|
|
89
|
+
hide,
|
|
90
|
+
toggle,
|
|
91
|
+
setVisible
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region src/types/media.ts
|
|
97
|
+
/**
|
|
98
|
+
* Field names available in passport components
|
|
99
|
+
*/
|
|
100
|
+
const PassportFieldName = {
|
|
101
|
+
Issuer: "issuer",
|
|
102
|
+
Date: "date"
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Passport display variants
|
|
106
|
+
*/
|
|
107
|
+
const PassportVariant = {
|
|
108
|
+
Basic: "basic",
|
|
109
|
+
Full: "full"
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/types/index.ts
|
|
114
|
+
/**
|
|
115
|
+
* Validation status enum with const assertion and satisfies for type safety
|
|
116
|
+
*/
|
|
117
|
+
const ValidationStatus = {
|
|
118
|
+
Idle: "idle",
|
|
119
|
+
Error: "error",
|
|
120
|
+
Success: "success",
|
|
121
|
+
Loading: "loading"
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region src/ui/core/credential/context/CredentialBadgeContext.tsx
|
|
126
|
+
const CredentialBadgeContext = createContext(null);
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/ui/core/credential/hooks/useCredentialBadgeContext.ts
|
|
130
|
+
/**
|
|
131
|
+
* Access the CredentialBadge context
|
|
132
|
+
*
|
|
133
|
+
* Must be used within a CredentialBadge.Root component
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```tsx
|
|
137
|
+
* function MyBadgeContent() {
|
|
138
|
+
* const { status, hasCredentials } = useCredentialBadgeContext();
|
|
139
|
+
* return <div>{status}</div>;
|
|
140
|
+
* }
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
function useCredentialBadgeContext() {
|
|
144
|
+
const context = useContext(CredentialBadgeContext);
|
|
145
|
+
if (!context) throw new Error("useCredentialBadgeContext must be used within a CredentialBadge.Root");
|
|
146
|
+
useDebugValue({
|
|
147
|
+
status: context.status,
|
|
148
|
+
hasCredentials: context.hasCredentials
|
|
149
|
+
});
|
|
150
|
+
return context;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/ui/core/credential/context/CredentialOverlayContext.tsx
|
|
155
|
+
const CredentialOverlayContext = createContext(null);
|
|
156
|
+
|
|
157
|
+
//#endregion
|
|
158
|
+
//#region src/ui/core/credential/hooks/useCredentialOverlayContext.ts
|
|
159
|
+
/**
|
|
160
|
+
* Access the CredentialOverlay context
|
|
161
|
+
*
|
|
162
|
+
* Must be used within a CredentialOverlay.Root component
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```tsx
|
|
166
|
+
* function MyTrigger() {
|
|
167
|
+
* const { show, hide, toggle, isVisible } = useCredentialOverlayContext();
|
|
168
|
+
* return (
|
|
169
|
+
* <button
|
|
170
|
+
* onMouseEnter={show}
|
|
171
|
+
* onMouseLeave={hide}
|
|
172
|
+
* onClick={toggle}
|
|
173
|
+
* >
|
|
174
|
+
* {isVisible ? "Hide" : "Show"}
|
|
175
|
+
* </button>
|
|
176
|
+
* );
|
|
177
|
+
* }
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
function useCredentialOverlayContext() {
|
|
181
|
+
const context = useContext(CredentialOverlayContext);
|
|
182
|
+
if (!context) throw new Error("useCredentialOverlayContext must be used within a CredentialOverlay.Root");
|
|
183
|
+
useDebugValue({
|
|
184
|
+
status: context.status,
|
|
185
|
+
isVisible: context.isVisible
|
|
186
|
+
});
|
|
187
|
+
return context;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/ui/core/credential/CredentialBadge.tsx
|
|
192
|
+
/**
|
|
193
|
+
* CredentialBadge.Root - Provider wrapper for the CredentialBadge compound component
|
|
194
|
+
*
|
|
195
|
+
* Provides context with status and hasCredentials to all child components.
|
|
196
|
+
* Children are slot components that render based on the current state.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* ```tsx
|
|
200
|
+
* <CredentialBadge.Root status={status} hasCredentials={hasCredentials}>
|
|
201
|
+
* <CredentialBadge.Loading>
|
|
202
|
+
* <Spinner />
|
|
203
|
+
* </CredentialBadge.Loading>
|
|
204
|
+
*
|
|
205
|
+
* <CredentialBadge.Verified>
|
|
206
|
+
* <CheckIcon className="text-green-500" />
|
|
207
|
+
* </CredentialBadge.Verified>
|
|
208
|
+
*
|
|
209
|
+
* <CredentialBadge.NoCredentials>
|
|
210
|
+
* <span>No credentials</span>
|
|
211
|
+
* </CredentialBadge.NoCredentials>
|
|
212
|
+
*
|
|
213
|
+
* <CredentialBadge.Error>
|
|
214
|
+
* <span>Error</span>
|
|
215
|
+
* </CredentialBadge.Error>
|
|
216
|
+
* </CredentialBadge.Root>
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
const CredentialBadgeRoot = forwardRef(({ status, hasCredentials, children, ...props }, ref) => {
|
|
220
|
+
const contextValue = useMemo(() => ({
|
|
221
|
+
status,
|
|
222
|
+
hasCredentials
|
|
223
|
+
}), [status, hasCredentials]);
|
|
224
|
+
return /* @__PURE__ */ jsx(CredentialBadgeContext.Provider, {
|
|
225
|
+
value: contextValue,
|
|
226
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
227
|
+
ref,
|
|
228
|
+
...props,
|
|
229
|
+
children
|
|
230
|
+
})
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
CredentialBadgeRoot.displayName = "CredentialBadge.Root";
|
|
234
|
+
function createStatusComponent(displayName, shouldRender) {
|
|
235
|
+
const Component = ({ children }) => {
|
|
236
|
+
const { status, hasCredentials } = useCredentialBadgeContext();
|
|
237
|
+
if (!shouldRender(status, hasCredentials)) return null;
|
|
238
|
+
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
239
|
+
};
|
|
240
|
+
Component.displayName = displayName;
|
|
241
|
+
return Component;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* CredentialBadge.Loading - Renders children only when status is "loading"
|
|
245
|
+
*/
|
|
246
|
+
const CredentialBadgeLoading = createStatusComponent("CredentialBadge.Loading", (status) => status === ValidationStatus.Loading);
|
|
247
|
+
/**
|
|
248
|
+
* CredentialBadge.Error - Renders children only when status is "error"
|
|
249
|
+
*/
|
|
250
|
+
const CredentialBadgeError = createStatusComponent("CredentialBadge.Error", (status) => status === ValidationStatus.Error);
|
|
251
|
+
/**
|
|
252
|
+
* CredentialBadge.Verified - Renders children only when status is "success" and has credentials
|
|
253
|
+
*/
|
|
254
|
+
const CredentialBadgeVerified = createStatusComponent("CredentialBadge.Verified", (status, hasCredentials) => status === ValidationStatus.Success && hasCredentials);
|
|
255
|
+
/**
|
|
256
|
+
* CredentialBadge.Idle - Renders children only when status is "idle"
|
|
257
|
+
*/
|
|
258
|
+
const CredentialBadgeIdle = createStatusComponent("CredentialBadge.Idle", (status) => status === ValidationStatus.Idle);
|
|
259
|
+
/**
|
|
260
|
+
* CredentialBadge.NoCredentials - Renders children only when status is "success" but no credentials
|
|
261
|
+
*/
|
|
262
|
+
const CredentialBadgeNoCredentials = createStatusComponent("CredentialBadge.NoCredentials", (status, hasCredentials) => status === ValidationStatus.Success && !hasCredentials);
|
|
263
|
+
/**
|
|
264
|
+
* CredentialBadge - Headless compound component for displaying credential status
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* ```tsx
|
|
268
|
+
* import { CredentialBadge } from "@limboai/react";
|
|
269
|
+
*
|
|
270
|
+
* <CredentialBadge.Root status={status} hasCredentials={hasCredentials}>
|
|
271
|
+
* <CredentialBadge.Loading>
|
|
272
|
+
* <div className="animate-pulse bg-blue-500 rounded-full p-2">
|
|
273
|
+
* <Spinner />
|
|
274
|
+
* </div>
|
|
275
|
+
* </CredentialBadge.Loading>
|
|
276
|
+
*
|
|
277
|
+
* <CredentialBadge.Verified>
|
|
278
|
+
* <button className="bg-green-500 rounded-full p-2">
|
|
279
|
+
* <CheckIcon />
|
|
280
|
+
* </button>
|
|
281
|
+
* </CredentialBadge.Verified>
|
|
282
|
+
*
|
|
283
|
+
* <CredentialBadge.NoCredentials>
|
|
284
|
+
* <span className="bg-red-500 text-white text-xs px-2 py-1 rounded">
|
|
285
|
+
* No credentials
|
|
286
|
+
* </span>
|
|
287
|
+
* </CredentialBadge.NoCredentials>
|
|
288
|
+
*
|
|
289
|
+
* <CredentialBadge.Error>
|
|
290
|
+
* <span className="bg-red-500 text-white text-xs px-2 py-1 rounded">
|
|
291
|
+
* Error
|
|
292
|
+
* </span>
|
|
293
|
+
* </CredentialBadge.Error>
|
|
294
|
+
* </CredentialBadge.Root>
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
const CredentialBadge = {
|
|
298
|
+
Root: CredentialBadgeRoot,
|
|
299
|
+
Loading: CredentialBadgeLoading,
|
|
300
|
+
Verified: CredentialBadgeVerified,
|
|
301
|
+
NoCredentials: CredentialBadgeNoCredentials,
|
|
302
|
+
Error: CredentialBadgeError,
|
|
303
|
+
Idle: CredentialBadgeIdle
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region src/ui/core/credential/CredentialOverlayContent.tsx
|
|
308
|
+
/**
|
|
309
|
+
* CredentialOverlay.Content - Content that appears when the overlay is visible
|
|
310
|
+
*
|
|
311
|
+
* Only renders when the overlay is visible and credentials exist.
|
|
312
|
+
* Use forceMount to always render (useful for CSS transitions).
|
|
313
|
+
*
|
|
314
|
+
* @example
|
|
315
|
+
* ```tsx
|
|
316
|
+
* <CredentialOverlay.Content className="absolute top-12 right-0 bg-white shadow-lg rounded-lg p-4">
|
|
317
|
+
* <Passport.Root validation={validation}>
|
|
318
|
+
* ...
|
|
319
|
+
* </Passport.Root>
|
|
320
|
+
* </CredentialOverlay.Content>
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
const CredentialOverlayContent = forwardRef(({ children, forceMount, onMouseLeave, ...props }, ref) => {
|
|
324
|
+
const { isVisible, hasCredentials, validation, hide } = useCredentialOverlayContext();
|
|
325
|
+
if (!forceMount && (!isVisible || !hasCredentials || !validation)) return null;
|
|
326
|
+
const handleMouseLeave = (e) => {
|
|
327
|
+
hide();
|
|
328
|
+
onMouseLeave?.(e);
|
|
329
|
+
};
|
|
330
|
+
return /* @__PURE__ */ jsx("div", {
|
|
331
|
+
ref,
|
|
332
|
+
role: "tooltip",
|
|
333
|
+
tabIndex: -1,
|
|
334
|
+
"data-state": isVisible ? "open" : "closed",
|
|
335
|
+
onMouseLeave: handleMouseLeave,
|
|
336
|
+
...props,
|
|
337
|
+
children
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
CredentialOverlayContent.displayName = "CredentialOverlay.Content";
|
|
341
|
+
|
|
342
|
+
//#endregion
|
|
343
|
+
//#region src/ui/core/credential/CredentialOverlayRoot.tsx
|
|
344
|
+
/**
|
|
345
|
+
* CredentialOverlay.Root - Provider wrapper for the CredentialOverlay compound component
|
|
346
|
+
*
|
|
347
|
+
* Provides context with validation data and visibility state to all child components.
|
|
348
|
+
* Manages tooltip visibility with configurable hide delay.
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* ```tsx
|
|
352
|
+
* <CredentialOverlay.Root validation={validation} status="success">
|
|
353
|
+
* <CredentialOverlay.Trigger className="absolute top-2 right-2">
|
|
354
|
+
* <button className="bg-green-500 p-2 rounded-full">✓</button>
|
|
355
|
+
* </CredentialOverlay.Trigger>
|
|
356
|
+
*
|
|
357
|
+
* <CredentialOverlay.Content className="absolute top-12 right-0 bg-white shadow-lg rounded-lg p-4">
|
|
358
|
+
* <Passport.Root validation={validation}>
|
|
359
|
+
* ...
|
|
360
|
+
* </Passport.Root>
|
|
361
|
+
* </CredentialOverlay.Content>
|
|
362
|
+
* </CredentialOverlay.Root>
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
const CredentialOverlayRoot = forwardRef(({ validation, status = "idle", hideDelay = 300, children, ...props }, ref) => {
|
|
366
|
+
const overlayId = useId();
|
|
367
|
+
const hasCredentials = validation?.has_credentials ?? false;
|
|
368
|
+
const { isVisible, show, hide, toggle } = useTooltipVisibility({
|
|
369
|
+
hasCredentials,
|
|
370
|
+
hideDelay
|
|
371
|
+
});
|
|
372
|
+
const contextValue = useMemo(() => ({
|
|
373
|
+
validation,
|
|
374
|
+
status,
|
|
375
|
+
hasCredentials,
|
|
376
|
+
isVisible,
|
|
377
|
+
show,
|
|
378
|
+
hide,
|
|
379
|
+
toggle,
|
|
380
|
+
overlayId
|
|
381
|
+
}), [
|
|
382
|
+
validation,
|
|
383
|
+
status,
|
|
384
|
+
hasCredentials,
|
|
385
|
+
isVisible,
|
|
386
|
+
show,
|
|
387
|
+
hide,
|
|
388
|
+
toggle,
|
|
389
|
+
overlayId
|
|
390
|
+
]);
|
|
391
|
+
return /* @__PURE__ */ jsx(CredentialOverlayContext.Provider, {
|
|
392
|
+
value: contextValue,
|
|
393
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
394
|
+
ref,
|
|
395
|
+
...props,
|
|
396
|
+
children
|
|
397
|
+
})
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
CredentialOverlayRoot.displayName = "CredentialOverlay.Root";
|
|
401
|
+
|
|
402
|
+
//#endregion
|
|
403
|
+
//#region src/ui/core/Slot.tsx
|
|
404
|
+
/**
|
|
405
|
+
* Compose multiple refs into a single callback ref
|
|
406
|
+
* Handles function refs, object refs, and undefined refs safely
|
|
407
|
+
*/
|
|
408
|
+
function composeRefs(...refs) {
|
|
409
|
+
return (instance) => {
|
|
410
|
+
for (const ref of refs) if (typeof ref === "function") ref(instance);
|
|
411
|
+
else if (ref != null) ref.current = instance;
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Compose event handlers, calling child handler first then slot handler
|
|
416
|
+
*/
|
|
417
|
+
function composeEventHandlers(childHandler, slotHandler) {
|
|
418
|
+
if (!childHandler && !slotHandler) return void 0;
|
|
419
|
+
if (!childHandler) return slotHandler;
|
|
420
|
+
if (!slotHandler) return childHandler;
|
|
421
|
+
return (event) => {
|
|
422
|
+
childHandler(event);
|
|
423
|
+
slotHandler(event);
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Merge props from slot and child element
|
|
428
|
+
* - Event handlers are composed (child first, then slot)
|
|
429
|
+
* - className is concatenated
|
|
430
|
+
* - style is shallow merged (child overrides slot)
|
|
431
|
+
* - Other props: child overrides slot
|
|
432
|
+
*/
|
|
433
|
+
function mergeProps(slotProps, childProps) {
|
|
434
|
+
const merged = { ...slotProps };
|
|
435
|
+
for (const key in childProps) {
|
|
436
|
+
const slotValue = slotProps[key];
|
|
437
|
+
const childValue = childProps[key];
|
|
438
|
+
if (/^on[A-Z]/.test(key)) merged[key] = composeEventHandlers(childValue, slotValue);
|
|
439
|
+
else if (key === "className") merged[key] = [slotValue, childValue].filter(Boolean).join(" ");
|
|
440
|
+
else if (key === "style") merged[key] = {
|
|
441
|
+
...slotValue,
|
|
442
|
+
...childValue
|
|
443
|
+
};
|
|
444
|
+
else merged[key] = childValue;
|
|
445
|
+
}
|
|
446
|
+
return merged;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Slot - Enables the asChild pattern for composable components
|
|
450
|
+
*
|
|
451
|
+
* Renders the child element with merged props and refs from the slot.
|
|
452
|
+
* This enables consumers to use their own elements while inheriting
|
|
453
|
+
* behavior from headless components.
|
|
454
|
+
*
|
|
455
|
+
* Inspired by Radix UI's Slot primitive.
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```tsx
|
|
459
|
+
* // Component with asChild support
|
|
460
|
+
* const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
461
|
+
* ({ asChild, ...props }, ref) => {
|
|
462
|
+
* const Comp = asChild ? Slot : 'button';
|
|
463
|
+
* return <Comp ref={ref} {...props} />;
|
|
464
|
+
* }
|
|
465
|
+
* );
|
|
466
|
+
*
|
|
467
|
+
* // Consumer renders their own element
|
|
468
|
+
* <Button asChild onClick={handleClick}>
|
|
469
|
+
* <a href="/link">Click me</a>
|
|
470
|
+
* </Button>
|
|
471
|
+
* // Renders: <a href="/link" onClick={handleClick}>Click me</a>
|
|
472
|
+
* ```
|
|
473
|
+
*/
|
|
474
|
+
const Slot = forwardRef(({ children, ...slotProps }, forwardedRef) => {
|
|
475
|
+
const childArray = Children.toArray(children);
|
|
476
|
+
const slottableChild = childArray.find(isValidElement);
|
|
477
|
+
if (!slottableChild || !isValidElement(slottableChild)) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
478
|
+
const child = slottableChild;
|
|
479
|
+
const grandchildren = childArray.flatMap((c) => c === slottableChild ? Children.toArray(child.props.children) : [c]);
|
|
480
|
+
return cloneElement(child, {
|
|
481
|
+
...mergeProps(slotProps, child.props),
|
|
482
|
+
ref: forwardedRef ? composeRefs(forwardedRef, child.props.ref) : child.props.ref,
|
|
483
|
+
children: grandchildren.length > 0 ? grandchildren : void 0
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
Slot.displayName = "Slot";
|
|
487
|
+
|
|
488
|
+
//#endregion
|
|
489
|
+
//#region src/ui/core/credential/CredentialOverlayTrigger.tsx
|
|
490
|
+
/**
|
|
491
|
+
* CredentialOverlay.Trigger - Trigger element for showing/hiding the overlay
|
|
492
|
+
*
|
|
493
|
+
* Attaches hover and click handlers to show/hide the tooltip.
|
|
494
|
+
* Use asChild to render your own element.
|
|
495
|
+
*
|
|
496
|
+
* @example
|
|
497
|
+
* ```tsx
|
|
498
|
+
* <CredentialOverlay.Trigger asChild>
|
|
499
|
+
* <button className="bg-green-500 p-2 rounded-full">
|
|
500
|
+
* <CheckIcon />
|
|
501
|
+
* </button>
|
|
502
|
+
* </CredentialOverlay.Trigger>
|
|
503
|
+
* ```
|
|
504
|
+
*/
|
|
505
|
+
const CredentialOverlayTrigger = forwardRef(({ asChild, children, onMouseEnter, onMouseLeave, onClick, ...props }, ref) => {
|
|
506
|
+
const { show, hide, toggle, hasCredentials } = useCredentialOverlayContext();
|
|
507
|
+
const triggerProps = {
|
|
508
|
+
onMouseEnter: useCallback((e) => {
|
|
509
|
+
show();
|
|
510
|
+
onMouseEnter?.(e);
|
|
511
|
+
}, [show, onMouseEnter]),
|
|
512
|
+
onMouseLeave: useCallback((e) => {
|
|
513
|
+
hide();
|
|
514
|
+
onMouseLeave?.(e);
|
|
515
|
+
}, [hide, onMouseLeave]),
|
|
516
|
+
onClick: useCallback((e) => {
|
|
517
|
+
toggle();
|
|
518
|
+
onClick?.(e);
|
|
519
|
+
}, [toggle, onClick]),
|
|
520
|
+
"aria-label": hasCredentials ? "View content credentials" : "No credentials found",
|
|
521
|
+
"aria-haspopup": "dialog",
|
|
522
|
+
...props
|
|
523
|
+
};
|
|
524
|
+
if (asChild) return /* @__PURE__ */ jsx(Slot, {
|
|
525
|
+
ref,
|
|
526
|
+
...triggerProps,
|
|
527
|
+
children
|
|
528
|
+
});
|
|
529
|
+
return /* @__PURE__ */ jsx("div", {
|
|
530
|
+
ref,
|
|
531
|
+
...triggerProps,
|
|
532
|
+
children
|
|
533
|
+
});
|
|
534
|
+
});
|
|
535
|
+
CredentialOverlayTrigger.displayName = "CredentialOverlay.Trigger";
|
|
536
|
+
|
|
537
|
+
//#endregion
|
|
538
|
+
//#region src/ui/core/credential/index.ts
|
|
539
|
+
/**
|
|
540
|
+
* CredentialOverlay - Headless compound component for trigger + tooltip pattern
|
|
541
|
+
*
|
|
542
|
+
* @example
|
|
543
|
+
* ```tsx
|
|
544
|
+
* import { CredentialOverlay, Passport, CredentialBadge } from "@limboai/react";
|
|
545
|
+
*
|
|
546
|
+
* <div className="relative">
|
|
547
|
+
* <img src="/photo.jpg" />
|
|
548
|
+
*
|
|
549
|
+
* <CredentialOverlay.Root validation={validation} status={status}>
|
|
550
|
+
* <CredentialOverlay.Trigger className="absolute top-2 right-2">
|
|
551
|
+
* <CredentialBadge.Root status={status} hasCredentials={hasCredentials}>
|
|
552
|
+
* <CredentialBadge.Verified>
|
|
553
|
+
* <button className="bg-green-500 p-2 rounded-full">✓</button>
|
|
554
|
+
* </CredentialBadge.Verified>
|
|
555
|
+
* </CredentialBadge.Root>
|
|
556
|
+
* </CredentialOverlay.Trigger>
|
|
557
|
+
*
|
|
558
|
+
* <CredentialOverlay.Content className="absolute top-12 right-0 bg-white shadow-lg rounded-lg p-4">
|
|
559
|
+
* <Passport.Root validation={validation}>
|
|
560
|
+
* ...
|
|
561
|
+
* </Passport.Root>
|
|
562
|
+
* </CredentialOverlay.Content>
|
|
563
|
+
* </CredentialOverlay.Root>
|
|
564
|
+
* </div>
|
|
565
|
+
* ```
|
|
566
|
+
*/
|
|
567
|
+
const CredentialOverlay = {
|
|
568
|
+
Root: CredentialOverlayRoot,
|
|
569
|
+
Trigger: CredentialOverlayTrigger,
|
|
570
|
+
Content: CredentialOverlayContent
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
//#endregion
|
|
574
|
+
//#region src/ui/core/passport/context/PassportContext.tsx
|
|
575
|
+
const PassportContext = createContext(null);
|
|
576
|
+
|
|
577
|
+
//#endregion
|
|
578
|
+
//#region src/ui/core/passport/hooks/usePassportContext.ts
|
|
579
|
+
/**
|
|
580
|
+
* Access the Passport context
|
|
581
|
+
*
|
|
582
|
+
* Must be used within a Passport.Root component
|
|
583
|
+
*
|
|
584
|
+
* @example
|
|
585
|
+
* ```tsx
|
|
586
|
+
* function MyCustomField() {
|
|
587
|
+
* const { issuer, issuedDate, copied, isPending } = usePassportContext();
|
|
588
|
+
* return (
|
|
589
|
+
* <div>
|
|
590
|
+
* <p>{issuer} - {issuedDate}</p>
|
|
591
|
+
* {isPending && <span>Copying...</span>}
|
|
592
|
+
* </div>
|
|
593
|
+
* );
|
|
594
|
+
* }
|
|
595
|
+
* ```
|
|
596
|
+
*/
|
|
597
|
+
function usePassportContext() {
|
|
598
|
+
const context = useContext(PassportContext);
|
|
599
|
+
if (!context) throw new Error("usePassportContext must be used within a Passport.Root");
|
|
600
|
+
useDebugValue({
|
|
601
|
+
issuer: context.issuer,
|
|
602
|
+
copied: context.copied
|
|
603
|
+
});
|
|
604
|
+
return context;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
//#endregion
|
|
608
|
+
//#region src/ui/core/passport/hooks/usePassportData.ts
|
|
609
|
+
/**
|
|
610
|
+
* Extract actions from c2pa.actions.v2 or c2pa.actions assertion
|
|
611
|
+
*/
|
|
612
|
+
function extractActions(assertions) {
|
|
613
|
+
const actionsAssertion = assertions.find((a) => a.label === "c2pa.actions.v2" || a.label === "c2pa.actions");
|
|
614
|
+
if (!actionsAssertion?.value) return [];
|
|
615
|
+
const value = actionsAssertion.value;
|
|
616
|
+
if (value.actions && Array.isArray(value.actions)) return value.actions.map((a) => a.action).filter((action) => Boolean(action));
|
|
617
|
+
if (value.action) return [value.action];
|
|
618
|
+
return [];
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Extract verified identities from cawg.identity assertion
|
|
622
|
+
* Supports both:
|
|
623
|
+
* - Direct format: value.verifiedIdentities
|
|
624
|
+
* - CAWG 1.1 Verifiable Credential format: value.credentialSubject.verifiedIdentities
|
|
625
|
+
*/
|
|
626
|
+
function extractIdentities(assertions) {
|
|
627
|
+
const identityAssertion = assertions.find((a) => a.label === "cawg.identity");
|
|
628
|
+
if (!identityAssertion?.value) return [];
|
|
629
|
+
const value = identityAssertion.value;
|
|
630
|
+
if (value.credentialSubject?.verifiedIdentities && Array.isArray(value.credentialSubject.verifiedIdentities)) return value.credentialSubject.verifiedIdentities;
|
|
631
|
+
if (value.verifiedIdentities && Array.isArray(value.verifiedIdentities)) return value.verifiedIdentities;
|
|
632
|
+
return [];
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Format a date string for display
|
|
636
|
+
*/
|
|
637
|
+
function formatDate(dateString) {
|
|
638
|
+
return new Date(dateString).toLocaleDateString("en-US", {
|
|
639
|
+
year: "numeric",
|
|
640
|
+
month: "long",
|
|
641
|
+
day: "numeric"
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Hook to extract and process data from a C2PA validation response
|
|
646
|
+
*
|
|
647
|
+
* Extracts issuer, date, actions, identities, and signers from the validation
|
|
648
|
+
* response for easy consumption by Passport components.
|
|
649
|
+
*
|
|
650
|
+
* @example
|
|
651
|
+
* ```tsx
|
|
652
|
+
* const { issuer, issuedDate, actions, verifiedIdentities, previousSigners } =
|
|
653
|
+
* usePassportData(validation);
|
|
654
|
+
*
|
|
655
|
+
* return (
|
|
656
|
+
* <div>
|
|
657
|
+
* <p>Issued by: {issuer}</p>
|
|
658
|
+
* <p>Date: {issuedDate}</p>
|
|
659
|
+
* </div>
|
|
660
|
+
* );
|
|
661
|
+
* ```
|
|
662
|
+
*/
|
|
663
|
+
function usePassportData(validation) {
|
|
664
|
+
const data = useMemo(() => {
|
|
665
|
+
const activeManifest = validation.manifest_chain?.find((manifest) => manifest.label === validation.active_manifest_label) || validation.manifest_chain?.[0] || null;
|
|
666
|
+
const issuer = activeManifest?.signature_info.issuer || "Unknown";
|
|
667
|
+
const issuedDateRaw = activeManifest?.signature_info.time || null;
|
|
668
|
+
return {
|
|
669
|
+
issuer,
|
|
670
|
+
issuedDate: issuedDateRaw ? formatDate(issuedDateRaw) : "Date unknown",
|
|
671
|
+
issuedDateRaw,
|
|
672
|
+
actions: activeManifest?.assertions ? extractActions(activeManifest.assertions) : [],
|
|
673
|
+
verifiedIdentities: activeManifest?.assertions ? extractIdentities(activeManifest.assertions) : [],
|
|
674
|
+
previousSigners: validation.manifest_chain?.filter((m) => m.label !== validation.active_manifest_label) || [],
|
|
675
|
+
activeManifest,
|
|
676
|
+
validation
|
|
677
|
+
};
|
|
678
|
+
}, [validation]);
|
|
679
|
+
useDebugValue({
|
|
680
|
+
issuer: data.issuer,
|
|
681
|
+
hasActions: data.actions.length > 0
|
|
682
|
+
});
|
|
683
|
+
return data;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
//#endregion
|
|
687
|
+
//#region src/ui/core/passport/PassportActions.tsx
|
|
688
|
+
/**
|
|
689
|
+
* Format action label for display
|
|
690
|
+
* e.g., "c2pa.edited" -> "Edited"
|
|
691
|
+
*/
|
|
692
|
+
function formatActionLabel(action) {
|
|
693
|
+
const withoutNamespace = action.split(".").pop() || action;
|
|
694
|
+
return withoutNamespace.charAt(0).toUpperCase() + withoutNamespace.slice(1);
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Passport.Actions - Display actions from the manifest
|
|
698
|
+
*
|
|
699
|
+
* Renders actions using a render prop for full styling control.
|
|
700
|
+
* Returns null if there are no actions.
|
|
701
|
+
*
|
|
702
|
+
* @example
|
|
703
|
+
* ```tsx
|
|
704
|
+
* <Passport.Actions className="mt-4">
|
|
705
|
+
* {(actions) => (
|
|
706
|
+
* <div className="flex gap-2">
|
|
707
|
+
* {actions.map((action) => (
|
|
708
|
+
* <span key={action} className="tag bg-gray-100 px-2 py-1 rounded">
|
|
709
|
+
* {action}
|
|
710
|
+
* </span>
|
|
711
|
+
* ))}
|
|
712
|
+
* </div>
|
|
713
|
+
* )}
|
|
714
|
+
* </Passport.Actions>
|
|
715
|
+
* ```
|
|
716
|
+
*/
|
|
717
|
+
const PassportActions = forwardRef(({ children, ...props }, ref) => {
|
|
718
|
+
const { actions } = usePassportContext();
|
|
719
|
+
if (actions.length === 0) return null;
|
|
720
|
+
const formattedActions = actions.map(formatActionLabel);
|
|
721
|
+
if (typeof children !== "function") return /* @__PURE__ */ jsxs("div", {
|
|
722
|
+
ref,
|
|
723
|
+
...props,
|
|
724
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
725
|
+
"data-passport-actions-label": true,
|
|
726
|
+
children: "Actions"
|
|
727
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
728
|
+
"data-passport-actions-list": true,
|
|
729
|
+
children: formattedActions.map((action) => /* @__PURE__ */ jsx("span", {
|
|
730
|
+
"data-passport-action": true,
|
|
731
|
+
children: action
|
|
732
|
+
}, action))
|
|
733
|
+
})]
|
|
734
|
+
});
|
|
735
|
+
return /* @__PURE__ */ jsx("div", {
|
|
736
|
+
ref,
|
|
737
|
+
...props,
|
|
738
|
+
children: children(formattedActions)
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
PassportActions.displayName = "Passport.Actions";
|
|
742
|
+
|
|
743
|
+
//#endregion
|
|
744
|
+
//#region src/ui/core/passport/PassportCopyButton.tsx
|
|
745
|
+
/**
|
|
746
|
+
* Passport.CopyButton - Copy validation JSON to clipboard
|
|
747
|
+
*
|
|
748
|
+
* Renders a button that copies the validation JSON to clipboard.
|
|
749
|
+
* Supports pending state from React 19's useActionState.
|
|
750
|
+
* Use asChild to provide your own button element.
|
|
751
|
+
*
|
|
752
|
+
* @example
|
|
753
|
+
* ```tsx
|
|
754
|
+
* // Default button
|
|
755
|
+
* <Passport.CopyButton className="btn" />
|
|
756
|
+
*
|
|
757
|
+
* // Custom button with asChild
|
|
758
|
+
* <Passport.CopyButton asChild>
|
|
759
|
+
* <button className="my-button">
|
|
760
|
+
* Copy JSON
|
|
761
|
+
* </button>
|
|
762
|
+
* </Passport.CopyButton>
|
|
763
|
+
*
|
|
764
|
+
* // Access pending state in custom component
|
|
765
|
+
* function CustomCopyButton() {
|
|
766
|
+
* const { copyJson, copied, isPending } = usePassportContext();
|
|
767
|
+
* return (
|
|
768
|
+
* <button onClick={copyJson} disabled={isPending}>
|
|
769
|
+
* {isPending ? "Copying..." : copied ? "Copied!" : "Copy"}
|
|
770
|
+
* </button>
|
|
771
|
+
* );
|
|
772
|
+
* }
|
|
773
|
+
* ```
|
|
774
|
+
*/
|
|
775
|
+
const PassportCopyButton = forwardRef(({ asChild, children, onClick, disabled, ...props }, ref) => {
|
|
776
|
+
const { copyJson, copied, isPending } = usePassportContext();
|
|
777
|
+
const handleClick = (e) => {
|
|
778
|
+
copyJson();
|
|
779
|
+
onClick?.(e);
|
|
780
|
+
};
|
|
781
|
+
const commonProps = {
|
|
782
|
+
onClick: handleClick,
|
|
783
|
+
disabled: disabled || isPending,
|
|
784
|
+
"data-copied": copied || void 0,
|
|
785
|
+
"data-pending": isPending || void 0,
|
|
786
|
+
"aria-busy": isPending || void 0,
|
|
787
|
+
...props
|
|
788
|
+
};
|
|
789
|
+
if (asChild && children) return /* @__PURE__ */ jsx(Slot, {
|
|
790
|
+
ref,
|
|
791
|
+
...commonProps,
|
|
792
|
+
children
|
|
793
|
+
});
|
|
794
|
+
return /* @__PURE__ */ jsx("button", {
|
|
795
|
+
ref,
|
|
796
|
+
type: "button",
|
|
797
|
+
...commonProps,
|
|
798
|
+
children: isPending ? "Copying..." : copied ? "Copied!" : "Copy JSON"
|
|
799
|
+
});
|
|
800
|
+
});
|
|
801
|
+
PassportCopyButton.displayName = "Passport.CopyButton";
|
|
802
|
+
|
|
803
|
+
//#endregion
|
|
804
|
+
//#region src/ui/core/passport/PassportField.tsx
|
|
805
|
+
const DEFAULT_LABELS = {
|
|
806
|
+
[PassportFieldName.Issuer]: "Issued by",
|
|
807
|
+
[PassportFieldName.Date]: "Date issued"
|
|
808
|
+
};
|
|
809
|
+
/**
|
|
810
|
+
* Passport.Field - Display a field from the passport data
|
|
811
|
+
*
|
|
812
|
+
* Automatically fills the value from context based on the field name.
|
|
813
|
+
* Use a render function for complete control over layout.
|
|
814
|
+
*
|
|
815
|
+
* @example
|
|
816
|
+
* ```tsx
|
|
817
|
+
* // Simple usage with default rendering
|
|
818
|
+
* <Passport.Field name="issuer" className="mb-2" />
|
|
819
|
+
*
|
|
820
|
+
* // Custom label
|
|
821
|
+
* <Passport.Field name="date" label="Signed on" />
|
|
822
|
+
*
|
|
823
|
+
* // Render function for custom layout
|
|
824
|
+
* <Passport.Field name="issuer">
|
|
825
|
+
* {({ label, value }) => (
|
|
826
|
+
* <div className="flex justify-between">
|
|
827
|
+
* <span className="font-bold">{label}</span>
|
|
828
|
+
* <span>{value}</span>
|
|
829
|
+
* </div>
|
|
830
|
+
* )}
|
|
831
|
+
* </Passport.Field>
|
|
832
|
+
* ```
|
|
833
|
+
*/
|
|
834
|
+
const PassportField = forwardRef(({ name, label, children, ...props }, ref) => {
|
|
835
|
+
const context = usePassportContext();
|
|
836
|
+
const value = name === PassportFieldName.Issuer ? context.issuer : context.issuedDate;
|
|
837
|
+
const displayLabel = label ?? DEFAULT_LABELS[name];
|
|
838
|
+
if (typeof children === "function") return /* @__PURE__ */ jsx("div", {
|
|
839
|
+
ref,
|
|
840
|
+
...props,
|
|
841
|
+
children: children({
|
|
842
|
+
label: displayLabel,
|
|
843
|
+
value
|
|
844
|
+
})
|
|
845
|
+
});
|
|
846
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
847
|
+
ref,
|
|
848
|
+
...props,
|
|
849
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
850
|
+
"data-passport-field-label": true,
|
|
851
|
+
children: displayLabel
|
|
852
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
853
|
+
"data-passport-field-value": true,
|
|
854
|
+
children: value
|
|
855
|
+
})]
|
|
856
|
+
});
|
|
857
|
+
});
|
|
858
|
+
PassportField.displayName = "Passport.Field";
|
|
859
|
+
|
|
860
|
+
//#endregion
|
|
861
|
+
//#region src/ui/core/passport/PassportFooter.tsx
|
|
862
|
+
/**
|
|
863
|
+
* Passport.Footer - Footer slot for the Passport
|
|
864
|
+
*
|
|
865
|
+
* A simple div wrapper for footer content. Style it yourself.
|
|
866
|
+
*
|
|
867
|
+
* @example
|
|
868
|
+
* ```tsx
|
|
869
|
+
* <Passport.Footer className="mt-4 border-t pt-4 flex justify-end">
|
|
870
|
+
* <Passport.CopyButton asChild>
|
|
871
|
+
* <button className="btn-primary">Copy JSON</button>
|
|
872
|
+
* </Passport.CopyButton>
|
|
873
|
+
* </Passport.Footer>
|
|
874
|
+
* ```
|
|
875
|
+
*/
|
|
876
|
+
const PassportFooter = forwardRef(({ children, ...props }, ref) => {
|
|
877
|
+
return /* @__PURE__ */ jsx("div", {
|
|
878
|
+
ref,
|
|
879
|
+
...props,
|
|
880
|
+
children
|
|
881
|
+
});
|
|
882
|
+
});
|
|
883
|
+
PassportFooter.displayName = "Passport.Footer";
|
|
884
|
+
|
|
885
|
+
//#endregion
|
|
886
|
+
//#region src/ui/core/passport/PassportHeader.tsx
|
|
887
|
+
/**
|
|
888
|
+
* Passport.Header - Header slot for the Passport
|
|
889
|
+
*
|
|
890
|
+
* A simple div wrapper for header content. Style it yourself.
|
|
891
|
+
*
|
|
892
|
+
* @example
|
|
893
|
+
* ```tsx
|
|
894
|
+
* <Passport.Header className="flex items-center gap-2 mb-4">
|
|
895
|
+
* <Passport.Logo className="w-6 h-6" />
|
|
896
|
+
* <Passport.Title className="text-lg font-bold" />
|
|
897
|
+
* </Passport.Header>
|
|
898
|
+
* ```
|
|
899
|
+
*/
|
|
900
|
+
const PassportHeader = forwardRef(({ children, ...props }, ref) => {
|
|
901
|
+
return /* @__PURE__ */ jsx("div", {
|
|
902
|
+
ref,
|
|
903
|
+
...props,
|
|
904
|
+
children
|
|
905
|
+
});
|
|
906
|
+
});
|
|
907
|
+
PassportHeader.displayName = "Passport.Header";
|
|
908
|
+
|
|
909
|
+
//#endregion
|
|
910
|
+
//#region src/ui/core/passport/PassportIdentities.tsx
|
|
911
|
+
/**
|
|
912
|
+
* Convert VerifiedIdentity to IdentityInfo for easier rendering
|
|
913
|
+
*/
|
|
914
|
+
function toIdentityInfo(identity) {
|
|
915
|
+
return {
|
|
916
|
+
username: identity.name,
|
|
917
|
+
provider: identity.provider.name,
|
|
918
|
+
uri: identity.uri,
|
|
919
|
+
verifiedAt: identity.verifiedAt
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Passport.Identities - Display verified identities from the manifest
|
|
924
|
+
*
|
|
925
|
+
* Renders identities using a render prop for full styling control.
|
|
926
|
+
* Returns null if there are no identities.
|
|
927
|
+
*
|
|
928
|
+
* @example
|
|
929
|
+
* ```tsx
|
|
930
|
+
* <Passport.Identities className="mt-4">
|
|
931
|
+
* {(identities) => (
|
|
932
|
+
* <ul className="space-y-2">
|
|
933
|
+
* {identities.map((identity) => (
|
|
934
|
+
* <li key={identity.provider} className="flex items-center gap-2">
|
|
935
|
+
* {identity.provider === "LinkedIn" && <LinkedInIcon />}
|
|
936
|
+
* {identity.uri ? (
|
|
937
|
+
* <a href={identity.uri} target="_blank" rel="noopener noreferrer">
|
|
938
|
+
* {identity.username}
|
|
939
|
+
* </a>
|
|
940
|
+
* ) : (
|
|
941
|
+
* <span>{identity.username}</span>
|
|
942
|
+
* )}
|
|
943
|
+
* </li>
|
|
944
|
+
* ))}
|
|
945
|
+
* </ul>
|
|
946
|
+
* )}
|
|
947
|
+
* </Passport.Identities>
|
|
948
|
+
* ```
|
|
949
|
+
*/
|
|
950
|
+
const PassportIdentities = forwardRef(({ children, ...props }, ref) => {
|
|
951
|
+
const { verifiedIdentities } = usePassportContext();
|
|
952
|
+
if (verifiedIdentities.length === 0) return null;
|
|
953
|
+
const identities = verifiedIdentities.map(toIdentityInfo);
|
|
954
|
+
const label = identities.length === 1 ? "Verified Identity" : `Verified Identities (${identities.length})`;
|
|
955
|
+
if (typeof children !== "function") return /* @__PURE__ */ jsxs("div", {
|
|
956
|
+
ref,
|
|
957
|
+
...props,
|
|
958
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
959
|
+
"data-passport-identities-label": true,
|
|
960
|
+
children: label
|
|
961
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
962
|
+
"data-passport-identities-list": true,
|
|
963
|
+
children: identities.map((identity) => /* @__PURE__ */ jsxs("div", {
|
|
964
|
+
"data-passport-identity": true,
|
|
965
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
966
|
+
"data-passport-identity-provider": true,
|
|
967
|
+
children: identity.provider
|
|
968
|
+
}), identity.uri ? /* @__PURE__ */ jsx("a", {
|
|
969
|
+
href: identity.uri,
|
|
970
|
+
target: "_blank",
|
|
971
|
+
rel: "noopener noreferrer",
|
|
972
|
+
"data-passport-identity-link": true,
|
|
973
|
+
children: identity.username
|
|
974
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
975
|
+
"data-passport-identity-username": true,
|
|
976
|
+
children: identity.username
|
|
977
|
+
})]
|
|
978
|
+
}, identity.provider))
|
|
979
|
+
})]
|
|
980
|
+
});
|
|
981
|
+
return /* @__PURE__ */ jsx("div", {
|
|
982
|
+
ref,
|
|
983
|
+
...props,
|
|
984
|
+
children: children(identities)
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
PassportIdentities.displayName = "Passport.Identities";
|
|
988
|
+
|
|
989
|
+
//#endregion
|
|
990
|
+
//#region src/ui/core/passport/PassportLogo.tsx
|
|
991
|
+
/**
|
|
992
|
+
* Passport.Logo - Logo for the Passport
|
|
993
|
+
*
|
|
994
|
+
* Renders the Content Credentials logo by default.
|
|
995
|
+
* Use asChild to provide your own logo element.
|
|
996
|
+
*
|
|
997
|
+
* @example
|
|
998
|
+
* ```tsx
|
|
999
|
+
* // Default logo
|
|
1000
|
+
* <Passport.Logo className="w-6 h-6" />
|
|
1001
|
+
*
|
|
1002
|
+
* // Custom logo with asChild
|
|
1003
|
+
* <Passport.Logo asChild>
|
|
1004
|
+
* <img src="/my-logo.svg" alt="Logo" className="w-6 h-6" />
|
|
1005
|
+
* </Passport.Logo>
|
|
1006
|
+
* ```
|
|
1007
|
+
*/
|
|
1008
|
+
const PassportLogo = forwardRef(({ asChild, children, ...props }, ref) => {
|
|
1009
|
+
if (asChild && children) return /* @__PURE__ */ jsx(Slot, {
|
|
1010
|
+
ref,
|
|
1011
|
+
...props,
|
|
1012
|
+
children
|
|
1013
|
+
});
|
|
1014
|
+
return /* @__PURE__ */ jsx(CRLogo, { ...props });
|
|
1015
|
+
});
|
|
1016
|
+
PassportLogo.displayName = "Passport.Logo";
|
|
1017
|
+
|
|
1018
|
+
//#endregion
|
|
1019
|
+
//#region src/ui/core/passport/PassportRoot.tsx
|
|
1020
|
+
/**
|
|
1021
|
+
* Passport.Root - Provider wrapper for the Passport compound component
|
|
1022
|
+
*
|
|
1023
|
+
* Provides context with processed validation data to all child components.
|
|
1024
|
+
* Uses React 19's useTransition for non-blocking copy operations.
|
|
1025
|
+
*
|
|
1026
|
+
* @example
|
|
1027
|
+
* ```tsx
|
|
1028
|
+
* <Passport.Root validation={validation} className="my-tooltip bg-white p-4 rounded">
|
|
1029
|
+
* <Passport.Header />
|
|
1030
|
+
* <Passport.Field name="issuer" />
|
|
1031
|
+
* <Passport.Footer />
|
|
1032
|
+
* </Passport.Root>
|
|
1033
|
+
* ```
|
|
1034
|
+
*/
|
|
1035
|
+
const PassportRoot = forwardRef(({ validation, children, ...props }, ref) => {
|
|
1036
|
+
const titleId = useId();
|
|
1037
|
+
const passportData = usePassportData(validation);
|
|
1038
|
+
const [copied, setCopied] = useState(false);
|
|
1039
|
+
const [isPending, startTransition] = useTransition();
|
|
1040
|
+
const copyJson = useCallback(async () => {
|
|
1041
|
+
startTransition(async () => {
|
|
1042
|
+
try {
|
|
1043
|
+
await navigator.clipboard.writeText(JSON.stringify(validation, null, 2));
|
|
1044
|
+
setCopied(true);
|
|
1045
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
console.error("Failed to copy:", error);
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
return true;
|
|
1051
|
+
}, [validation]);
|
|
1052
|
+
const contextValue = useMemo(() => ({
|
|
1053
|
+
...passportData,
|
|
1054
|
+
copyJson,
|
|
1055
|
+
copied,
|
|
1056
|
+
isPending,
|
|
1057
|
+
titleId
|
|
1058
|
+
}), [
|
|
1059
|
+
passportData,
|
|
1060
|
+
copyJson,
|
|
1061
|
+
copied,
|
|
1062
|
+
isPending,
|
|
1063
|
+
titleId
|
|
1064
|
+
]);
|
|
1065
|
+
return /* @__PURE__ */ jsx(PassportContext.Provider, {
|
|
1066
|
+
value: contextValue,
|
|
1067
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
1068
|
+
ref,
|
|
1069
|
+
role: "dialog",
|
|
1070
|
+
"aria-labelledby": titleId,
|
|
1071
|
+
...props,
|
|
1072
|
+
children
|
|
1073
|
+
})
|
|
1074
|
+
});
|
|
1075
|
+
});
|
|
1076
|
+
PassportRoot.displayName = "Passport.Root";
|
|
1077
|
+
|
|
1078
|
+
//#endregion
|
|
1079
|
+
//#region src/ui/core/passport/PassportSigners.tsx
|
|
1080
|
+
/**
|
|
1081
|
+
* Passport.Signers - Display previous signers in the manifest chain
|
|
1082
|
+
*
|
|
1083
|
+
* Renders signers using a render prop for full styling control.
|
|
1084
|
+
* Returns null if there are no previous signers.
|
|
1085
|
+
*
|
|
1086
|
+
* @example
|
|
1087
|
+
* ```tsx
|
|
1088
|
+
* <Passport.Signers className="mt-4">
|
|
1089
|
+
* {(signers) => (
|
|
1090
|
+
* <ul className="space-y-1">
|
|
1091
|
+
* {signers.map((signer) => (
|
|
1092
|
+
* <li key={signer.id} className="text-sm text-gray-600">
|
|
1093
|
+
* {signer.label}
|
|
1094
|
+
* </li>
|
|
1095
|
+
* ))}
|
|
1096
|
+
* </ul>
|
|
1097
|
+
* )}
|
|
1098
|
+
* </Passport.Signers>
|
|
1099
|
+
* ```
|
|
1100
|
+
*/
|
|
1101
|
+
const PassportSigners = forwardRef(({ children, ...props }, ref) => {
|
|
1102
|
+
const { previousSigners } = usePassportContext();
|
|
1103
|
+
if (previousSigners.length === 0) return null;
|
|
1104
|
+
const signers = previousSigners.map((manifest) => ({
|
|
1105
|
+
id: manifest.instance_id,
|
|
1106
|
+
label: manifest.signature_info.issuer,
|
|
1107
|
+
signedAt: manifest.signature_info.time ?? void 0
|
|
1108
|
+
}));
|
|
1109
|
+
const label = signers.length === 1 ? "Previous Signer" : `Previous Signers (${signers.length})`;
|
|
1110
|
+
if (typeof children !== "function") return /* @__PURE__ */ jsxs("div", {
|
|
1111
|
+
ref,
|
|
1112
|
+
...props,
|
|
1113
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
1114
|
+
"data-passport-signers-label": true,
|
|
1115
|
+
children: label
|
|
1116
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1117
|
+
"data-passport-signers-list": true,
|
|
1118
|
+
children: signers.map((signer) => /* @__PURE__ */ jsx("div", {
|
|
1119
|
+
"data-passport-signer": true,
|
|
1120
|
+
children: signer.label
|
|
1121
|
+
}, signer.id))
|
|
1122
|
+
})]
|
|
1123
|
+
});
|
|
1124
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1125
|
+
ref,
|
|
1126
|
+
...props,
|
|
1127
|
+
children: children(signers)
|
|
1128
|
+
});
|
|
1129
|
+
});
|
|
1130
|
+
PassportSigners.displayName = "Passport.Signers";
|
|
1131
|
+
|
|
1132
|
+
//#endregion
|
|
1133
|
+
//#region src/ui/core/passport/PassportTitle.tsx
|
|
1134
|
+
/**
|
|
1135
|
+
* Passport.Title - Title for the Passport
|
|
1136
|
+
*
|
|
1137
|
+
* Renders a heading with the passport title. Defaults to "Content Credentials"
|
|
1138
|
+
* if no children are provided.
|
|
1139
|
+
*
|
|
1140
|
+
* @example
|
|
1141
|
+
* ```tsx
|
|
1142
|
+
* // Default title
|
|
1143
|
+
* <Passport.Title className="text-lg font-bold" />
|
|
1144
|
+
*
|
|
1145
|
+
* // Custom title
|
|
1146
|
+
* <Passport.Title className="text-lg font-bold">
|
|
1147
|
+
* My Custom Title
|
|
1148
|
+
* </Passport.Title>
|
|
1149
|
+
*
|
|
1150
|
+
* // With asChild
|
|
1151
|
+
* <Passport.Title asChild>
|
|
1152
|
+
* <h1 className="custom-heading">Content Credentials</h1>
|
|
1153
|
+
* </Passport.Title>
|
|
1154
|
+
* ```
|
|
1155
|
+
*/
|
|
1156
|
+
const PassportTitle = forwardRef(({ asChild, children, ...props }, ref) => {
|
|
1157
|
+
const Comp = asChild ? Slot : "h2";
|
|
1158
|
+
const content = children ?? "Content Credentials";
|
|
1159
|
+
return /* @__PURE__ */ jsx(Comp, {
|
|
1160
|
+
ref,
|
|
1161
|
+
id: "passport-title",
|
|
1162
|
+
...props,
|
|
1163
|
+
children: content
|
|
1164
|
+
});
|
|
1165
|
+
});
|
|
1166
|
+
PassportTitle.displayName = "Passport.Title";
|
|
1167
|
+
|
|
1168
|
+
//#endregion
|
|
1169
|
+
//#region src/ui/core/passport/index.ts
|
|
1170
|
+
/**
|
|
1171
|
+
* Passport - Headless compound component for displaying C2PA content credentials
|
|
1172
|
+
*
|
|
1173
|
+
* @example
|
|
1174
|
+
* ```tsx
|
|
1175
|
+
* import { Passport } from "@limboai/react";
|
|
1176
|
+
*
|
|
1177
|
+
* <Passport.Root validation={validation} className="my-tooltip">
|
|
1178
|
+
* <Passport.Header className="flex items-center gap-2">
|
|
1179
|
+
* <Passport.Logo className="w-6 h-6" />
|
|
1180
|
+
* <Passport.Title className="text-lg font-bold" />
|
|
1181
|
+
* </Passport.Header>
|
|
1182
|
+
*
|
|
1183
|
+
* <Passport.Field name="issuer" className="my-field" />
|
|
1184
|
+
* <Passport.Field name="date" className="my-field" />
|
|
1185
|
+
*
|
|
1186
|
+
* <Passport.Actions className="mt-4">
|
|
1187
|
+
* {(actions) => actions.map(a => <span key={a} className="tag">{a}</span>)}
|
|
1188
|
+
* </Passport.Actions>
|
|
1189
|
+
*
|
|
1190
|
+
* <Passport.Identities>
|
|
1191
|
+
* {(identities) => identities.map(id => <MyIdentityCard key={id.provider} {...id} />)}
|
|
1192
|
+
* </Passport.Identities>
|
|
1193
|
+
*
|
|
1194
|
+
* <Passport.Signers>
|
|
1195
|
+
* {(signers) => signers.map(s => <MySignerRow key={s.id} {...s} />)}
|
|
1196
|
+
* </Passport.Signers>
|
|
1197
|
+
*
|
|
1198
|
+
* <Passport.Footer className="mt-4 border-t pt-4">
|
|
1199
|
+
* <Passport.CopyButton asChild>
|
|
1200
|
+
* <button className="btn-primary">Copy JSON</button>
|
|
1201
|
+
* </Passport.CopyButton>
|
|
1202
|
+
* </Passport.Footer>
|
|
1203
|
+
* </Passport.Root>
|
|
1204
|
+
* ```
|
|
1205
|
+
*/
|
|
1206
|
+
const Passport = {
|
|
1207
|
+
Root: PassportRoot,
|
|
1208
|
+
Header: PassportHeader,
|
|
1209
|
+
Title: PassportTitle,
|
|
1210
|
+
Logo: PassportLogo,
|
|
1211
|
+
Field: PassportField,
|
|
1212
|
+
Actions: PassportActions,
|
|
1213
|
+
Identities: PassportIdentities,
|
|
1214
|
+
Signers: PassportSigners,
|
|
1215
|
+
Footer: PassportFooter,
|
|
1216
|
+
CopyButton: PassportCopyButton
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
//#endregion
|
|
1220
|
+
//#region src/ui/default/constants.ts
|
|
1221
|
+
const BadgePosition = {
|
|
1222
|
+
TopRight: "top-right",
|
|
1223
|
+
TopLeft: "top-left",
|
|
1224
|
+
BottomRight: "bottom-right",
|
|
1225
|
+
BottomLeft: "bottom-left"
|
|
1226
|
+
};
|
|
1227
|
+
const PassportVariant$1 = {
|
|
1228
|
+
Basic: "basic",
|
|
1229
|
+
Full: "full"
|
|
1230
|
+
};
|
|
1231
|
+
const Theme = {
|
|
1232
|
+
Light: "light",
|
|
1233
|
+
Dark: "dark"
|
|
1234
|
+
};
|
|
1235
|
+
const wrapperStyle = {
|
|
1236
|
+
position: "relative",
|
|
1237
|
+
display: "inline-block"
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
//#endregion
|
|
1241
|
+
//#region src/ui/default/LimboBadge.tsx
|
|
1242
|
+
const badgeStyles = {
|
|
1243
|
+
light: {
|
|
1244
|
+
backgroundColor: "#ffffff",
|
|
1245
|
+
borderRadius: "9999px",
|
|
1246
|
+
padding: "8px",
|
|
1247
|
+
boxShadow: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
|
|
1248
|
+
cursor: "pointer",
|
|
1249
|
+
border: "1px solid #e5e7eb",
|
|
1250
|
+
display: "flex",
|
|
1251
|
+
alignItems: "center",
|
|
1252
|
+
justifyContent: "center"
|
|
1253
|
+
},
|
|
1254
|
+
dark: {
|
|
1255
|
+
backgroundColor: "#1f2937",
|
|
1256
|
+
borderRadius: "9999px",
|
|
1257
|
+
padding: "8px",
|
|
1258
|
+
boxShadow: "0 10px 15px -3px rgb(0 0 0 / 0.3), 0 4px 6px -4px rgb(0 0 0 / 0.3)",
|
|
1259
|
+
cursor: "pointer",
|
|
1260
|
+
border: "1px solid #374151",
|
|
1261
|
+
display: "flex",
|
|
1262
|
+
alignItems: "center",
|
|
1263
|
+
justifyContent: "center"
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
const badgePositionStyles = {
|
|
1267
|
+
"top-right": {
|
|
1268
|
+
position: "absolute",
|
|
1269
|
+
top: "8px",
|
|
1270
|
+
right: "8px"
|
|
1271
|
+
},
|
|
1272
|
+
"top-left": {
|
|
1273
|
+
position: "absolute",
|
|
1274
|
+
top: "8px",
|
|
1275
|
+
left: "8px"
|
|
1276
|
+
},
|
|
1277
|
+
"bottom-right": {
|
|
1278
|
+
position: "absolute",
|
|
1279
|
+
bottom: "8px",
|
|
1280
|
+
right: "8px"
|
|
1281
|
+
},
|
|
1282
|
+
"bottom-left": {
|
|
1283
|
+
position: "absolute",
|
|
1284
|
+
bottom: "8px",
|
|
1285
|
+
left: "8px"
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
/**
|
|
1289
|
+
* LimboBadge - Pre-styled badge component with CRLogo
|
|
1290
|
+
*
|
|
1291
|
+
* A ready-to-use badge for displaying content credentials status.
|
|
1292
|
+
*/
|
|
1293
|
+
const LimboBadge = ({ position = BadgePosition.TopRight, theme = Theme.Light, onClick, onMouseEnter, onMouseLeave, style }) => {
|
|
1294
|
+
return /* @__PURE__ */ jsx("button", {
|
|
1295
|
+
type: "button",
|
|
1296
|
+
onClick,
|
|
1297
|
+
onMouseEnter,
|
|
1298
|
+
onMouseLeave,
|
|
1299
|
+
style: {
|
|
1300
|
+
...badgeStyles[theme],
|
|
1301
|
+
...badgePositionStyles[position],
|
|
1302
|
+
...style
|
|
1303
|
+
},
|
|
1304
|
+
"aria-label": "View content credentials",
|
|
1305
|
+
children: /* @__PURE__ */ jsx(CRLogo, { size: 16 })
|
|
1306
|
+
});
|
|
1307
|
+
};
|
|
1308
|
+
LimboBadge.displayName = "LimboBadge";
|
|
1309
|
+
|
|
1310
|
+
//#endregion
|
|
1311
|
+
//#region src/ui/media/hooks/useHeicConversion.ts
|
|
1312
|
+
const isHeic = (src) => /\.(heic|heif)$/i.test(src);
|
|
1313
|
+
async function convertHeicToJpeg(file, signal) {
|
|
1314
|
+
if (typeof window === "undefined") return void 0;
|
|
1315
|
+
const { default: heic2any } = await import("heic2any");
|
|
1316
|
+
if (signal.aborted) return void 0;
|
|
1317
|
+
const res = await heic2any({
|
|
1318
|
+
blob: file,
|
|
1319
|
+
toType: "image/jpeg"
|
|
1320
|
+
});
|
|
1321
|
+
const blob = res instanceof Blob ? res : res[0];
|
|
1322
|
+
if (!blob) throw new Error("HEIC to JPEG conversion failed");
|
|
1323
|
+
return blob;
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Hook for automatic HEIC to JPEG conversion
|
|
1327
|
+
*
|
|
1328
|
+
* Uses AbortController for proper cleanup and race condition handling.
|
|
1329
|
+
* Automatically revokes blob URLs on cleanup.
|
|
1330
|
+
*
|
|
1331
|
+
* @example
|
|
1332
|
+
* ```tsx
|
|
1333
|
+
* const { displaySrc, isConverting, error } = useHeicConversion({
|
|
1334
|
+
* src: imageUrl,
|
|
1335
|
+
* enabled: true,
|
|
1336
|
+
* });
|
|
1337
|
+
*
|
|
1338
|
+
* return (
|
|
1339
|
+
* <img
|
|
1340
|
+
* src={displaySrc}
|
|
1341
|
+
* data-converting={isConverting || undefined}
|
|
1342
|
+
* />
|
|
1343
|
+
* );
|
|
1344
|
+
* ```
|
|
1345
|
+
*/
|
|
1346
|
+
function useHeicConversion({ src, enabled = true }) {
|
|
1347
|
+
const [displaySrc, setDisplaySrc] = useState(src);
|
|
1348
|
+
const [isConverting, setIsConverting] = useState(false);
|
|
1349
|
+
const [error, setError] = useState(null);
|
|
1350
|
+
useEffect(() => {
|
|
1351
|
+
if (!src || !enabled || !isHeic(src)) {
|
|
1352
|
+
setDisplaySrc(src);
|
|
1353
|
+
setIsConverting(false);
|
|
1354
|
+
setError(null);
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
const controller = new AbortController();
|
|
1358
|
+
const { signal } = controller;
|
|
1359
|
+
let blobUrl = null;
|
|
1360
|
+
setIsConverting(true);
|
|
1361
|
+
setError(null);
|
|
1362
|
+
const convert = async () => {
|
|
1363
|
+
try {
|
|
1364
|
+
const response = await fetch(src, { signal });
|
|
1365
|
+
if (!response.ok) throw new Error(`Fetch failed: ${response.status}`);
|
|
1366
|
+
const blob = await convertHeicToJpeg(await response.blob(), signal);
|
|
1367
|
+
if (signal.aborted || !blob) return;
|
|
1368
|
+
blobUrl = URL.createObjectURL(blob);
|
|
1369
|
+
setDisplaySrc(blobUrl);
|
|
1370
|
+
setIsConverting(false);
|
|
1371
|
+
} catch (e) {
|
|
1372
|
+
if (signal.aborted) return;
|
|
1373
|
+
setError(e instanceof Error ? e.message : "HEIC conversion failed");
|
|
1374
|
+
setDisplaySrc(src);
|
|
1375
|
+
setIsConverting(false);
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
convert();
|
|
1379
|
+
return () => {
|
|
1380
|
+
controller.abort();
|
|
1381
|
+
if (blobUrl) URL.revokeObjectURL(blobUrl);
|
|
1382
|
+
};
|
|
1383
|
+
}, [src, enabled]);
|
|
1384
|
+
useDebugValue({
|
|
1385
|
+
isConverting,
|
|
1386
|
+
hasError: !!error
|
|
1387
|
+
});
|
|
1388
|
+
return {
|
|
1389
|
+
displaySrc,
|
|
1390
|
+
isConverting,
|
|
1391
|
+
error
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
//#endregion
|
|
1396
|
+
//#region src/ui/media/context/MediaContext.tsx
|
|
1397
|
+
const MediaContext = createContext(null);
|
|
1398
|
+
MediaContext.displayName = "MediaContext";
|
|
1399
|
+
|
|
1400
|
+
//#endregion
|
|
1401
|
+
//#region src/ui/media/hooks/useMediaContext.ts
|
|
1402
|
+
/**
|
|
1403
|
+
* Hook to access Media context
|
|
1404
|
+
*
|
|
1405
|
+
* Use this hook inside MediaImage/MediaVideo render props to access
|
|
1406
|
+
* the media context including validation data and overlay visibility state.
|
|
1407
|
+
*
|
|
1408
|
+
* @example
|
|
1409
|
+
* ```tsx
|
|
1410
|
+
* const { validation, status, isVisible, toggle } = useMediaContext();
|
|
1411
|
+
* ```
|
|
1412
|
+
*/
|
|
1413
|
+
function useMediaContext() {
|
|
1414
|
+
const context = useContext(MediaContext);
|
|
1415
|
+
if (!context) throw new Error("useMediaContext must be used within a MediaImage or MediaVideo component");
|
|
1416
|
+
useDebugValue({
|
|
1417
|
+
status: context.status,
|
|
1418
|
+
isVisible: context.isVisible
|
|
1419
|
+
});
|
|
1420
|
+
return context;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
//#endregion
|
|
1424
|
+
//#region src/ui/media/hooks/useMediaOverlay.ts
|
|
1425
|
+
/**
|
|
1426
|
+
* Shared hook for overlay state management in media components
|
|
1427
|
+
*
|
|
1428
|
+
* Extracts common logic used by both MediaImage and MediaVideo components
|
|
1429
|
+
* for managing tooltip visibility and building render props.
|
|
1430
|
+
*
|
|
1431
|
+
* @example
|
|
1432
|
+
* ```tsx
|
|
1433
|
+
* const { hasCredentials, contextValue, badgeProps, tooltipProps } = useMediaOverlay({
|
|
1434
|
+
* validation,
|
|
1435
|
+
* validationStatus,
|
|
1436
|
+
* hideDelay: 300,
|
|
1437
|
+
* });
|
|
1438
|
+
* ```
|
|
1439
|
+
*/
|
|
1440
|
+
function useMediaOverlay({ validation, validationStatus, hideDelay = 300 }) {
|
|
1441
|
+
const hasCredentials = validation?.has_credentials ?? false;
|
|
1442
|
+
const { isVisible, show, hide, toggle } = useTooltipVisibility({
|
|
1443
|
+
hasCredentials,
|
|
1444
|
+
hideDelay
|
|
1445
|
+
});
|
|
1446
|
+
const contextValue = {
|
|
1447
|
+
validation,
|
|
1448
|
+
status: validationStatus,
|
|
1449
|
+
hasCredentials,
|
|
1450
|
+
isVisible,
|
|
1451
|
+
show,
|
|
1452
|
+
hide,
|
|
1453
|
+
toggle
|
|
1454
|
+
};
|
|
1455
|
+
const badgeProps = {
|
|
1456
|
+
status: validationStatus,
|
|
1457
|
+
hasCredentials,
|
|
1458
|
+
isVisible,
|
|
1459
|
+
show,
|
|
1460
|
+
hide,
|
|
1461
|
+
toggle
|
|
1462
|
+
};
|
|
1463
|
+
const tooltipProps = validation ? {
|
|
1464
|
+
validation,
|
|
1465
|
+
isVisible,
|
|
1466
|
+
onMouseEnter: show,
|
|
1467
|
+
onClose: hide
|
|
1468
|
+
} : null;
|
|
1469
|
+
useDebugValue({
|
|
1470
|
+
isVisible,
|
|
1471
|
+
hasCredentials
|
|
1472
|
+
});
|
|
1473
|
+
return {
|
|
1474
|
+
hasCredentials,
|
|
1475
|
+
contextValue,
|
|
1476
|
+
badgeProps,
|
|
1477
|
+
tooltipProps
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
//#endregion
|
|
1482
|
+
//#region src/ui/media/MediaImage.tsx
|
|
1483
|
+
/**
|
|
1484
|
+
* MediaImage - Headless image component with built-in credential overlay support
|
|
1485
|
+
*
|
|
1486
|
+
* A minimal image component that handles HEIC to JPEG conversion and provides
|
|
1487
|
+
* built-in support for displaying credential badges and tooltips via render props.
|
|
1488
|
+
*
|
|
1489
|
+
* This component is headless - it applies no styles. Users must provide
|
|
1490
|
+
* all styling through className, style props, or render functions.
|
|
1491
|
+
*
|
|
1492
|
+
* @example
|
|
1493
|
+
* ```tsx
|
|
1494
|
+
* // Basic usage - just the image with HEIC support
|
|
1495
|
+
* <MediaImage src="/photo.heic" alt="Photo" className="w-full h-auto" />
|
|
1496
|
+
*
|
|
1497
|
+
* // With built-in overlay (using render props)
|
|
1498
|
+
* <MediaImage
|
|
1499
|
+
* src="/photo.jpg"
|
|
1500
|
+
* alt="Photo"
|
|
1501
|
+
* validation={validation}
|
|
1502
|
+
* validationStatus="success"
|
|
1503
|
+
* wrapperClassName="relative inline-block"
|
|
1504
|
+
* className="w-80 h-60 object-cover rounded-lg"
|
|
1505
|
+
* renderBadge={({ status, hasCredentials, toggle }) => (
|
|
1506
|
+
* <button
|
|
1507
|
+
* onClick={toggle}
|
|
1508
|
+
* className="absolute top-2 right-2 bg-green-500 rounded-full p-2"
|
|
1509
|
+
* >
|
|
1510
|
+
* <CRLogo size={16} />
|
|
1511
|
+
* </button>
|
|
1512
|
+
* )}
|
|
1513
|
+
* renderTooltip={({ validation, isVisible, onClose }) =>
|
|
1514
|
+
* isVisible && (
|
|
1515
|
+
* <div className="absolute top-14 right-0 bg-white shadow-lg rounded-lg p-4">
|
|
1516
|
+
* <Passport.Root validation={validation}>
|
|
1517
|
+
* <Passport.Header>...</Passport.Header>
|
|
1518
|
+
* <Passport.Field name="issuer" />
|
|
1519
|
+
* <Passport.Footer>
|
|
1520
|
+
* <Passport.CopyButton />
|
|
1521
|
+
* </Passport.Footer>
|
|
1522
|
+
* </Passport.Root>
|
|
1523
|
+
* </div>
|
|
1524
|
+
* )
|
|
1525
|
+
* }
|
|
1526
|
+
* />
|
|
1527
|
+
*
|
|
1528
|
+
* // With compound components (external overlay)
|
|
1529
|
+
* <div className="relative">
|
|
1530
|
+
* <MediaImage src="/photo.jpg" alt="Photo" className="w-full" />
|
|
1531
|
+
* <CredentialOverlay.Root validation={validation} status="success">
|
|
1532
|
+
* <CredentialOverlay.Trigger className="absolute top-2 right-2">
|
|
1533
|
+
* <CredentialBadge.Root status="success" hasCredentials={true}>
|
|
1534
|
+
* <CredentialBadge.Verified>
|
|
1535
|
+
* <button className="bg-green-500 p-2 rounded-full">OK</button>
|
|
1536
|
+
* </CredentialBadge.Verified>
|
|
1537
|
+
* </CredentialBadge.Root>
|
|
1538
|
+
* </CredentialOverlay.Trigger>
|
|
1539
|
+
* <CredentialOverlay.Content className="absolute top-12 right-0">
|
|
1540
|
+
* <Passport.Root validation={validation}>...</Passport.Root>
|
|
1541
|
+
* </CredentialOverlay.Content>
|
|
1542
|
+
* </CredentialOverlay.Root>
|
|
1543
|
+
* </div>
|
|
1544
|
+
* ```
|
|
1545
|
+
*/
|
|
1546
|
+
const MediaImage = forwardRef(({ validation = null, validationStatus = "idle", renderBadge, renderTooltip, wrapperClassName, wrapperStyle, hideDelay = 300, enableHeicConversion = true, src, ...imgProps }, ref) => {
|
|
1547
|
+
const { displaySrc, isConverting } = useHeicConversion({
|
|
1548
|
+
src,
|
|
1549
|
+
enabled: enableHeicConversion
|
|
1550
|
+
});
|
|
1551
|
+
const { contextValue, badgeProps, tooltipProps } = useMediaOverlay({
|
|
1552
|
+
validation,
|
|
1553
|
+
validationStatus,
|
|
1554
|
+
hideDelay
|
|
1555
|
+
});
|
|
1556
|
+
const imageElement = /* @__PURE__ */ jsx("img", {
|
|
1557
|
+
ref,
|
|
1558
|
+
...imgProps,
|
|
1559
|
+
src: displaySrc,
|
|
1560
|
+
alt: imgProps.alt || "",
|
|
1561
|
+
"data-converting": isConverting || void 0
|
|
1562
|
+
});
|
|
1563
|
+
if (!renderBadge && !renderTooltip) return imageElement;
|
|
1564
|
+
return /* @__PURE__ */ jsx(MediaContext.Provider, {
|
|
1565
|
+
value: contextValue,
|
|
1566
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1567
|
+
className: wrapperClassName,
|
|
1568
|
+
style: wrapperStyle,
|
|
1569
|
+
children: [
|
|
1570
|
+
imageElement,
|
|
1571
|
+
renderBadge?.(badgeProps),
|
|
1572
|
+
tooltipProps && renderTooltip?.(tooltipProps)
|
|
1573
|
+
]
|
|
1574
|
+
})
|
|
1575
|
+
});
|
|
1576
|
+
});
|
|
1577
|
+
MediaImage.displayName = "MediaImage";
|
|
1578
|
+
|
|
1579
|
+
//#endregion
|
|
1580
|
+
//#region src/ui/media/MediaVideo.tsx
|
|
1581
|
+
/**
|
|
1582
|
+
* MediaVideo - Headless video component with built-in credential overlay support
|
|
1583
|
+
*
|
|
1584
|
+
* A minimal video component for use with C2PA credentials. Provides built-in
|
|
1585
|
+
* support for displaying credential badges and tooltips via render props.
|
|
1586
|
+
*
|
|
1587
|
+
* This component is headless - it applies no styles. Users must provide
|
|
1588
|
+
* all styling through className, style props, or render functions.
|
|
1589
|
+
*
|
|
1590
|
+
* @example
|
|
1591
|
+
* ```tsx
|
|
1592
|
+
* // Basic usage - just the video
|
|
1593
|
+
* <MediaVideo src="/video.mp4" controls className="w-full h-auto" />
|
|
1594
|
+
*
|
|
1595
|
+
* // With built-in overlay (using render props)
|
|
1596
|
+
* <MediaVideo
|
|
1597
|
+
* src="/video.mp4"
|
|
1598
|
+
* controls
|
|
1599
|
+
* validation={validation}
|
|
1600
|
+
* validationStatus="success"
|
|
1601
|
+
* wrapperClassName="relative inline-block"
|
|
1602
|
+
* className="w-80 h-60 object-cover rounded-lg"
|
|
1603
|
+
* renderBadge={({ status, hasCredentials, toggle }) => (
|
|
1604
|
+
* <button
|
|
1605
|
+
* onClick={toggle}
|
|
1606
|
+
* className="absolute top-2 right-2 bg-green-500 rounded-full p-2"
|
|
1607
|
+
* >
|
|
1608
|
+
* <CRLogo size={16} />
|
|
1609
|
+
* </button>
|
|
1610
|
+
* )}
|
|
1611
|
+
* renderTooltip={({ validation, isVisible, onClose }) =>
|
|
1612
|
+
* isVisible && (
|
|
1613
|
+
* <div className="absolute top-14 right-0 bg-white shadow-lg rounded-lg p-4">
|
|
1614
|
+
* <Passport.Root validation={validation}>
|
|
1615
|
+
* <Passport.Header>...</Passport.Header>
|
|
1616
|
+
* <Passport.Field name="issuer" />
|
|
1617
|
+
* <Passport.Footer>
|
|
1618
|
+
* <Passport.CopyButton />
|
|
1619
|
+
* </Passport.Footer>
|
|
1620
|
+
* </Passport.Root>
|
|
1621
|
+
* </div>
|
|
1622
|
+
* )
|
|
1623
|
+
* }
|
|
1624
|
+
* />
|
|
1625
|
+
*
|
|
1626
|
+
* // With compound components (external overlay)
|
|
1627
|
+
* <div className="relative">
|
|
1628
|
+
* <MediaVideo src="/video.mp4" controls className="w-full" />
|
|
1629
|
+
* <CredentialOverlay.Root validation={validation} status="success">
|
|
1630
|
+
* <CredentialOverlay.Trigger className="absolute top-2 right-2">
|
|
1631
|
+
* <CredentialBadge.Root status="success" hasCredentials={true}>
|
|
1632
|
+
* <CredentialBadge.Verified>
|
|
1633
|
+
* <button className="bg-green-500 p-2 rounded-full">OK</button>
|
|
1634
|
+
* </CredentialBadge.Verified>
|
|
1635
|
+
* </CredentialBadge.Root>
|
|
1636
|
+
* </CredentialOverlay.Trigger>
|
|
1637
|
+
* <CredentialOverlay.Content className="absolute top-12 right-0">
|
|
1638
|
+
* <Passport.Root validation={validation}>...</Passport.Root>
|
|
1639
|
+
* </CredentialOverlay.Content>
|
|
1640
|
+
* </CredentialOverlay.Root>
|
|
1641
|
+
* </div>
|
|
1642
|
+
* ```
|
|
1643
|
+
*/
|
|
1644
|
+
const MediaVideo = forwardRef(({ validation = null, validationStatus = "idle", renderBadge, renderTooltip, wrapperClassName, wrapperStyle, hideDelay = 300, ...videoProps }, ref) => {
|
|
1645
|
+
const { contextValue, badgeProps, tooltipProps } = useMediaOverlay({
|
|
1646
|
+
validation,
|
|
1647
|
+
validationStatus,
|
|
1648
|
+
hideDelay
|
|
1649
|
+
});
|
|
1650
|
+
const videoElement = /* @__PURE__ */ jsx("video", {
|
|
1651
|
+
ref,
|
|
1652
|
+
...videoProps
|
|
1653
|
+
});
|
|
1654
|
+
if (!renderBadge && !renderTooltip) return videoElement;
|
|
1655
|
+
return /* @__PURE__ */ jsx(MediaContext.Provider, {
|
|
1656
|
+
value: contextValue,
|
|
1657
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
1658
|
+
className: wrapperClassName,
|
|
1659
|
+
style: wrapperStyle,
|
|
1660
|
+
children: [
|
|
1661
|
+
videoElement,
|
|
1662
|
+
renderBadge?.(badgeProps),
|
|
1663
|
+
tooltipProps && renderTooltip?.(tooltipProps)
|
|
1664
|
+
]
|
|
1665
|
+
})
|
|
1666
|
+
});
|
|
1667
|
+
});
|
|
1668
|
+
MediaVideo.displayName = "MediaVideo";
|
|
1669
|
+
|
|
1670
|
+
//#endregion
|
|
1671
|
+
//#region src/ui/default/LimboPassport.tsx
|
|
1672
|
+
const tooltipPositionStyles = {
|
|
1673
|
+
"top-right": {
|
|
1674
|
+
position: "absolute",
|
|
1675
|
+
top: "56px",
|
|
1676
|
+
right: "0",
|
|
1677
|
+
zIndex: 10
|
|
1678
|
+
},
|
|
1679
|
+
"top-left": {
|
|
1680
|
+
position: "absolute",
|
|
1681
|
+
top: "56px",
|
|
1682
|
+
left: "0",
|
|
1683
|
+
zIndex: 10
|
|
1684
|
+
},
|
|
1685
|
+
"bottom-right": {
|
|
1686
|
+
position: "absolute",
|
|
1687
|
+
bottom: "56px",
|
|
1688
|
+
right: "0",
|
|
1689
|
+
zIndex: 10
|
|
1690
|
+
},
|
|
1691
|
+
"bottom-left": {
|
|
1692
|
+
position: "absolute",
|
|
1693
|
+
bottom: "56px",
|
|
1694
|
+
left: "0",
|
|
1695
|
+
zIndex: 10
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
const passportContainerStyles = {
|
|
1699
|
+
light: {
|
|
1700
|
+
backgroundColor: "#ffffff",
|
|
1701
|
+
border: "1px solid #e5e7eb",
|
|
1702
|
+
borderRadius: "12px",
|
|
1703
|
+
padding: "20px",
|
|
1704
|
+
width: "320px",
|
|
1705
|
+
boxShadow: "0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)",
|
|
1706
|
+
fontFamily: "system-ui, -apple-system, sans-serif"
|
|
1707
|
+
},
|
|
1708
|
+
dark: {
|
|
1709
|
+
backgroundColor: "#111827",
|
|
1710
|
+
border: "1px solid #374151",
|
|
1711
|
+
borderRadius: "12px",
|
|
1712
|
+
padding: "20px",
|
|
1713
|
+
width: "320px",
|
|
1714
|
+
boxShadow: "0 10px 15px -3px rgb(0 0 0 / 0.3), 0 4px 6px -4px rgb(0 0 0 / 0.3)",
|
|
1715
|
+
fontFamily: "system-ui, -apple-system, sans-serif"
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
const fieldLabelStyles = {
|
|
1719
|
+
light: {
|
|
1720
|
+
fontSize: "11px",
|
|
1721
|
+
fontWeight: 500,
|
|
1722
|
+
color: "#6b7280",
|
|
1723
|
+
textTransform: "uppercase",
|
|
1724
|
+
letterSpacing: "0.05em",
|
|
1725
|
+
marginBottom: "4px"
|
|
1726
|
+
},
|
|
1727
|
+
dark: {
|
|
1728
|
+
fontSize: "11px",
|
|
1729
|
+
fontWeight: 500,
|
|
1730
|
+
color: "#9ca3af",
|
|
1731
|
+
textTransform: "uppercase",
|
|
1732
|
+
letterSpacing: "0.05em",
|
|
1733
|
+
marginBottom: "4px"
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1736
|
+
const fieldValueStyles = {
|
|
1737
|
+
light: {
|
|
1738
|
+
fontSize: "13px",
|
|
1739
|
+
color: "#111827"
|
|
1740
|
+
},
|
|
1741
|
+
dark: {
|
|
1742
|
+
fontSize: "13px",
|
|
1743
|
+
color: "#f3f4f6"
|
|
1744
|
+
}
|
|
1745
|
+
};
|
|
1746
|
+
const passportHeaderStyles = {
|
|
1747
|
+
container: {
|
|
1748
|
+
display: "flex",
|
|
1749
|
+
alignItems: "center",
|
|
1750
|
+
gap: "8px",
|
|
1751
|
+
marginBottom: "16px"
|
|
1752
|
+
},
|
|
1753
|
+
logo: {
|
|
1754
|
+
width: "20px",
|
|
1755
|
+
height: "20px"
|
|
1756
|
+
},
|
|
1757
|
+
title: {
|
|
1758
|
+
light: {
|
|
1759
|
+
fontSize: "16px",
|
|
1760
|
+
fontWeight: 600,
|
|
1761
|
+
color: "#111827"
|
|
1762
|
+
},
|
|
1763
|
+
dark: {
|
|
1764
|
+
fontSize: "16px",
|
|
1765
|
+
fontWeight: 600,
|
|
1766
|
+
color: "#ffffff"
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
};
|
|
1770
|
+
const passportFooterStyles = {
|
|
1771
|
+
container: {
|
|
1772
|
+
light: {
|
|
1773
|
+
marginTop: "16px",
|
|
1774
|
+
paddingTop: "16px",
|
|
1775
|
+
borderTop: "1px solid #f3f4f6",
|
|
1776
|
+
display: "flex",
|
|
1777
|
+
justifyContent: "space-between",
|
|
1778
|
+
alignItems: "center"
|
|
1779
|
+
},
|
|
1780
|
+
dark: {
|
|
1781
|
+
marginTop: "16px",
|
|
1782
|
+
paddingTop: "16px",
|
|
1783
|
+
borderTop: "1px solid #374151",
|
|
1784
|
+
display: "flex",
|
|
1785
|
+
justifyContent: "space-between",
|
|
1786
|
+
alignItems: "center"
|
|
1787
|
+
}
|
|
1788
|
+
},
|
|
1789
|
+
copyButton: {
|
|
1790
|
+
light: {
|
|
1791
|
+
display: "inline-flex",
|
|
1792
|
+
alignItems: "center",
|
|
1793
|
+
gap: "6px",
|
|
1794
|
+
fontSize: "13px",
|
|
1795
|
+
fontWeight: 500,
|
|
1796
|
+
color: "#4b5563",
|
|
1797
|
+
cursor: "pointer",
|
|
1798
|
+
backgroundColor: "transparent",
|
|
1799
|
+
border: "none",
|
|
1800
|
+
padding: 0
|
|
1801
|
+
},
|
|
1802
|
+
dark: {
|
|
1803
|
+
display: "inline-flex",
|
|
1804
|
+
alignItems: "center",
|
|
1805
|
+
gap: "6px",
|
|
1806
|
+
fontSize: "13px",
|
|
1807
|
+
fontWeight: 500,
|
|
1808
|
+
color: "#9ca3af",
|
|
1809
|
+
cursor: "pointer",
|
|
1810
|
+
backgroundColor: "transparent",
|
|
1811
|
+
border: "none",
|
|
1812
|
+
padding: 0
|
|
1813
|
+
}
|
|
1814
|
+
},
|
|
1815
|
+
closeButton: {
|
|
1816
|
+
light: {
|
|
1817
|
+
fontSize: "13px",
|
|
1818
|
+
color: "#9ca3af",
|
|
1819
|
+
backgroundColor: "transparent",
|
|
1820
|
+
border: "none",
|
|
1821
|
+
cursor: "pointer"
|
|
1822
|
+
},
|
|
1823
|
+
dark: {
|
|
1824
|
+
fontSize: "13px",
|
|
1825
|
+
color: "#6b7280",
|
|
1826
|
+
backgroundColor: "transparent",
|
|
1827
|
+
border: "none",
|
|
1828
|
+
cursor: "pointer"
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
};
|
|
1832
|
+
const fieldContainerStyle = { marginBottom: "12px" };
|
|
1833
|
+
const identityStyles = {
|
|
1834
|
+
container: {
|
|
1835
|
+
display: "flex",
|
|
1836
|
+
flexDirection: "column",
|
|
1837
|
+
gap: "6px"
|
|
1838
|
+
},
|
|
1839
|
+
item: {
|
|
1840
|
+
display: "flex",
|
|
1841
|
+
alignItems: "center",
|
|
1842
|
+
gap: "8px"
|
|
1843
|
+
},
|
|
1844
|
+
link: {
|
|
1845
|
+
color: "#3b82f6",
|
|
1846
|
+
textDecoration: "none"
|
|
1847
|
+
}
|
|
1848
|
+
};
|
|
1849
|
+
const signerStyles = { container: {
|
|
1850
|
+
display: "flex",
|
|
1851
|
+
flexDirection: "column",
|
|
1852
|
+
gap: "4px"
|
|
1853
|
+
} };
|
|
1854
|
+
const actionTagStyles = {
|
|
1855
|
+
light: {
|
|
1856
|
+
backgroundColor: "#f3f4f6",
|
|
1857
|
+
color: "#374151",
|
|
1858
|
+
fontSize: "12px",
|
|
1859
|
+
padding: "2px 8px",
|
|
1860
|
+
borderRadius: "4px"
|
|
1861
|
+
},
|
|
1862
|
+
dark: {
|
|
1863
|
+
backgroundColor: "#374151",
|
|
1864
|
+
color: "#d1d5db",
|
|
1865
|
+
fontSize: "12px",
|
|
1866
|
+
padding: "2px 8px",
|
|
1867
|
+
borderRadius: "4px"
|
|
1868
|
+
}
|
|
1869
|
+
};
|
|
1870
|
+
const actionsContainerStyle = {
|
|
1871
|
+
display: "flex",
|
|
1872
|
+
flexWrap: "wrap",
|
|
1873
|
+
gap: "6px"
|
|
1874
|
+
};
|
|
1875
|
+
/**
|
|
1876
|
+
* LimboPassport - Pre-styled passport tooltip component
|
|
1877
|
+
*
|
|
1878
|
+
* Renders a passport with configurable variant (basic or full).
|
|
1879
|
+
*/
|
|
1880
|
+
const LimboPassport = ({ validation, isVisible, theme = Theme.Light, position = BadgePosition.TopRight, variant = PassportVariant$1.Basic, showDate = true, onMouseEnter, onClose, style }) => {
|
|
1881
|
+
if (!isVisible) return null;
|
|
1882
|
+
const containerStyle = {
|
|
1883
|
+
...tooltipPositionStyles[position],
|
|
1884
|
+
...style
|
|
1885
|
+
};
|
|
1886
|
+
const isFull = variant === PassportVariant$1.Full;
|
|
1887
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1888
|
+
role: "tooltip",
|
|
1889
|
+
style: containerStyle,
|
|
1890
|
+
onMouseEnter,
|
|
1891
|
+
onMouseLeave: onClose,
|
|
1892
|
+
children: /* @__PURE__ */ jsxs(Passport.Root, {
|
|
1893
|
+
validation,
|
|
1894
|
+
style: passportContainerStyles[theme],
|
|
1895
|
+
children: [
|
|
1896
|
+
/* @__PURE__ */ jsxs(Passport.Header, {
|
|
1897
|
+
style: passportHeaderStyles.container,
|
|
1898
|
+
children: [/* @__PURE__ */ jsx(Passport.Logo, { style: passportHeaderStyles.logo }), /* @__PURE__ */ jsx(Passport.Title, { style: passportHeaderStyles.title[theme] })]
|
|
1899
|
+
}),
|
|
1900
|
+
/* @__PURE__ */ jsx(Passport.Field, {
|
|
1901
|
+
name: "issuer",
|
|
1902
|
+
style: fieldContainerStyle,
|
|
1903
|
+
children: ({ label, value }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
1904
|
+
style: fieldLabelStyles[theme],
|
|
1905
|
+
children: label
|
|
1906
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1907
|
+
style: fieldValueStyles[theme],
|
|
1908
|
+
children: value
|
|
1909
|
+
})] })
|
|
1910
|
+
}),
|
|
1911
|
+
showDate && /* @__PURE__ */ jsx(Passport.Field, {
|
|
1912
|
+
name: "date",
|
|
1913
|
+
style: fieldContainerStyle,
|
|
1914
|
+
children: ({ label, value }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
1915
|
+
style: fieldLabelStyles[theme],
|
|
1916
|
+
children: label
|
|
1917
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1918
|
+
style: fieldValueStyles[theme],
|
|
1919
|
+
children: value
|
|
1920
|
+
})] })
|
|
1921
|
+
}),
|
|
1922
|
+
isFull && /* @__PURE__ */ jsx(Passport.Identities, {
|
|
1923
|
+
style: fieldContainerStyle,
|
|
1924
|
+
children: (identities) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
1925
|
+
style: fieldLabelStyles[theme],
|
|
1926
|
+
children: identities.length === 1 ? "Verified Identity" : `Verified Identities (${identities.length})`
|
|
1927
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1928
|
+
style: identityStyles.container,
|
|
1929
|
+
children: identities.map((identity) => /* @__PURE__ */ jsxs("div", {
|
|
1930
|
+
style: identityStyles.item,
|
|
1931
|
+
children: [identity.provider.toLowerCase().includes("linkedin") && /* @__PURE__ */ jsx(LinkedInIcon, { size: 16 }), identity.uri ? /* @__PURE__ */ jsx("a", {
|
|
1932
|
+
href: identity.uri,
|
|
1933
|
+
target: "_blank",
|
|
1934
|
+
rel: "noopener noreferrer",
|
|
1935
|
+
style: {
|
|
1936
|
+
...fieldValueStyles[theme],
|
|
1937
|
+
...identityStyles.link
|
|
1938
|
+
},
|
|
1939
|
+
children: identity.username
|
|
1940
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
1941
|
+
style: fieldValueStyles[theme],
|
|
1942
|
+
children: identity.username
|
|
1943
|
+
})]
|
|
1944
|
+
}, identity.provider))
|
|
1945
|
+
})] })
|
|
1946
|
+
}),
|
|
1947
|
+
isFull && /* @__PURE__ */ jsx(Passport.Signers, {
|
|
1948
|
+
style: fieldContainerStyle,
|
|
1949
|
+
children: (signers) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
1950
|
+
style: fieldLabelStyles[theme],
|
|
1951
|
+
children: signers.length === 1 ? "Previous Signer" : `Previous Signers (${signers.length})`
|
|
1952
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1953
|
+
style: signerStyles.container,
|
|
1954
|
+
children: signers.map((signer) => /* @__PURE__ */ jsx("div", {
|
|
1955
|
+
style: fieldValueStyles[theme],
|
|
1956
|
+
children: signer.label
|
|
1957
|
+
}, signer.id))
|
|
1958
|
+
})] })
|
|
1959
|
+
}),
|
|
1960
|
+
isFull && /* @__PURE__ */ jsx(Passport.Actions, {
|
|
1961
|
+
style: fieldContainerStyle,
|
|
1962
|
+
children: (actions) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("div", {
|
|
1963
|
+
style: fieldLabelStyles[theme],
|
|
1964
|
+
children: "Actions"
|
|
1965
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
1966
|
+
style: actionsContainerStyle,
|
|
1967
|
+
children: actions.map((action) => /* @__PURE__ */ jsx("span", {
|
|
1968
|
+
style: actionTagStyles[theme],
|
|
1969
|
+
children: action
|
|
1970
|
+
}, action))
|
|
1971
|
+
})] })
|
|
1972
|
+
}),
|
|
1973
|
+
/* @__PURE__ */ jsxs(Passport.Footer, {
|
|
1974
|
+
style: passportFooterStyles.container[theme],
|
|
1975
|
+
children: [/* @__PURE__ */ jsx(Passport.CopyButton, { style: passportFooterStyles.copyButton[theme] }), isFull && onClose && /* @__PURE__ */ jsx("button", {
|
|
1976
|
+
type: "button",
|
|
1977
|
+
onClick: onClose,
|
|
1978
|
+
style: passportFooterStyles.closeButton[theme],
|
|
1979
|
+
children: "Close"
|
|
1980
|
+
})]
|
|
1981
|
+
})
|
|
1982
|
+
]
|
|
1983
|
+
})
|
|
1984
|
+
});
|
|
1985
|
+
};
|
|
1986
|
+
LimboPassport.displayName = "LimboPassport";
|
|
1987
|
+
|
|
1988
|
+
//#endregion
|
|
1989
|
+
//#region src/ui/default/LimboImage.tsx
|
|
1990
|
+
/**
|
|
1991
|
+
* LimboImage - Pre-styled image component with default badge and passport
|
|
1992
|
+
*
|
|
1993
|
+
* A ready-to-use image component that displays content credentials with
|
|
1994
|
+
* a pre-configured badge and passport tooltip.
|
|
1995
|
+
*
|
|
1996
|
+
* @example
|
|
1997
|
+
* ```tsx
|
|
1998
|
+
* // Basic usage with click interaction
|
|
1999
|
+
* <LimboImage
|
|
2000
|
+
* src="/photo.jpg"
|
|
2001
|
+
* alt="Photo"
|
|
2002
|
+
* validation={validation}
|
|
2003
|
+
* validationStatus="success"
|
|
2004
|
+
* />
|
|
2005
|
+
*
|
|
2006
|
+
* // With hover interaction and dark theme
|
|
2007
|
+
* <LimboImage
|
|
2008
|
+
* src="/photo.jpg"
|
|
2009
|
+
* alt="Photo"
|
|
2010
|
+
* validation={validation}
|
|
2011
|
+
* validationStatus="success"
|
|
2012
|
+
* interactionMode="hover"
|
|
2013
|
+
* theme="dark"
|
|
2014
|
+
* badgePosition="bottomRight"
|
|
2015
|
+
* />
|
|
2016
|
+
*
|
|
2017
|
+
* // Full passport variant
|
|
2018
|
+
* <LimboImage
|
|
2019
|
+
* src="/photo.jpg"
|
|
2020
|
+
* alt="Photo"
|
|
2021
|
+
* validation={validation}
|
|
2022
|
+
* validationStatus="success"
|
|
2023
|
+
* passportVariant="full"
|
|
2024
|
+
* />
|
|
2025
|
+
* ```
|
|
2026
|
+
*/
|
|
2027
|
+
const LimboImage = forwardRef(({ validation = null, validationStatus = "idle", badgePosition = BadgePosition.TopRight, theme = Theme.Light, passportVariant = PassportVariant$1.Basic, interactionMode = "click", showDate = true, hideDelay = 300, enableHeicConversion = true, wrapperStyle: customWrapperStyle, ...imgProps }, ref) => {
|
|
2028
|
+
const combinedWrapperStyle = {
|
|
2029
|
+
...wrapperStyle,
|
|
2030
|
+
...customWrapperStyle
|
|
2031
|
+
};
|
|
2032
|
+
const renderBadge = ({ hasCredentials, toggle, show, hide }) => {
|
|
2033
|
+
if (!hasCredentials) return null;
|
|
2034
|
+
return /* @__PURE__ */ jsx(LimboBadge, {
|
|
2035
|
+
position: badgePosition,
|
|
2036
|
+
theme,
|
|
2037
|
+
...interactionMode === "hover" ? {
|
|
2038
|
+
onMouseEnter: show,
|
|
2039
|
+
onMouseLeave: hide
|
|
2040
|
+
} : { onClick: toggle }
|
|
2041
|
+
});
|
|
2042
|
+
};
|
|
2043
|
+
const renderTooltip = ({ validation: tooltipValidation, isVisible, onMouseEnter, onClose }) => /* @__PURE__ */ jsx(LimboPassport, {
|
|
2044
|
+
validation: tooltipValidation,
|
|
2045
|
+
isVisible,
|
|
2046
|
+
theme,
|
|
2047
|
+
position: badgePosition,
|
|
2048
|
+
variant: passportVariant,
|
|
2049
|
+
showDate,
|
|
2050
|
+
onMouseEnter: interactionMode === "hover" ? onMouseEnter : void 0,
|
|
2051
|
+
onClose
|
|
2052
|
+
});
|
|
2053
|
+
return /* @__PURE__ */ jsx(MediaImage, {
|
|
2054
|
+
ref,
|
|
2055
|
+
...imgProps,
|
|
2056
|
+
validation,
|
|
2057
|
+
validationStatus,
|
|
2058
|
+
hideDelay,
|
|
2059
|
+
enableHeicConversion,
|
|
2060
|
+
wrapperStyle: combinedWrapperStyle,
|
|
2061
|
+
renderBadge,
|
|
2062
|
+
renderTooltip
|
|
2063
|
+
});
|
|
2064
|
+
});
|
|
2065
|
+
LimboImage.displayName = "LimboImage";
|
|
2066
|
+
|
|
2067
|
+
//#endregion
|
|
2068
|
+
//#region src/ui/default/LimboVideo.tsx
|
|
2069
|
+
/**
|
|
2070
|
+
* LimboVideo - Pre-styled video component with default badge and passport
|
|
2071
|
+
*
|
|
2072
|
+
* A ready-to-use video component that displays content credentials with
|
|
2073
|
+
* a pre-configured badge and passport tooltip.
|
|
2074
|
+
*
|
|
2075
|
+
* @example
|
|
2076
|
+
* ```tsx
|
|
2077
|
+
* // Basic usage with click interaction
|
|
2078
|
+
* <LimboVideo
|
|
2079
|
+
* src="/video.mp4"
|
|
2080
|
+
* controls
|
|
2081
|
+
* validation={validation}
|
|
2082
|
+
* validationStatus="success"
|
|
2083
|
+
* />
|
|
2084
|
+
*
|
|
2085
|
+
* // With hover interaction and dark theme
|
|
2086
|
+
* <LimboVideo
|
|
2087
|
+
* src="/video.mp4"
|
|
2088
|
+
* controls
|
|
2089
|
+
* validation={validation}
|
|
2090
|
+
* validationStatus="success"
|
|
2091
|
+
* interactionMode="hover"
|
|
2092
|
+
* theme="dark"
|
|
2093
|
+
* badgePosition="bottomRight"
|
|
2094
|
+
* />
|
|
2095
|
+
*
|
|
2096
|
+
* // Full passport variant
|
|
2097
|
+
* <LimboVideo
|
|
2098
|
+
* src="/video.mp4"
|
|
2099
|
+
* controls
|
|
2100
|
+
* validation={validation}
|
|
2101
|
+
* validationStatus="success"
|
|
2102
|
+
* passportVariant="full"
|
|
2103
|
+
* />
|
|
2104
|
+
* ```
|
|
2105
|
+
*/
|
|
2106
|
+
const LimboVideo = forwardRef(({ validation = null, validationStatus = "idle", badgePosition = BadgePosition.TopRight, theme = Theme.Light, passportVariant = PassportVariant$1.Basic, interactionMode = "click", showDate = true, hideDelay = 300, wrapperStyle: customWrapperStyle, ...videoProps }, ref) => {
|
|
2107
|
+
const combinedWrapperStyle = {
|
|
2108
|
+
...wrapperStyle,
|
|
2109
|
+
...customWrapperStyle
|
|
2110
|
+
};
|
|
2111
|
+
const renderBadge = ({ hasCredentials, toggle, show, hide }) => {
|
|
2112
|
+
if (!hasCredentials) return null;
|
|
2113
|
+
return /* @__PURE__ */ jsx(LimboBadge, {
|
|
2114
|
+
position: badgePosition,
|
|
2115
|
+
theme,
|
|
2116
|
+
...interactionMode === "hover" ? {
|
|
2117
|
+
onMouseEnter: show,
|
|
2118
|
+
onMouseLeave: hide
|
|
2119
|
+
} : { onClick: toggle }
|
|
2120
|
+
});
|
|
2121
|
+
};
|
|
2122
|
+
const renderTooltip = ({ validation: tooltipValidation, isVisible, onMouseEnter, onClose }) => /* @__PURE__ */ jsx(LimboPassport, {
|
|
2123
|
+
validation: tooltipValidation,
|
|
2124
|
+
isVisible,
|
|
2125
|
+
theme,
|
|
2126
|
+
position: badgePosition,
|
|
2127
|
+
variant: passportVariant,
|
|
2128
|
+
showDate,
|
|
2129
|
+
onMouseEnter: interactionMode === "hover" ? onMouseEnter : void 0,
|
|
2130
|
+
onClose
|
|
2131
|
+
});
|
|
2132
|
+
return /* @__PURE__ */ jsx(MediaVideo, {
|
|
2133
|
+
ref,
|
|
2134
|
+
...videoProps,
|
|
2135
|
+
validation,
|
|
2136
|
+
validationStatus,
|
|
2137
|
+
hideDelay,
|
|
2138
|
+
wrapperStyle: combinedWrapperStyle,
|
|
2139
|
+
renderBadge,
|
|
2140
|
+
renderTooltip
|
|
2141
|
+
});
|
|
2142
|
+
});
|
|
2143
|
+
LimboVideo.displayName = "LimboVideo";
|
|
2144
|
+
|
|
2145
|
+
//#endregion
|
|
2146
|
+
export { BadgePosition, CRLogo, CredentialBadge, CredentialOverlay, LimboBadge, LimboImage, LimboPassport, LimboVideo, LinkedInIcon, MediaImage, MediaVideo, Passport, PassportVariant, Theme, ValidationStatus, useCredentialBadgeContext, useCredentialOverlayContext, useHeicConversion, useMediaContext, usePassportContext, usePassportData, useTooltipVisibility };
|
|
2147
|
+
//# sourceMappingURL=index.mjs.map
|