@particle-academy/react-fancy 5.44.0 → 5.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/dist/chunk-VDHHWSMT.js +276 -0
- package/dist/chunk-VDHHWSMT.js.map +1 -0
- package/dist/chunk-XL62UDVX.cjs +278 -0
- package/dist/chunk-XL62UDVX.cjs.map +1 -0
- package/dist/faux-client.cjs +3 -2
- package/dist/faux-client.d.cts +26 -0
- package/dist/faux-client.d.ts +26 -0
- package/dist/faux-client.js +2 -1
- package/dist/index.cjs +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-FA4IG5RB.js +0 -100
- package/dist/chunk-FA4IG5RB.js.map +0 -1
- package/dist/chunk-WCWZADR2.cjs +0 -102
- package/dist/chunk-WCWZADR2.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [5.45.1] - 2026-10-19
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **`FauxClient`'s controls stay out of sight until wanted.** They are hidden,
|
|
19
|
+
and fade in when the pointer is over the frame or focus is inside it; on a
|
|
20
|
+
touch screen, which has no hover, they are always shown. In a browser frame
|
|
21
|
+
they sit inline in the address bar as ghost icons; on a device or bare frame
|
|
22
|
+
they are a slim pill in the top-right corner, leaving the bottom corners to
|
|
23
|
+
the content (a slide viewer's page count lives there). 5.45.0 drew them as a
|
|
24
|
+
bordered box over the content's bottom-right corner, permanently.
|
|
25
|
+
**Nothing to do:** same props, same `data-*` handles.
|
|
26
|
+
|
|
27
|
+
## [5.45.0] - 2026-10-19
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`FauxClient` zoom and full screen**, for content that has to be read rather
|
|
32
|
+
than glanced at, such as a slide or a sheet shrunk to fit a panel.
|
|
33
|
+
`<FauxClient variant="device" controls>` adds a zoom bar (−, percentage, +;
|
|
34
|
+
the same bar `ImageViewer` has) and a full-screen button in the frame's
|
|
35
|
+
corner. Zooming keeps the frame its size and scrolls the content inside it.
|
|
36
|
+
The zoom is controllable (`zoom` / `defaultZoom` / `onZoomChange`, bounded by
|
|
37
|
+
`minZoom` / `maxZoom`) so an agent can read and set it. Full screen follows
|
|
38
|
+
the browser's own state: Esc leaves the button telling the truth, and
|
|
39
|
+
`onFullscreenChange` and `data-fullscreen` report it.
|
|
40
|
+
**Nothing to do** unless you want them: `controls` defaults to `false`, and a
|
|
41
|
+
frame without it renders as before.
|
|
42
|
+
|
|
14
43
|
## [5.44.0] - 2026-10-19
|
|
15
44
|
|
|
16
45
|
### Added
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { useControllableState } from './chunk-EDYAXGUM.js';
|
|
2
|
+
import { cn } from './chunk-MMW2JGKZ.js';
|
|
3
|
+
import { forwardRef, useRef, useState, useLayoutEffect, useEffect, useCallback } from 'react';
|
|
4
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var DOT_COLORS = ["bg-rose-300", "bg-amber-300", "bg-green-300"];
|
|
7
|
+
var ZOOM_STEP = 1.25;
|
|
8
|
+
var clamp = (n, min, max) => Math.min(max, Math.max(min, n));
|
|
9
|
+
var FauxClient = forwardRef(
|
|
10
|
+
({
|
|
11
|
+
variant = "browser",
|
|
12
|
+
url,
|
|
13
|
+
meta,
|
|
14
|
+
dots = true,
|
|
15
|
+
width,
|
|
16
|
+
scale = "fit",
|
|
17
|
+
controls = false,
|
|
18
|
+
zoom: controlledZoom,
|
|
19
|
+
defaultZoom = 1,
|
|
20
|
+
onZoomChange,
|
|
21
|
+
minZoom = 0.5,
|
|
22
|
+
maxZoom = 4,
|
|
23
|
+
onFullscreenChange,
|
|
24
|
+
className,
|
|
25
|
+
barClassName,
|
|
26
|
+
bodyClassName,
|
|
27
|
+
children,
|
|
28
|
+
...rest
|
|
29
|
+
}, ref) => {
|
|
30
|
+
const scaled = typeof width === "number";
|
|
31
|
+
const rootRef = useRef(null);
|
|
32
|
+
const viewportRef = useRef(null);
|
|
33
|
+
const stageRef = useRef(null);
|
|
34
|
+
const [fit, setFit] = useState(typeof scale === "number" ? scale : 1);
|
|
35
|
+
const [naturalHeight, setNaturalHeight] = useState(void 0);
|
|
36
|
+
const showZoom = controls === true || typeof controls === "object" && controls.zoom !== false;
|
|
37
|
+
const showFullscreen = controls === true || typeof controls === "object" && controls.fullscreen !== false;
|
|
38
|
+
const [zoom, setZoom] = useControllableState(controlledZoom, defaultZoom, onZoomChange);
|
|
39
|
+
const [fullscreen, setFullscreen] = useState(false);
|
|
40
|
+
useLayoutEffect(() => {
|
|
41
|
+
if (!scaled) return;
|
|
42
|
+
const viewport = viewportRef.current;
|
|
43
|
+
const stage = stageRef.current;
|
|
44
|
+
if (!viewport || !stage) return;
|
|
45
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
46
|
+
const measure = () => {
|
|
47
|
+
const avail = viewport.clientWidth;
|
|
48
|
+
setFit(scale === "fit" ? width && width > 0 ? avail / width : 1 : scale);
|
|
49
|
+
setNaturalHeight(stage.offsetHeight);
|
|
50
|
+
};
|
|
51
|
+
measure();
|
|
52
|
+
const ro = new ResizeObserver(measure);
|
|
53
|
+
ro.observe(viewport);
|
|
54
|
+
ro.observe(stage);
|
|
55
|
+
return () => ro.disconnect();
|
|
56
|
+
}, [scaled, scale, width]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (!showFullscreen || typeof document === "undefined") return;
|
|
59
|
+
const sync = () => {
|
|
60
|
+
const on = document.fullscreenElement != null && document.fullscreenElement === rootRef.current;
|
|
61
|
+
setFullscreen(on);
|
|
62
|
+
onFullscreenChange?.(on);
|
|
63
|
+
};
|
|
64
|
+
document.addEventListener("fullscreenchange", sync);
|
|
65
|
+
return () => document.removeEventListener("fullscreenchange", sync);
|
|
66
|
+
}, [showFullscreen, onFullscreenChange]);
|
|
67
|
+
const toggleFullscreen = useCallback(() => {
|
|
68
|
+
const el = rootRef.current;
|
|
69
|
+
if (!el || typeof document === "undefined") return;
|
|
70
|
+
if (document.fullscreenElement === el) void document.exitFullscreen?.();
|
|
71
|
+
else void el.requestFullscreen?.();
|
|
72
|
+
}, []);
|
|
73
|
+
const zoomBy = (factor) => {
|
|
74
|
+
setZoom(clamp(Math.round(zoom * factor * 100) / 100, minZoom, maxZoom));
|
|
75
|
+
};
|
|
76
|
+
const setRefs = (el) => {
|
|
77
|
+
rootRef.current = el;
|
|
78
|
+
if (typeof ref === "function") ref(el);
|
|
79
|
+
else if (ref) ref.current = el;
|
|
80
|
+
};
|
|
81
|
+
const isBrowser = variant === "browser";
|
|
82
|
+
const isDevice = variant === "device";
|
|
83
|
+
const effective = fit * zoom;
|
|
84
|
+
const zoomed = zoom !== 1;
|
|
85
|
+
const hasControls = showZoom || showFullscreen;
|
|
86
|
+
const controlsBar = hasControls ? /* @__PURE__ */ jsxs(
|
|
87
|
+
"div",
|
|
88
|
+
{
|
|
89
|
+
"data-react-fancy-faux-client-controls": "",
|
|
90
|
+
className: cn(
|
|
91
|
+
"pointer-events-auto flex shrink-0 items-center gap-px opacity-0 transition-opacity duration-150",
|
|
92
|
+
"group-hover/faux-client:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100",
|
|
93
|
+
isBrowser ? "-my-1 text-zinc-400 dark:text-zinc-500" : "rounded-full bg-zinc-900/60 px-1 py-0.5 text-white/80 shadow-sm backdrop-blur-sm"
|
|
94
|
+
),
|
|
95
|
+
children: [
|
|
96
|
+
showZoom && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
97
|
+
/* @__PURE__ */ jsx(
|
|
98
|
+
"button",
|
|
99
|
+
{
|
|
100
|
+
type: "button",
|
|
101
|
+
"data-react-fancy-faux-client-zoom-out": "",
|
|
102
|
+
"aria-label": "Zoom out",
|
|
103
|
+
title: "Zoom out",
|
|
104
|
+
disabled: zoom <= minZoom,
|
|
105
|
+
onClick: () => zoomBy(1 / ZOOM_STEP),
|
|
106
|
+
className: controlButton(isBrowser),
|
|
107
|
+
children: /* @__PURE__ */ jsx(ZoomOutIcon, {})
|
|
108
|
+
}
|
|
109
|
+
),
|
|
110
|
+
/* @__PURE__ */ jsxs(
|
|
111
|
+
"button",
|
|
112
|
+
{
|
|
113
|
+
type: "button",
|
|
114
|
+
"data-react-fancy-faux-client-zoom-reset": "",
|
|
115
|
+
"aria-label": "Reset zoom",
|
|
116
|
+
title: "Reset zoom",
|
|
117
|
+
onClick: () => setZoom(1),
|
|
118
|
+
className: cn(controlButton(isBrowser), "w-auto min-w-[2.25rem] px-1 font-sans text-[10.5px] tabular-nums"),
|
|
119
|
+
children: [
|
|
120
|
+
Math.round(zoom * 100),
|
|
121
|
+
"%"
|
|
122
|
+
]
|
|
123
|
+
}
|
|
124
|
+
),
|
|
125
|
+
/* @__PURE__ */ jsx(
|
|
126
|
+
"button",
|
|
127
|
+
{
|
|
128
|
+
type: "button",
|
|
129
|
+
"data-react-fancy-faux-client-zoom-in": "",
|
|
130
|
+
"aria-label": "Zoom in",
|
|
131
|
+
title: "Zoom in",
|
|
132
|
+
disabled: zoom >= maxZoom,
|
|
133
|
+
onClick: () => zoomBy(ZOOM_STEP),
|
|
134
|
+
className: controlButton(isBrowser),
|
|
135
|
+
children: /* @__PURE__ */ jsx(ZoomInIcon, {})
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
] }),
|
|
139
|
+
showFullscreen && /* @__PURE__ */ jsx(
|
|
140
|
+
"button",
|
|
141
|
+
{
|
|
142
|
+
type: "button",
|
|
143
|
+
"data-react-fancy-faux-client-fullscreen": "",
|
|
144
|
+
"aria-label": fullscreen ? "Exit full screen" : "Full screen",
|
|
145
|
+
"aria-pressed": fullscreen,
|
|
146
|
+
title: fullscreen ? "Exit full screen" : "Full screen",
|
|
147
|
+
onClick: toggleFullscreen,
|
|
148
|
+
className: controlButton(isBrowser),
|
|
149
|
+
children: fullscreen ? /* @__PURE__ */ jsx(ExitFullscreenIcon, {}) : /* @__PURE__ */ jsx(FullscreenIcon, {})
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
) : null;
|
|
155
|
+
return /* @__PURE__ */ jsxs(
|
|
156
|
+
"div",
|
|
157
|
+
{
|
|
158
|
+
ref: setRefs,
|
|
159
|
+
"data-react-fancy-faux-client": "",
|
|
160
|
+
"data-fullscreen": showFullscreen ? String(fullscreen) : void 0,
|
|
161
|
+
className: cn(
|
|
162
|
+
"group/faux-client relative overflow-hidden bg-white dark:bg-zinc-900",
|
|
163
|
+
isBrowser && "rounded-2xl border border-zinc-200 shadow-xl dark:border-zinc-800",
|
|
164
|
+
isDevice && "rounded-[2rem] border-[10px] border-zinc-800 shadow-2xl dark:border-zinc-700",
|
|
165
|
+
variant === "bare" && "rounded-lg border border-zinc-200 dark:border-zinc-800",
|
|
166
|
+
// Full screen: the frame fills the screen and its body takes the room.
|
|
167
|
+
fullscreen && "flex flex-col rounded-none",
|
|
168
|
+
className
|
|
169
|
+
),
|
|
170
|
+
...rest,
|
|
171
|
+
children: [
|
|
172
|
+
isBrowser && /* @__PURE__ */ jsxs(
|
|
173
|
+
"div",
|
|
174
|
+
{
|
|
175
|
+
className: cn(
|
|
176
|
+
"flex items-center gap-2 border-b border-zinc-200 bg-zinc-50 px-3.5 py-2.5 font-mono text-[11.5px] text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400",
|
|
177
|
+
barClassName
|
|
178
|
+
),
|
|
179
|
+
children: [
|
|
180
|
+
dots && /* @__PURE__ */ jsx("div", { className: "mr-1.5 flex gap-[5px]", children: DOT_COLORS.map((color, i) => /* @__PURE__ */ jsx("span", { className: cn("h-[9px] w-[9px] rounded-full", color) }, i)) }),
|
|
181
|
+
url != null && /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: url }),
|
|
182
|
+
meta != null && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-zinc-400 dark:text-zinc-500", children: meta }),
|
|
183
|
+
hasControls && /* @__PURE__ */ jsx("div", { className: cn(url == null && meta == null && "ml-auto"), children: controlsBar })
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
),
|
|
187
|
+
/* @__PURE__ */ jsx(
|
|
188
|
+
"div",
|
|
189
|
+
{
|
|
190
|
+
ref: viewportRef,
|
|
191
|
+
className: cn(
|
|
192
|
+
"relative",
|
|
193
|
+
// Zoomed in, the content is bigger than the frame: scroll to it.
|
|
194
|
+
zoomed ? "overflow-auto" : "overflow-hidden",
|
|
195
|
+
fullscreen && "min-h-0 flex-1",
|
|
196
|
+
bodyClassName
|
|
197
|
+
),
|
|
198
|
+
style: scaled && !fullscreen && naturalHeight !== void 0 ? { height: naturalHeight * fit } : void 0,
|
|
199
|
+
children: scaled ? (
|
|
200
|
+
// A sizer the size of the zoomed content, so the scrollbars are right.
|
|
201
|
+
/* @__PURE__ */ jsx(
|
|
202
|
+
"div",
|
|
203
|
+
{
|
|
204
|
+
style: naturalHeight !== void 0 && width !== void 0 ? { width: width * effective, height: naturalHeight * effective, position: "relative" } : void 0,
|
|
205
|
+
children: /* @__PURE__ */ jsx(
|
|
206
|
+
"div",
|
|
207
|
+
{
|
|
208
|
+
ref: stageRef,
|
|
209
|
+
"data-react-fancy-faux-client-content": "",
|
|
210
|
+
className: "absolute left-0 top-0 origin-top-left",
|
|
211
|
+
style: { width, transform: `scale(${effective})` },
|
|
212
|
+
children
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
) : /* @__PURE__ */ jsx(
|
|
218
|
+
"div",
|
|
219
|
+
{
|
|
220
|
+
"data-react-fancy-faux-client-content": "",
|
|
221
|
+
className: cn(fullscreen && "h-full"),
|
|
222
|
+
style: zoomed ? { zoom: String(zoom) } : void 0,
|
|
223
|
+
children
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
),
|
|
228
|
+
hasControls && !isBrowser && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute right-2 top-2 z-10", children: controlsBar })
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
FauxClient.displayName = "FauxClient";
|
|
235
|
+
function controlButton(inBar) {
|
|
236
|
+
return cn(
|
|
237
|
+
"flex h-6 w-6 items-center justify-center rounded-full transition-colors disabled:pointer-events-none disabled:opacity-30",
|
|
238
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/50",
|
|
239
|
+
inBar ? "hover:bg-zinc-900/5 hover:text-zinc-700 dark:hover:bg-white/10 dark:hover:text-zinc-200" : "hover:bg-white/15 hover:text-white"
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
function ZoomOutIcon() {
|
|
243
|
+
return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
244
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
245
|
+
/* @__PURE__ */ jsx("path", { d: "m20 20-3.5-3.5" }),
|
|
246
|
+
/* @__PURE__ */ jsx("path", { d: "M8 11h6" })
|
|
247
|
+
] });
|
|
248
|
+
}
|
|
249
|
+
function ZoomInIcon() {
|
|
250
|
+
return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
251
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
252
|
+
/* @__PURE__ */ jsx("path", { d: "m20 20-3.5-3.5" }),
|
|
253
|
+
/* @__PURE__ */ jsx("path", { d: "M8 11h6" }),
|
|
254
|
+
/* @__PURE__ */ jsx("path", { d: "M11 8v6" })
|
|
255
|
+
] });
|
|
256
|
+
}
|
|
257
|
+
function FullscreenIcon() {
|
|
258
|
+
return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
259
|
+
/* @__PURE__ */ jsx("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
|
|
260
|
+
/* @__PURE__ */ jsx("path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }),
|
|
261
|
+
/* @__PURE__ */ jsx("path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }),
|
|
262
|
+
/* @__PURE__ */ jsx("path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" })
|
|
263
|
+
] });
|
|
264
|
+
}
|
|
265
|
+
function ExitFullscreenIcon() {
|
|
266
|
+
return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
267
|
+
/* @__PURE__ */ jsx("path", { d: "M8 3v3a2 2 0 0 1-2 2H3" }),
|
|
268
|
+
/* @__PURE__ */ jsx("path", { d: "M21 8h-3a2 2 0 0 1-2-2V3" }),
|
|
269
|
+
/* @__PURE__ */ jsx("path", { d: "M3 16h3a2 2 0 0 1 2 2v3" }),
|
|
270
|
+
/* @__PURE__ */ jsx("path", { d: "M16 21v-3a2 2 0 0 1 2-2h3" })
|
|
271
|
+
] });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export { FauxClient };
|
|
275
|
+
//# sourceMappingURL=chunk-VDHHWSMT.js.map
|
|
276
|
+
//# sourceMappingURL=chunk-VDHHWSMT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/FauxClient/FauxClient.tsx"],"names":[],"mappings":"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","file":"chunk-VDHHWSMT.js","sourcesContent":["import { forwardRef, useCallback, useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useControllableState } from \"../../hooks/use-controllable-state\";\nimport type { FauxClientProps } from \"./FauxClient.types\";\n\nconst DOT_COLORS = [\"bg-rose-300\", \"bg-amber-300\", \"bg-green-300\"];\n\n/** One click of the zoom bar. Matches `ImageViewer`, so the kit zooms one way. */\nconst ZOOM_STEP = 1.25;\n\nconst clamp = (n: number, min: number, max: number) => Math.min(max, Math.max(min, n));\n\n/**\n * A frame that mimics a browser window or device, rendering **real UI** inside.\n * With a logical `width` + `scale=\"fit\"` it renders a full-size page/app and\n * scales it down to fit any container (live thumbnails, device mockups) — the\n * content is real and interactive, just zoomed.\n *\n * Omit `width` for chrome around natural-size content (e.g. a code/preview card).\n *\n * `controls` adds a zoom bar and a full-screen button, for content that has to be\n * READ rather than glanced at: a slide or a sheet shrunk to fit a panel.\n */\nexport const FauxClient = forwardRef<HTMLDivElement, FauxClientProps>(\n (\n {\n variant = \"browser\",\n url,\n meta,\n dots = true,\n width,\n scale = \"fit\",\n controls = false,\n zoom: controlledZoom,\n defaultZoom = 1,\n onZoomChange,\n minZoom = 0.5,\n maxZoom = 4,\n onFullscreenChange,\n className,\n barClassName,\n bodyClassName,\n children,\n ...rest\n },\n ref,\n ) => {\n const scaled = typeof width === \"number\";\n const rootRef = useRef<HTMLDivElement | null>(null);\n const viewportRef = useRef<HTMLDivElement>(null);\n const stageRef = useRef<HTMLDivElement>(null);\n const [fit, setFit] = useState(typeof scale === \"number\" ? scale : 1);\n const [naturalHeight, setNaturalHeight] = useState<number | undefined>(undefined);\n\n const showZoom = controls === true || (typeof controls === \"object\" && controls.zoom !== false);\n const showFullscreen = controls === true || (typeof controls === \"object\" && controls.fullscreen !== false);\n const [zoom, setZoom] = useControllableState(controlledZoom, defaultZoom, onZoomChange);\n const [fullscreen, setFullscreen] = useState(false);\n\n useLayoutEffect(() => {\n if (!scaled) return;\n const viewport = viewportRef.current;\n const stage = stageRef.current;\n if (!viewport || !stage) return;\n if (typeof ResizeObserver === \"undefined\") return;\n\n const measure = () => {\n const avail = viewport.clientWidth;\n setFit(scale === \"fit\" ? (width && width > 0 ? avail / width : 1) : scale);\n // offsetHeight is the pre-transform layout height (CSS transforms don't affect it).\n setNaturalHeight(stage.offsetHeight);\n };\n\n measure();\n const ro = new ResizeObserver(measure);\n ro.observe(viewport);\n ro.observe(stage);\n return () => ro.disconnect();\n }, [scaled, scale, width]);\n\n // Full screen is the browser's state, not ours: follow it, so Esc and the\n // browser's own exit leave the button telling the truth.\n useEffect(() => {\n if (!showFullscreen || typeof document === \"undefined\") return;\n const sync = () => {\n const on = document.fullscreenElement != null && document.fullscreenElement === rootRef.current;\n setFullscreen(on);\n onFullscreenChange?.(on);\n };\n document.addEventListener(\"fullscreenchange\", sync);\n return () => document.removeEventListener(\"fullscreenchange\", sync);\n }, [showFullscreen, onFullscreenChange]);\n\n const toggleFullscreen = useCallback(() => {\n const el = rootRef.current;\n if (!el || typeof document === \"undefined\") return;\n if (document.fullscreenElement === el) void document.exitFullscreen?.();\n else void el.requestFullscreen?.();\n }, []);\n\n const zoomBy = (factor: number) => {\n // Rounded so ten clicks in and ten out land back on 100%, not 99.99%.\n setZoom(clamp(Math.round(zoom * factor * 100) / 100, minZoom, maxZoom));\n };\n\n const setRefs = (el: HTMLDivElement | null) => {\n rootRef.current = el;\n if (typeof ref === \"function\") ref(el);\n else if (ref) ref.current = el;\n };\n\n const isBrowser = variant === \"browser\";\n const isDevice = variant === \"device\";\n const effective = fit * zoom;\n const zoomed = zoom !== 1;\n const hasControls = showZoom || showFullscreen;\n\n /*\n * Out of the way until wanted: hidden, and revealed when the pointer is over\n * the frame or focus is inside it (keyboard). Always shown where there is no\n * hover to reveal it (touch), or it could never be reached.\n *\n * In the browser chrome it sits in the address bar, inline. On a device or a\n * bare frame there is no chrome, so it floats top-right as a slim strip: the\n * bottom corners belong to the content (a slide viewer's page count lives\n * there).\n */\n const controlsBar = hasControls ? (\n <div\n data-react-fancy-faux-client-controls=\"\"\n className={cn(\n \"pointer-events-auto flex shrink-0 items-center gap-px opacity-0 transition-opacity duration-150\",\n \"group-hover/faux-client:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100\",\n isBrowser\n ? \"-my-1 text-zinc-400 dark:text-zinc-500\"\n : \"rounded-full bg-zinc-900/60 px-1 py-0.5 text-white/80 shadow-sm backdrop-blur-sm\",\n )}\n >\n {showZoom && (\n <>\n <button\n type=\"button\"\n data-react-fancy-faux-client-zoom-out=\"\"\n aria-label=\"Zoom out\"\n title=\"Zoom out\"\n disabled={zoom <= minZoom}\n onClick={() => zoomBy(1 / ZOOM_STEP)}\n className={controlButton(isBrowser)}\n >\n <ZoomOutIcon />\n </button>\n <button\n type=\"button\"\n data-react-fancy-faux-client-zoom-reset=\"\"\n aria-label=\"Reset zoom\"\n title=\"Reset zoom\"\n onClick={() => setZoom(1)}\n className={cn(controlButton(isBrowser), \"w-auto min-w-[2.25rem] px-1 font-sans text-[10.5px] tabular-nums\")}\n >\n {Math.round(zoom * 100)}%\n </button>\n <button\n type=\"button\"\n data-react-fancy-faux-client-zoom-in=\"\"\n aria-label=\"Zoom in\"\n title=\"Zoom in\"\n disabled={zoom >= maxZoom}\n onClick={() => zoomBy(ZOOM_STEP)}\n className={controlButton(isBrowser)}\n >\n <ZoomInIcon />\n </button>\n </>\n )}\n {showFullscreen && (\n <button\n type=\"button\"\n data-react-fancy-faux-client-fullscreen=\"\"\n aria-label={fullscreen ? \"Exit full screen\" : \"Full screen\"}\n aria-pressed={fullscreen}\n title={fullscreen ? \"Exit full screen\" : \"Full screen\"}\n onClick={toggleFullscreen}\n className={controlButton(isBrowser)}\n >\n {fullscreen ? <ExitFullscreenIcon /> : <FullscreenIcon />}\n </button>\n )}\n </div>\n ) : null;\n\n return (\n <div\n ref={setRefs}\n data-react-fancy-faux-client=\"\"\n data-fullscreen={showFullscreen ? String(fullscreen) : undefined}\n className={cn(\n \"group/faux-client relative overflow-hidden bg-white dark:bg-zinc-900\",\n isBrowser && \"rounded-2xl border border-zinc-200 shadow-xl dark:border-zinc-800\",\n isDevice && \"rounded-[2rem] border-[10px] border-zinc-800 shadow-2xl dark:border-zinc-700\",\n variant === \"bare\" && \"rounded-lg border border-zinc-200 dark:border-zinc-800\",\n // Full screen: the frame fills the screen and its body takes the room.\n fullscreen && \"flex flex-col rounded-none\",\n className,\n )}\n {...rest}\n >\n {isBrowser && (\n <div\n className={cn(\n \"flex items-center gap-2 border-b border-zinc-200 bg-zinc-50 px-3.5 py-2.5 font-mono text-[11.5px] text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400\",\n barClassName,\n )}\n >\n {dots && (\n <div className=\"mr-1.5 flex gap-[5px]\">\n {DOT_COLORS.map((color, i) => (\n <span key={i} className={cn(\"h-[9px] w-[9px] rounded-full\", color)} />\n ))}\n </div>\n )}\n {url != null && <span className=\"min-w-0 flex-1 truncate\">{url}</span>}\n {meta != null && <span className=\"shrink-0 text-zinc-400 dark:text-zinc-500\">{meta}</span>}\n {hasControls && <div className={cn(url == null && meta == null && \"ml-auto\")}>{controlsBar}</div>}\n </div>\n )}\n\n <div\n ref={viewportRef}\n className={cn(\n \"relative\",\n // Zoomed in, the content is bigger than the frame: scroll to it.\n zoomed ? \"overflow-auto\" : \"overflow-hidden\",\n fullscreen && \"min-h-0 flex-1\",\n bodyClassName,\n )}\n // The frame keeps its FIT height whatever the zoom; zooming scrolls.\n style={scaled && !fullscreen && naturalHeight !== undefined ? { height: naturalHeight * fit } : undefined}\n >\n {scaled ? (\n // A sizer the size of the zoomed content, so the scrollbars are right.\n <div\n style={\n naturalHeight !== undefined && width !== undefined\n ? { width: width * effective, height: naturalHeight * effective, position: \"relative\" }\n : undefined\n }\n >\n <div\n ref={stageRef}\n data-react-fancy-faux-client-content=\"\"\n className=\"absolute left-0 top-0 origin-top-left\"\n style={{ width, transform: `scale(${effective})` }}\n >\n {children}\n </div>\n </div>\n ) : (\n <div\n data-react-fancy-faux-client-content=\"\"\n className={cn(fullscreen && \"h-full\")}\n // CSS `zoom` rather than a transform: it changes layout size, so the\n // content reflows and the viewport scrolls to all of it.\n style={zoomed ? { zoom: String(zoom) } : undefined}\n >\n {children}\n </div>\n )}\n </div>\n\n {hasControls && !isBrowser && (\n <div className=\"pointer-events-none absolute right-2 top-2 z-10\">{controlsBar}</div>\n )}\n </div>\n );\n },\n);\n\nFauxClient.displayName = \"FauxClient\";\n\n/** Ghost icon buttons: no box of their own, so the bar reads as part of the frame. */\nfunction controlButton(inBar: boolean): string {\n return cn(\n \"flex h-6 w-6 items-center justify-center rounded-full transition-colors disabled:pointer-events-none disabled:opacity-30\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/50\",\n inBar\n ? \"hover:bg-zinc-900/5 hover:text-zinc-700 dark:hover:bg-white/10 dark:hover:text-zinc-200\"\n : \"hover:bg-white/15 hover:text-white\",\n );\n}\n\nfunction ZoomOutIcon() {\n return (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.5-3.5\" />\n <path d=\"M8 11h6\" />\n </svg>\n );\n}\n\nfunction ZoomInIcon() {\n return (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.5-3.5\" />\n <path d=\"M8 11h6\" />\n <path d=\"M11 8v6\" />\n </svg>\n );\n}\n\n/** Inline, like `Input`'s reveal icons: a frame should not need an icon set configured. */\nfunction FullscreenIcon() {\n return (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M8 3H5a2 2 0 0 0-2 2v3\" />\n <path d=\"M21 8V5a2 2 0 0 0-2-2h-3\" />\n <path d=\"M3 16v3a2 2 0 0 0 2 2h3\" />\n <path d=\"M16 21h3a2 2 0 0 0 2-2v-3\" />\n </svg>\n );\n}\n\nfunction ExitFullscreenIcon() {\n return (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M8 3v3a2 2 0 0 1-2 2H3\" />\n <path d=\"M21 8h-3a2 2 0 0 1-2-2V3\" />\n <path d=\"M3 16h3a2 2 0 0 1 2 2v3\" />\n <path d=\"M16 21v-3a2 2 0 0 1 2-2h3\" />\n </svg>\n );\n}\n"]}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkSNHU2AI4_cjs = require('./chunk-SNHU2AI4.cjs');
|
|
4
|
+
var chunkZU5Q77OT_cjs = require('./chunk-ZU5Q77OT.cjs');
|
|
5
|
+
var react = require('react');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
var DOT_COLORS = ["bg-rose-300", "bg-amber-300", "bg-green-300"];
|
|
9
|
+
var ZOOM_STEP = 1.25;
|
|
10
|
+
var clamp = (n, min, max) => Math.min(max, Math.max(min, n));
|
|
11
|
+
var FauxClient = react.forwardRef(
|
|
12
|
+
({
|
|
13
|
+
variant = "browser",
|
|
14
|
+
url,
|
|
15
|
+
meta,
|
|
16
|
+
dots = true,
|
|
17
|
+
width,
|
|
18
|
+
scale = "fit",
|
|
19
|
+
controls = false,
|
|
20
|
+
zoom: controlledZoom,
|
|
21
|
+
defaultZoom = 1,
|
|
22
|
+
onZoomChange,
|
|
23
|
+
minZoom = 0.5,
|
|
24
|
+
maxZoom = 4,
|
|
25
|
+
onFullscreenChange,
|
|
26
|
+
className,
|
|
27
|
+
barClassName,
|
|
28
|
+
bodyClassName,
|
|
29
|
+
children,
|
|
30
|
+
...rest
|
|
31
|
+
}, ref) => {
|
|
32
|
+
const scaled = typeof width === "number";
|
|
33
|
+
const rootRef = react.useRef(null);
|
|
34
|
+
const viewportRef = react.useRef(null);
|
|
35
|
+
const stageRef = react.useRef(null);
|
|
36
|
+
const [fit, setFit] = react.useState(typeof scale === "number" ? scale : 1);
|
|
37
|
+
const [naturalHeight, setNaturalHeight] = react.useState(void 0);
|
|
38
|
+
const showZoom = controls === true || typeof controls === "object" && controls.zoom !== false;
|
|
39
|
+
const showFullscreen = controls === true || typeof controls === "object" && controls.fullscreen !== false;
|
|
40
|
+
const [zoom, setZoom] = chunkSNHU2AI4_cjs.useControllableState(controlledZoom, defaultZoom, onZoomChange);
|
|
41
|
+
const [fullscreen, setFullscreen] = react.useState(false);
|
|
42
|
+
react.useLayoutEffect(() => {
|
|
43
|
+
if (!scaled) return;
|
|
44
|
+
const viewport = viewportRef.current;
|
|
45
|
+
const stage = stageRef.current;
|
|
46
|
+
if (!viewport || !stage) return;
|
|
47
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
48
|
+
const measure = () => {
|
|
49
|
+
const avail = viewport.clientWidth;
|
|
50
|
+
setFit(scale === "fit" ? width && width > 0 ? avail / width : 1 : scale);
|
|
51
|
+
setNaturalHeight(stage.offsetHeight);
|
|
52
|
+
};
|
|
53
|
+
measure();
|
|
54
|
+
const ro = new ResizeObserver(measure);
|
|
55
|
+
ro.observe(viewport);
|
|
56
|
+
ro.observe(stage);
|
|
57
|
+
return () => ro.disconnect();
|
|
58
|
+
}, [scaled, scale, width]);
|
|
59
|
+
react.useEffect(() => {
|
|
60
|
+
if (!showFullscreen || typeof document === "undefined") return;
|
|
61
|
+
const sync = () => {
|
|
62
|
+
const on = document.fullscreenElement != null && document.fullscreenElement === rootRef.current;
|
|
63
|
+
setFullscreen(on);
|
|
64
|
+
onFullscreenChange?.(on);
|
|
65
|
+
};
|
|
66
|
+
document.addEventListener("fullscreenchange", sync);
|
|
67
|
+
return () => document.removeEventListener("fullscreenchange", sync);
|
|
68
|
+
}, [showFullscreen, onFullscreenChange]);
|
|
69
|
+
const toggleFullscreen = react.useCallback(() => {
|
|
70
|
+
const el = rootRef.current;
|
|
71
|
+
if (!el || typeof document === "undefined") return;
|
|
72
|
+
if (document.fullscreenElement === el) void document.exitFullscreen?.();
|
|
73
|
+
else void el.requestFullscreen?.();
|
|
74
|
+
}, []);
|
|
75
|
+
const zoomBy = (factor) => {
|
|
76
|
+
setZoom(clamp(Math.round(zoom * factor * 100) / 100, minZoom, maxZoom));
|
|
77
|
+
};
|
|
78
|
+
const setRefs = (el) => {
|
|
79
|
+
rootRef.current = el;
|
|
80
|
+
if (typeof ref === "function") ref(el);
|
|
81
|
+
else if (ref) ref.current = el;
|
|
82
|
+
};
|
|
83
|
+
const isBrowser = variant === "browser";
|
|
84
|
+
const isDevice = variant === "device";
|
|
85
|
+
const effective = fit * zoom;
|
|
86
|
+
const zoomed = zoom !== 1;
|
|
87
|
+
const hasControls = showZoom || showFullscreen;
|
|
88
|
+
const controlsBar = hasControls ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
89
|
+
"div",
|
|
90
|
+
{
|
|
91
|
+
"data-react-fancy-faux-client-controls": "",
|
|
92
|
+
className: chunkZU5Q77OT_cjs.cn(
|
|
93
|
+
"pointer-events-auto flex shrink-0 items-center gap-px opacity-0 transition-opacity duration-150",
|
|
94
|
+
"group-hover/faux-client:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100",
|
|
95
|
+
isBrowser ? "-my-1 text-zinc-400 dark:text-zinc-500" : "rounded-full bg-zinc-900/60 px-1 py-0.5 text-white/80 shadow-sm backdrop-blur-sm"
|
|
96
|
+
),
|
|
97
|
+
children: [
|
|
98
|
+
showZoom && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
99
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
100
|
+
"button",
|
|
101
|
+
{
|
|
102
|
+
type: "button",
|
|
103
|
+
"data-react-fancy-faux-client-zoom-out": "",
|
|
104
|
+
"aria-label": "Zoom out",
|
|
105
|
+
title: "Zoom out",
|
|
106
|
+
disabled: zoom <= minZoom,
|
|
107
|
+
onClick: () => zoomBy(1 / ZOOM_STEP),
|
|
108
|
+
className: controlButton(isBrowser),
|
|
109
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ZoomOutIcon, {})
|
|
110
|
+
}
|
|
111
|
+
),
|
|
112
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
113
|
+
"button",
|
|
114
|
+
{
|
|
115
|
+
type: "button",
|
|
116
|
+
"data-react-fancy-faux-client-zoom-reset": "",
|
|
117
|
+
"aria-label": "Reset zoom",
|
|
118
|
+
title: "Reset zoom",
|
|
119
|
+
onClick: () => setZoom(1),
|
|
120
|
+
className: chunkZU5Q77OT_cjs.cn(controlButton(isBrowser), "w-auto min-w-[2.25rem] px-1 font-sans text-[10.5px] tabular-nums"),
|
|
121
|
+
children: [
|
|
122
|
+
Math.round(zoom * 100),
|
|
123
|
+
"%"
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
),
|
|
127
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
128
|
+
"button",
|
|
129
|
+
{
|
|
130
|
+
type: "button",
|
|
131
|
+
"data-react-fancy-faux-client-zoom-in": "",
|
|
132
|
+
"aria-label": "Zoom in",
|
|
133
|
+
title: "Zoom in",
|
|
134
|
+
disabled: zoom >= maxZoom,
|
|
135
|
+
onClick: () => zoomBy(ZOOM_STEP),
|
|
136
|
+
className: controlButton(isBrowser),
|
|
137
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ZoomInIcon, {})
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
] }),
|
|
141
|
+
showFullscreen && /* @__PURE__ */ jsxRuntime.jsx(
|
|
142
|
+
"button",
|
|
143
|
+
{
|
|
144
|
+
type: "button",
|
|
145
|
+
"data-react-fancy-faux-client-fullscreen": "",
|
|
146
|
+
"aria-label": fullscreen ? "Exit full screen" : "Full screen",
|
|
147
|
+
"aria-pressed": fullscreen,
|
|
148
|
+
title: fullscreen ? "Exit full screen" : "Full screen",
|
|
149
|
+
onClick: toggleFullscreen,
|
|
150
|
+
className: controlButton(isBrowser),
|
|
151
|
+
children: fullscreen ? /* @__PURE__ */ jsxRuntime.jsx(ExitFullscreenIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(FullscreenIcon, {})
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
) : null;
|
|
157
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
158
|
+
"div",
|
|
159
|
+
{
|
|
160
|
+
ref: setRefs,
|
|
161
|
+
"data-react-fancy-faux-client": "",
|
|
162
|
+
"data-fullscreen": showFullscreen ? String(fullscreen) : void 0,
|
|
163
|
+
className: chunkZU5Q77OT_cjs.cn(
|
|
164
|
+
"group/faux-client relative overflow-hidden bg-white dark:bg-zinc-900",
|
|
165
|
+
isBrowser && "rounded-2xl border border-zinc-200 shadow-xl dark:border-zinc-800",
|
|
166
|
+
isDevice && "rounded-[2rem] border-[10px] border-zinc-800 shadow-2xl dark:border-zinc-700",
|
|
167
|
+
variant === "bare" && "rounded-lg border border-zinc-200 dark:border-zinc-800",
|
|
168
|
+
// Full screen: the frame fills the screen and its body takes the room.
|
|
169
|
+
fullscreen && "flex flex-col rounded-none",
|
|
170
|
+
className
|
|
171
|
+
),
|
|
172
|
+
...rest,
|
|
173
|
+
children: [
|
|
174
|
+
isBrowser && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
175
|
+
"div",
|
|
176
|
+
{
|
|
177
|
+
className: chunkZU5Q77OT_cjs.cn(
|
|
178
|
+
"flex items-center gap-2 border-b border-zinc-200 bg-zinc-50 px-3.5 py-2.5 font-mono text-[11.5px] text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400",
|
|
179
|
+
barClassName
|
|
180
|
+
),
|
|
181
|
+
children: [
|
|
182
|
+
dots && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mr-1.5 flex gap-[5px]", children: DOT_COLORS.map((color, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: chunkZU5Q77OT_cjs.cn("h-[9px] w-[9px] rounded-full", color) }, i)) }),
|
|
183
|
+
url != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: url }),
|
|
184
|
+
meta != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-zinc-400 dark:text-zinc-500", children: meta }),
|
|
185
|
+
hasControls && /* @__PURE__ */ jsxRuntime.jsx("div", { className: chunkZU5Q77OT_cjs.cn(url == null && meta == null && "ml-auto"), children: controlsBar })
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
),
|
|
189
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
190
|
+
"div",
|
|
191
|
+
{
|
|
192
|
+
ref: viewportRef,
|
|
193
|
+
className: chunkZU5Q77OT_cjs.cn(
|
|
194
|
+
"relative",
|
|
195
|
+
// Zoomed in, the content is bigger than the frame: scroll to it.
|
|
196
|
+
zoomed ? "overflow-auto" : "overflow-hidden",
|
|
197
|
+
fullscreen && "min-h-0 flex-1",
|
|
198
|
+
bodyClassName
|
|
199
|
+
),
|
|
200
|
+
style: scaled && !fullscreen && naturalHeight !== void 0 ? { height: naturalHeight * fit } : void 0,
|
|
201
|
+
children: scaled ? (
|
|
202
|
+
// A sizer the size of the zoomed content, so the scrollbars are right.
|
|
203
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
204
|
+
"div",
|
|
205
|
+
{
|
|
206
|
+
style: naturalHeight !== void 0 && width !== void 0 ? { width: width * effective, height: naturalHeight * effective, position: "relative" } : void 0,
|
|
207
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
208
|
+
"div",
|
|
209
|
+
{
|
|
210
|
+
ref: stageRef,
|
|
211
|
+
"data-react-fancy-faux-client-content": "",
|
|
212
|
+
className: "absolute left-0 top-0 origin-top-left",
|
|
213
|
+
style: { width, transform: `scale(${effective})` },
|
|
214
|
+
children
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
220
|
+
"div",
|
|
221
|
+
{
|
|
222
|
+
"data-react-fancy-faux-client-content": "",
|
|
223
|
+
className: chunkZU5Q77OT_cjs.cn(fullscreen && "h-full"),
|
|
224
|
+
style: zoomed ? { zoom: String(zoom) } : void 0,
|
|
225
|
+
children
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
),
|
|
230
|
+
hasControls && !isBrowser && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute right-2 top-2 z-10", children: controlsBar })
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
FauxClient.displayName = "FauxClient";
|
|
237
|
+
function controlButton(inBar) {
|
|
238
|
+
return chunkZU5Q77OT_cjs.cn(
|
|
239
|
+
"flex h-6 w-6 items-center justify-center rounded-full transition-colors disabled:pointer-events-none disabled:opacity-30",
|
|
240
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/50",
|
|
241
|
+
inBar ? "hover:bg-zinc-900/5 hover:text-zinc-700 dark:hover:bg-white/10 dark:hover:text-zinc-200" : "hover:bg-white/15 hover:text-white"
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
function ZoomOutIcon() {
|
|
245
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
246
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
247
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m20 20-3.5-3.5" }),
|
|
248
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 11h6" })
|
|
249
|
+
] });
|
|
250
|
+
}
|
|
251
|
+
function ZoomInIcon() {
|
|
252
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
253
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
254
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m20 20-3.5-3.5" }),
|
|
255
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 11h6" }),
|
|
256
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M11 8v6" })
|
|
257
|
+
] });
|
|
258
|
+
}
|
|
259
|
+
function FullscreenIcon() {
|
|
260
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
261
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
|
|
262
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }),
|
|
263
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 16v3a2 2 0 0 0 2 2h3" }),
|
|
264
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 21h3a2 2 0 0 0 2-2v-3" })
|
|
265
|
+
] });
|
|
266
|
+
}
|
|
267
|
+
function ExitFullscreenIcon() {
|
|
268
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
269
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 3v3a2 2 0 0 1-2 2H3" }),
|
|
270
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21 8h-3a2 2 0 0 1-2-2V3" }),
|
|
271
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3 16h3a2 2 0 0 1 2 2v3" }),
|
|
272
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 21v-3a2 2 0 0 1 2-2h3" })
|
|
273
|
+
] });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
exports.FauxClient = FauxClient;
|
|
277
|
+
//# sourceMappingURL=chunk-XL62UDVX.cjs.map
|
|
278
|
+
//# sourceMappingURL=chunk-XL62UDVX.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/FauxClient/FauxClient.tsx"],"names":["forwardRef","useRef","useState","useControllableState","useLayoutEffect","useEffect","useCallback","jsxs","cn","Fragment","jsx"],"mappings":"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","file":"chunk-XL62UDVX.cjs","sourcesContent":["import { forwardRef, useCallback, useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { useControllableState } from \"../../hooks/use-controllable-state\";\nimport type { FauxClientProps } from \"./FauxClient.types\";\n\nconst DOT_COLORS = [\"bg-rose-300\", \"bg-amber-300\", \"bg-green-300\"];\n\n/** One click of the zoom bar. Matches `ImageViewer`, so the kit zooms one way. */\nconst ZOOM_STEP = 1.25;\n\nconst clamp = (n: number, min: number, max: number) => Math.min(max, Math.max(min, n));\n\n/**\n * A frame that mimics a browser window or device, rendering **real UI** inside.\n * With a logical `width` + `scale=\"fit\"` it renders a full-size page/app and\n * scales it down to fit any container (live thumbnails, device mockups) — the\n * content is real and interactive, just zoomed.\n *\n * Omit `width` for chrome around natural-size content (e.g. a code/preview card).\n *\n * `controls` adds a zoom bar and a full-screen button, for content that has to be\n * READ rather than glanced at: a slide or a sheet shrunk to fit a panel.\n */\nexport const FauxClient = forwardRef<HTMLDivElement, FauxClientProps>(\n (\n {\n variant = \"browser\",\n url,\n meta,\n dots = true,\n width,\n scale = \"fit\",\n controls = false,\n zoom: controlledZoom,\n defaultZoom = 1,\n onZoomChange,\n minZoom = 0.5,\n maxZoom = 4,\n onFullscreenChange,\n className,\n barClassName,\n bodyClassName,\n children,\n ...rest\n },\n ref,\n ) => {\n const scaled = typeof width === \"number\";\n const rootRef = useRef<HTMLDivElement | null>(null);\n const viewportRef = useRef<HTMLDivElement>(null);\n const stageRef = useRef<HTMLDivElement>(null);\n const [fit, setFit] = useState(typeof scale === \"number\" ? scale : 1);\n const [naturalHeight, setNaturalHeight] = useState<number | undefined>(undefined);\n\n const showZoom = controls === true || (typeof controls === \"object\" && controls.zoom !== false);\n const showFullscreen = controls === true || (typeof controls === \"object\" && controls.fullscreen !== false);\n const [zoom, setZoom] = useControllableState(controlledZoom, defaultZoom, onZoomChange);\n const [fullscreen, setFullscreen] = useState(false);\n\n useLayoutEffect(() => {\n if (!scaled) return;\n const viewport = viewportRef.current;\n const stage = stageRef.current;\n if (!viewport || !stage) return;\n if (typeof ResizeObserver === \"undefined\") return;\n\n const measure = () => {\n const avail = viewport.clientWidth;\n setFit(scale === \"fit\" ? (width && width > 0 ? avail / width : 1) : scale);\n // offsetHeight is the pre-transform layout height (CSS transforms don't affect it).\n setNaturalHeight(stage.offsetHeight);\n };\n\n measure();\n const ro = new ResizeObserver(measure);\n ro.observe(viewport);\n ro.observe(stage);\n return () => ro.disconnect();\n }, [scaled, scale, width]);\n\n // Full screen is the browser's state, not ours: follow it, so Esc and the\n // browser's own exit leave the button telling the truth.\n useEffect(() => {\n if (!showFullscreen || typeof document === \"undefined\") return;\n const sync = () => {\n const on = document.fullscreenElement != null && document.fullscreenElement === rootRef.current;\n setFullscreen(on);\n onFullscreenChange?.(on);\n };\n document.addEventListener(\"fullscreenchange\", sync);\n return () => document.removeEventListener(\"fullscreenchange\", sync);\n }, [showFullscreen, onFullscreenChange]);\n\n const toggleFullscreen = useCallback(() => {\n const el = rootRef.current;\n if (!el || typeof document === \"undefined\") return;\n if (document.fullscreenElement === el) void document.exitFullscreen?.();\n else void el.requestFullscreen?.();\n }, []);\n\n const zoomBy = (factor: number) => {\n // Rounded so ten clicks in and ten out land back on 100%, not 99.99%.\n setZoom(clamp(Math.round(zoom * factor * 100) / 100, minZoom, maxZoom));\n };\n\n const setRefs = (el: HTMLDivElement | null) => {\n rootRef.current = el;\n if (typeof ref === \"function\") ref(el);\n else if (ref) ref.current = el;\n };\n\n const isBrowser = variant === \"browser\";\n const isDevice = variant === \"device\";\n const effective = fit * zoom;\n const zoomed = zoom !== 1;\n const hasControls = showZoom || showFullscreen;\n\n /*\n * Out of the way until wanted: hidden, and revealed when the pointer is over\n * the frame or focus is inside it (keyboard). Always shown where there is no\n * hover to reveal it (touch), or it could never be reached.\n *\n * In the browser chrome it sits in the address bar, inline. On a device or a\n * bare frame there is no chrome, so it floats top-right as a slim strip: the\n * bottom corners belong to the content (a slide viewer's page count lives\n * there).\n */\n const controlsBar = hasControls ? (\n <div\n data-react-fancy-faux-client-controls=\"\"\n className={cn(\n \"pointer-events-auto flex shrink-0 items-center gap-px opacity-0 transition-opacity duration-150\",\n \"group-hover/faux-client:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100\",\n isBrowser\n ? \"-my-1 text-zinc-400 dark:text-zinc-500\"\n : \"rounded-full bg-zinc-900/60 px-1 py-0.5 text-white/80 shadow-sm backdrop-blur-sm\",\n )}\n >\n {showZoom && (\n <>\n <button\n type=\"button\"\n data-react-fancy-faux-client-zoom-out=\"\"\n aria-label=\"Zoom out\"\n title=\"Zoom out\"\n disabled={zoom <= minZoom}\n onClick={() => zoomBy(1 / ZOOM_STEP)}\n className={controlButton(isBrowser)}\n >\n <ZoomOutIcon />\n </button>\n <button\n type=\"button\"\n data-react-fancy-faux-client-zoom-reset=\"\"\n aria-label=\"Reset zoom\"\n title=\"Reset zoom\"\n onClick={() => setZoom(1)}\n className={cn(controlButton(isBrowser), \"w-auto min-w-[2.25rem] px-1 font-sans text-[10.5px] tabular-nums\")}\n >\n {Math.round(zoom * 100)}%\n </button>\n <button\n type=\"button\"\n data-react-fancy-faux-client-zoom-in=\"\"\n aria-label=\"Zoom in\"\n title=\"Zoom in\"\n disabled={zoom >= maxZoom}\n onClick={() => zoomBy(ZOOM_STEP)}\n className={controlButton(isBrowser)}\n >\n <ZoomInIcon />\n </button>\n </>\n )}\n {showFullscreen && (\n <button\n type=\"button\"\n data-react-fancy-faux-client-fullscreen=\"\"\n aria-label={fullscreen ? \"Exit full screen\" : \"Full screen\"}\n aria-pressed={fullscreen}\n title={fullscreen ? \"Exit full screen\" : \"Full screen\"}\n onClick={toggleFullscreen}\n className={controlButton(isBrowser)}\n >\n {fullscreen ? <ExitFullscreenIcon /> : <FullscreenIcon />}\n </button>\n )}\n </div>\n ) : null;\n\n return (\n <div\n ref={setRefs}\n data-react-fancy-faux-client=\"\"\n data-fullscreen={showFullscreen ? String(fullscreen) : undefined}\n className={cn(\n \"group/faux-client relative overflow-hidden bg-white dark:bg-zinc-900\",\n isBrowser && \"rounded-2xl border border-zinc-200 shadow-xl dark:border-zinc-800\",\n isDevice && \"rounded-[2rem] border-[10px] border-zinc-800 shadow-2xl dark:border-zinc-700\",\n variant === \"bare\" && \"rounded-lg border border-zinc-200 dark:border-zinc-800\",\n // Full screen: the frame fills the screen and its body takes the room.\n fullscreen && \"flex flex-col rounded-none\",\n className,\n )}\n {...rest}\n >\n {isBrowser && (\n <div\n className={cn(\n \"flex items-center gap-2 border-b border-zinc-200 bg-zinc-50 px-3.5 py-2.5 font-mono text-[11.5px] text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400\",\n barClassName,\n )}\n >\n {dots && (\n <div className=\"mr-1.5 flex gap-[5px]\">\n {DOT_COLORS.map((color, i) => (\n <span key={i} className={cn(\"h-[9px] w-[9px] rounded-full\", color)} />\n ))}\n </div>\n )}\n {url != null && <span className=\"min-w-0 flex-1 truncate\">{url}</span>}\n {meta != null && <span className=\"shrink-0 text-zinc-400 dark:text-zinc-500\">{meta}</span>}\n {hasControls && <div className={cn(url == null && meta == null && \"ml-auto\")}>{controlsBar}</div>}\n </div>\n )}\n\n <div\n ref={viewportRef}\n className={cn(\n \"relative\",\n // Zoomed in, the content is bigger than the frame: scroll to it.\n zoomed ? \"overflow-auto\" : \"overflow-hidden\",\n fullscreen && \"min-h-0 flex-1\",\n bodyClassName,\n )}\n // The frame keeps its FIT height whatever the zoom; zooming scrolls.\n style={scaled && !fullscreen && naturalHeight !== undefined ? { height: naturalHeight * fit } : undefined}\n >\n {scaled ? (\n // A sizer the size of the zoomed content, so the scrollbars are right.\n <div\n style={\n naturalHeight !== undefined && width !== undefined\n ? { width: width * effective, height: naturalHeight * effective, position: \"relative\" }\n : undefined\n }\n >\n <div\n ref={stageRef}\n data-react-fancy-faux-client-content=\"\"\n className=\"absolute left-0 top-0 origin-top-left\"\n style={{ width, transform: `scale(${effective})` }}\n >\n {children}\n </div>\n </div>\n ) : (\n <div\n data-react-fancy-faux-client-content=\"\"\n className={cn(fullscreen && \"h-full\")}\n // CSS `zoom` rather than a transform: it changes layout size, so the\n // content reflows and the viewport scrolls to all of it.\n style={zoomed ? { zoom: String(zoom) } : undefined}\n >\n {children}\n </div>\n )}\n </div>\n\n {hasControls && !isBrowser && (\n <div className=\"pointer-events-none absolute right-2 top-2 z-10\">{controlsBar}</div>\n )}\n </div>\n );\n },\n);\n\nFauxClient.displayName = \"FauxClient\";\n\n/** Ghost icon buttons: no box of their own, so the bar reads as part of the frame. */\nfunction controlButton(inBar: boolean): string {\n return cn(\n \"flex h-6 w-6 items-center justify-center rounded-full transition-colors disabled:pointer-events-none disabled:opacity-30\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/50\",\n inBar\n ? \"hover:bg-zinc-900/5 hover:text-zinc-700 dark:hover:bg-white/10 dark:hover:text-zinc-200\"\n : \"hover:bg-white/15 hover:text-white\",\n );\n}\n\nfunction ZoomOutIcon() {\n return (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.5-3.5\" />\n <path d=\"M8 11h6\" />\n </svg>\n );\n}\n\nfunction ZoomInIcon() {\n return (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <circle cx=\"11\" cy=\"11\" r=\"7\" />\n <path d=\"m20 20-3.5-3.5\" />\n <path d=\"M8 11h6\" />\n <path d=\"M11 8v6\" />\n </svg>\n );\n}\n\n/** Inline, like `Input`'s reveal icons: a frame should not need an icon set configured. */\nfunction FullscreenIcon() {\n return (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M8 3H5a2 2 0 0 0-2 2v3\" />\n <path d=\"M21 8V5a2 2 0 0 0-2-2h-3\" />\n <path d=\"M3 16v3a2 2 0 0 0 2 2h3\" />\n <path d=\"M16 21h3a2 2 0 0 0 2-2v-3\" />\n </svg>\n );\n}\n\nfunction ExitFullscreenIcon() {\n return (\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M8 3v3a2 2 0 0 1-2 2H3\" />\n <path d=\"M21 8h-3a2 2 0 0 1-2-2V3\" />\n <path d=\"M3 16h3a2 2 0 0 1 2 2v3\" />\n <path d=\"M16 21v-3a2 2 0 0 1 2-2h3\" />\n </svg>\n );\n}\n"]}
|
package/dist/faux-client.cjs
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkXL62UDVX_cjs = require('./chunk-XL62UDVX.cjs');
|
|
4
|
+
require('./chunk-SNHU2AI4.cjs');
|
|
4
5
|
require('./chunk-ZU5Q77OT.cjs');
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
Object.defineProperty(exports, "FauxClient", {
|
|
9
10
|
enumerable: true,
|
|
10
|
-
get: function () { return
|
|
11
|
+
get: function () { return chunkXL62UDVX_cjs.FauxClient; }
|
|
11
12
|
});
|
|
12
13
|
//# sourceMappingURL=faux-client.cjs.map
|
|
13
14
|
//# sourceMappingURL=faux-client.cjs.map
|
package/dist/faux-client.d.cts
CHANGED
|
@@ -18,6 +18,29 @@ interface FauxClientProps extends Omit<HTMLAttributes<HTMLDivElement>, "children
|
|
|
18
18
|
width?: number;
|
|
19
19
|
/** `"fit"` scales the logical {@link FauxClientProps.width} to the frame; a number is a fixed scale. */
|
|
20
20
|
scale?: number | "fit";
|
|
21
|
+
/**
|
|
22
|
+
* A zoom bar (−, percentage, +) and a full-screen button, in the frame's
|
|
23
|
+
* bottom-right corner. `true` for both; `{ zoom: false }` or
|
|
24
|
+
* `{ fullscreen: false }` to leave one out. Default `false`.
|
|
25
|
+
*/
|
|
26
|
+
controls?: boolean | {
|
|
27
|
+
zoom?: boolean;
|
|
28
|
+
fullscreen?: boolean;
|
|
29
|
+
};
|
|
30
|
+
/** The zoom, 1 = fit. Controllable, so an agent can read and set it. */
|
|
31
|
+
zoom?: number;
|
|
32
|
+
defaultZoom?: number;
|
|
33
|
+
onZoomChange?: (zoom: number) => void;
|
|
34
|
+
/** Default 0.5. */
|
|
35
|
+
minZoom?: number;
|
|
36
|
+
/** Default 4. */
|
|
37
|
+
maxZoom?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Called when the frame enters or leaves full screen, however it happened
|
|
40
|
+
* (the button, Esc, the browser). The frame also carries
|
|
41
|
+
* `data-fullscreen="true"` while it is, for styling the content.
|
|
42
|
+
*/
|
|
43
|
+
onFullscreenChange?: (fullscreen: boolean) => void;
|
|
21
44
|
className?: string;
|
|
22
45
|
/** Class for the browser bar. */
|
|
23
46
|
barClassName?: string;
|
|
@@ -33,6 +56,9 @@ interface FauxClientProps extends Omit<HTMLAttributes<HTMLDivElement>, "children
|
|
|
33
56
|
* content is real and interactive, just zoomed.
|
|
34
57
|
*
|
|
35
58
|
* Omit `width` for chrome around natural-size content (e.g. a code/preview card).
|
|
59
|
+
*
|
|
60
|
+
* `controls` adds a zoom bar and a full-screen button, for content that has to be
|
|
61
|
+
* READ rather than glanced at: a slide or a sheet shrunk to fit a panel.
|
|
36
62
|
*/
|
|
37
63
|
declare const FauxClient: react.ForwardRefExoticComponent<FauxClientProps & react.RefAttributes<HTMLDivElement>>;
|
|
38
64
|
|
package/dist/faux-client.d.ts
CHANGED
|
@@ -18,6 +18,29 @@ interface FauxClientProps extends Omit<HTMLAttributes<HTMLDivElement>, "children
|
|
|
18
18
|
width?: number;
|
|
19
19
|
/** `"fit"` scales the logical {@link FauxClientProps.width} to the frame; a number is a fixed scale. */
|
|
20
20
|
scale?: number | "fit";
|
|
21
|
+
/**
|
|
22
|
+
* A zoom bar (−, percentage, +) and a full-screen button, in the frame's
|
|
23
|
+
* bottom-right corner. `true` for both; `{ zoom: false }` or
|
|
24
|
+
* `{ fullscreen: false }` to leave one out. Default `false`.
|
|
25
|
+
*/
|
|
26
|
+
controls?: boolean | {
|
|
27
|
+
zoom?: boolean;
|
|
28
|
+
fullscreen?: boolean;
|
|
29
|
+
};
|
|
30
|
+
/** The zoom, 1 = fit. Controllable, so an agent can read and set it. */
|
|
31
|
+
zoom?: number;
|
|
32
|
+
defaultZoom?: number;
|
|
33
|
+
onZoomChange?: (zoom: number) => void;
|
|
34
|
+
/** Default 0.5. */
|
|
35
|
+
minZoom?: number;
|
|
36
|
+
/** Default 4. */
|
|
37
|
+
maxZoom?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Called when the frame enters or leaves full screen, however it happened
|
|
40
|
+
* (the button, Esc, the browser). The frame also carries
|
|
41
|
+
* `data-fullscreen="true"` while it is, for styling the content.
|
|
42
|
+
*/
|
|
43
|
+
onFullscreenChange?: (fullscreen: boolean) => void;
|
|
21
44
|
className?: string;
|
|
22
45
|
/** Class for the browser bar. */
|
|
23
46
|
barClassName?: string;
|
|
@@ -33,6 +56,9 @@ interface FauxClientProps extends Omit<HTMLAttributes<HTMLDivElement>, "children
|
|
|
33
56
|
* content is real and interactive, just zoomed.
|
|
34
57
|
*
|
|
35
58
|
* Omit `width` for chrome around natural-size content (e.g. a code/preview card).
|
|
59
|
+
*
|
|
60
|
+
* `controls` adds a zoom bar and a full-screen button, for content that has to be
|
|
61
|
+
* READ rather than glanced at: a slide or a sheet shrunk to fit a panel.
|
|
36
62
|
*/
|
|
37
63
|
declare const FauxClient: react.ForwardRefExoticComponent<FauxClientProps & react.RefAttributes<HTMLDivElement>>;
|
|
38
64
|
|
package/dist/faux-client.js
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -61,7 +61,7 @@ var chunkZMD3PIYF_cjs = require('./chunk-ZMD3PIYF.cjs');
|
|
|
61
61
|
var chunkTH7VP4CL_cjs = require('./chunk-TH7VP4CL.cjs');
|
|
62
62
|
var chunkV35A6B7A_cjs = require('./chunk-V35A6B7A.cjs');
|
|
63
63
|
var chunkRPVCMW64_cjs = require('./chunk-RPVCMW64.cjs');
|
|
64
|
-
var
|
|
64
|
+
var chunkXL62UDVX_cjs = require('./chunk-XL62UDVX.cjs');
|
|
65
65
|
var chunkT45XK5GN_cjs = require('./chunk-T45XK5GN.cjs');
|
|
66
66
|
var chunkVKXNCYLE_cjs = require('./chunk-VKXNCYLE.cjs');
|
|
67
67
|
var chunkOOZCJLEF_cjs = require('./chunk-OOZCJLEF.cjs');
|
|
@@ -467,7 +467,7 @@ Object.defineProperty(exports, "useMenu", {
|
|
|
467
467
|
});
|
|
468
468
|
Object.defineProperty(exports, "FauxClient", {
|
|
469
469
|
enumerable: true,
|
|
470
|
-
get: function () { return
|
|
470
|
+
get: function () { return chunkXL62UDVX_cjs.FauxClient; }
|
|
471
471
|
});
|
|
472
472
|
Object.defineProperty(exports, "FileBrowser", {
|
|
473
473
|
enumerable: true,
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,7 @@ export { MediaViewer, resolveMediaType } from './chunk-KO2HLNBB.js';
|
|
|
59
59
|
export { VideoViewer } from './chunk-VOGI7GX3.js';
|
|
60
60
|
export { PdfViewer } from './chunk-SOYORSLZ.js';
|
|
61
61
|
export { Menu, useMenu } from './chunk-RA2VOQ46.js';
|
|
62
|
-
export { FauxClient } from './chunk-
|
|
62
|
+
export { FauxClient } from './chunk-VDHHWSMT.js';
|
|
63
63
|
export { FileBrowser, FileBrowserNewFolder, useFileBrowser, validateFolderName } from './chunk-2KAJ4L7C.js';
|
|
64
64
|
export { FileUpload, useFileUpload } from './chunk-W6RDAZJC.js';
|
|
65
65
|
export { Grid } from './chunk-TM3RTWKI.js';
|
package/package.json
CHANGED
package/dist/chunk-FA4IG5RB.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { cn } from './chunk-MMW2JGKZ.js';
|
|
2
|
-
import { forwardRef, useRef, useState, useLayoutEffect } from 'react';
|
|
3
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
-
|
|
5
|
-
var DOT_COLORS = ["bg-rose-300", "bg-amber-300", "bg-green-300"];
|
|
6
|
-
var FauxClient = forwardRef(
|
|
7
|
-
({
|
|
8
|
-
variant = "browser",
|
|
9
|
-
url,
|
|
10
|
-
meta,
|
|
11
|
-
dots = true,
|
|
12
|
-
width,
|
|
13
|
-
scale = "fit",
|
|
14
|
-
className,
|
|
15
|
-
barClassName,
|
|
16
|
-
bodyClassName,
|
|
17
|
-
children,
|
|
18
|
-
...rest
|
|
19
|
-
}, ref) => {
|
|
20
|
-
const scaled = typeof width === "number";
|
|
21
|
-
const viewportRef = useRef(null);
|
|
22
|
-
const stageRef = useRef(null);
|
|
23
|
-
const [s, setS] = useState(typeof scale === "number" ? scale : 1);
|
|
24
|
-
const [contentHeight, setContentHeight] = useState(void 0);
|
|
25
|
-
useLayoutEffect(() => {
|
|
26
|
-
if (!scaled) return;
|
|
27
|
-
const viewport = viewportRef.current;
|
|
28
|
-
const stage = stageRef.current;
|
|
29
|
-
if (!viewport || !stage) return;
|
|
30
|
-
if (typeof ResizeObserver === "undefined") return;
|
|
31
|
-
const measure = () => {
|
|
32
|
-
const avail = viewport.clientWidth;
|
|
33
|
-
const next = scale === "fit" ? width && width > 0 ? avail / width : 1 : scale;
|
|
34
|
-
const naturalHeight = stage.offsetHeight;
|
|
35
|
-
setS(next);
|
|
36
|
-
setContentHeight(naturalHeight * next);
|
|
37
|
-
};
|
|
38
|
-
measure();
|
|
39
|
-
const ro = new ResizeObserver(measure);
|
|
40
|
-
ro.observe(viewport);
|
|
41
|
-
ro.observe(stage);
|
|
42
|
-
return () => ro.disconnect();
|
|
43
|
-
}, [scaled, scale, width]);
|
|
44
|
-
const isBrowser = variant === "browser";
|
|
45
|
-
const isDevice = variant === "device";
|
|
46
|
-
return /* @__PURE__ */ jsxs(
|
|
47
|
-
"div",
|
|
48
|
-
{
|
|
49
|
-
ref,
|
|
50
|
-
"data-react-fancy-faux-client": "",
|
|
51
|
-
className: cn(
|
|
52
|
-
"overflow-hidden bg-white dark:bg-zinc-900",
|
|
53
|
-
isBrowser && "rounded-2xl border border-zinc-200 shadow-xl dark:border-zinc-800",
|
|
54
|
-
isDevice && "rounded-[2rem] border-[10px] border-zinc-800 shadow-2xl dark:border-zinc-700",
|
|
55
|
-
variant === "bare" && "rounded-lg border border-zinc-200 dark:border-zinc-800",
|
|
56
|
-
className
|
|
57
|
-
),
|
|
58
|
-
...rest,
|
|
59
|
-
children: [
|
|
60
|
-
isBrowser && /* @__PURE__ */ jsxs(
|
|
61
|
-
"div",
|
|
62
|
-
{
|
|
63
|
-
className: cn(
|
|
64
|
-
"flex items-center gap-2 border-b border-zinc-200 bg-zinc-50 px-3.5 py-2.5 font-mono text-[11.5px] text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400",
|
|
65
|
-
barClassName
|
|
66
|
-
),
|
|
67
|
-
children: [
|
|
68
|
-
dots && /* @__PURE__ */ jsx("div", { className: "mr-1.5 flex gap-[5px]", children: DOT_COLORS.map((color, i) => /* @__PURE__ */ jsx("span", { className: cn("h-[9px] w-[9px] rounded-full", color) }, i)) }),
|
|
69
|
-
url != null && /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: url }),
|
|
70
|
-
meta != null && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-zinc-400 dark:text-zinc-500", children: meta })
|
|
71
|
-
]
|
|
72
|
-
}
|
|
73
|
-
),
|
|
74
|
-
/* @__PURE__ */ jsx(
|
|
75
|
-
"div",
|
|
76
|
-
{
|
|
77
|
-
ref: viewportRef,
|
|
78
|
-
className: cn("relative overflow-hidden", bodyClassName),
|
|
79
|
-
style: scaled ? { height: contentHeight } : void 0,
|
|
80
|
-
children: scaled ? /* @__PURE__ */ jsx(
|
|
81
|
-
"div",
|
|
82
|
-
{
|
|
83
|
-
ref: stageRef,
|
|
84
|
-
className: "absolute left-0 top-0 origin-top-left",
|
|
85
|
-
style: { width, transform: `scale(${s})` },
|
|
86
|
-
children
|
|
87
|
-
}
|
|
88
|
-
) : children
|
|
89
|
-
}
|
|
90
|
-
)
|
|
91
|
-
]
|
|
92
|
-
}
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
);
|
|
96
|
-
FauxClient.displayName = "FauxClient";
|
|
97
|
-
|
|
98
|
-
export { FauxClient };
|
|
99
|
-
//# sourceMappingURL=chunk-FA4IG5RB.js.map
|
|
100
|
-
//# sourceMappingURL=chunk-FA4IG5RB.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/FauxClient/FauxClient.tsx"],"names":[],"mappings":"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","file":"chunk-FA4IG5RB.js","sourcesContent":["import { forwardRef, useLayoutEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport type { FauxClientProps } from \"./FauxClient.types\";\n\nconst DOT_COLORS = [\"bg-rose-300\", \"bg-amber-300\", \"bg-green-300\"];\n\n/**\n * A frame that mimics a browser window or device, rendering **real UI** inside.\n * With a logical `width` + `scale=\"fit\"` it renders a full-size page/app and\n * scales it down to fit any container (live thumbnails, device mockups) — the\n * content is real and interactive, just zoomed.\n *\n * Omit `width` for chrome around natural-size content (e.g. a code/preview card).\n */\nexport const FauxClient = forwardRef<HTMLDivElement, FauxClientProps>(\n (\n {\n variant = \"browser\",\n url,\n meta,\n dots = true,\n width,\n scale = \"fit\",\n className,\n barClassName,\n bodyClassName,\n children,\n ...rest\n },\n ref,\n ) => {\n const scaled = typeof width === \"number\";\n const viewportRef = useRef<HTMLDivElement>(null);\n const stageRef = useRef<HTMLDivElement>(null);\n const [s, setS] = useState(typeof scale === \"number\" ? scale : 1);\n const [contentHeight, setContentHeight] = useState<number | undefined>(undefined);\n\n useLayoutEffect(() => {\n if (!scaled) return;\n const viewport = viewportRef.current;\n const stage = stageRef.current;\n if (!viewport || !stage) return;\n if (typeof ResizeObserver === \"undefined\") return;\n\n const measure = () => {\n const avail = viewport.clientWidth;\n const next = scale === \"fit\" ? (width && width > 0 ? avail / width : 1) : scale;\n // offsetHeight is the pre-transform layout height (CSS transforms don't affect it).\n const naturalHeight = stage.offsetHeight;\n setS(next);\n setContentHeight(naturalHeight * next);\n };\n\n measure();\n const ro = new ResizeObserver(measure);\n ro.observe(viewport);\n ro.observe(stage);\n return () => ro.disconnect();\n }, [scaled, scale, width]);\n\n const isBrowser = variant === \"browser\";\n const isDevice = variant === \"device\";\n\n return (\n <div\n ref={ref}\n data-react-fancy-faux-client=\"\"\n className={cn(\n \"overflow-hidden bg-white dark:bg-zinc-900\",\n isBrowser && \"rounded-2xl border border-zinc-200 shadow-xl dark:border-zinc-800\",\n isDevice && \"rounded-[2rem] border-[10px] border-zinc-800 shadow-2xl dark:border-zinc-700\",\n variant === \"bare\" && \"rounded-lg border border-zinc-200 dark:border-zinc-800\",\n className,\n )}\n {...rest}\n >\n {isBrowser && (\n <div\n className={cn(\n \"flex items-center gap-2 border-b border-zinc-200 bg-zinc-50 px-3.5 py-2.5 font-mono text-[11.5px] text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400\",\n barClassName,\n )}\n >\n {dots && (\n <div className=\"mr-1.5 flex gap-[5px]\">\n {DOT_COLORS.map((color, i) => (\n <span key={i} className={cn(\"h-[9px] w-[9px] rounded-full\", color)} />\n ))}\n </div>\n )}\n {url != null && <span className=\"min-w-0 flex-1 truncate\">{url}</span>}\n {meta != null && <span className=\"shrink-0 text-zinc-400 dark:text-zinc-500\">{meta}</span>}\n </div>\n )}\n\n <div\n ref={viewportRef}\n className={cn(\"relative overflow-hidden\", bodyClassName)}\n style={scaled ? { height: contentHeight } : undefined}\n >\n {scaled ? (\n <div\n ref={stageRef}\n className=\"absolute left-0 top-0 origin-top-left\"\n style={{ width, transform: `scale(${s})` }}\n >\n {children}\n </div>\n ) : (\n children\n )}\n </div>\n </div>\n );\n },\n);\n\nFauxClient.displayName = \"FauxClient\";\n"]}
|
package/dist/chunk-WCWZADR2.cjs
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var chunkZU5Q77OT_cjs = require('./chunk-ZU5Q77OT.cjs');
|
|
4
|
-
var react = require('react');
|
|
5
|
-
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
-
|
|
7
|
-
var DOT_COLORS = ["bg-rose-300", "bg-amber-300", "bg-green-300"];
|
|
8
|
-
var FauxClient = react.forwardRef(
|
|
9
|
-
({
|
|
10
|
-
variant = "browser",
|
|
11
|
-
url,
|
|
12
|
-
meta,
|
|
13
|
-
dots = true,
|
|
14
|
-
width,
|
|
15
|
-
scale = "fit",
|
|
16
|
-
className,
|
|
17
|
-
barClassName,
|
|
18
|
-
bodyClassName,
|
|
19
|
-
children,
|
|
20
|
-
...rest
|
|
21
|
-
}, ref) => {
|
|
22
|
-
const scaled = typeof width === "number";
|
|
23
|
-
const viewportRef = react.useRef(null);
|
|
24
|
-
const stageRef = react.useRef(null);
|
|
25
|
-
const [s, setS] = react.useState(typeof scale === "number" ? scale : 1);
|
|
26
|
-
const [contentHeight, setContentHeight] = react.useState(void 0);
|
|
27
|
-
react.useLayoutEffect(() => {
|
|
28
|
-
if (!scaled) return;
|
|
29
|
-
const viewport = viewportRef.current;
|
|
30
|
-
const stage = stageRef.current;
|
|
31
|
-
if (!viewport || !stage) return;
|
|
32
|
-
if (typeof ResizeObserver === "undefined") return;
|
|
33
|
-
const measure = () => {
|
|
34
|
-
const avail = viewport.clientWidth;
|
|
35
|
-
const next = scale === "fit" ? width && width > 0 ? avail / width : 1 : scale;
|
|
36
|
-
const naturalHeight = stage.offsetHeight;
|
|
37
|
-
setS(next);
|
|
38
|
-
setContentHeight(naturalHeight * next);
|
|
39
|
-
};
|
|
40
|
-
measure();
|
|
41
|
-
const ro = new ResizeObserver(measure);
|
|
42
|
-
ro.observe(viewport);
|
|
43
|
-
ro.observe(stage);
|
|
44
|
-
return () => ro.disconnect();
|
|
45
|
-
}, [scaled, scale, width]);
|
|
46
|
-
const isBrowser = variant === "browser";
|
|
47
|
-
const isDevice = variant === "device";
|
|
48
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
49
|
-
"div",
|
|
50
|
-
{
|
|
51
|
-
ref,
|
|
52
|
-
"data-react-fancy-faux-client": "",
|
|
53
|
-
className: chunkZU5Q77OT_cjs.cn(
|
|
54
|
-
"overflow-hidden bg-white dark:bg-zinc-900",
|
|
55
|
-
isBrowser && "rounded-2xl border border-zinc-200 shadow-xl dark:border-zinc-800",
|
|
56
|
-
isDevice && "rounded-[2rem] border-[10px] border-zinc-800 shadow-2xl dark:border-zinc-700",
|
|
57
|
-
variant === "bare" && "rounded-lg border border-zinc-200 dark:border-zinc-800",
|
|
58
|
-
className
|
|
59
|
-
),
|
|
60
|
-
...rest,
|
|
61
|
-
children: [
|
|
62
|
-
isBrowser && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
63
|
-
"div",
|
|
64
|
-
{
|
|
65
|
-
className: chunkZU5Q77OT_cjs.cn(
|
|
66
|
-
"flex items-center gap-2 border-b border-zinc-200 bg-zinc-50 px-3.5 py-2.5 font-mono text-[11.5px] text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400",
|
|
67
|
-
barClassName
|
|
68
|
-
),
|
|
69
|
-
children: [
|
|
70
|
-
dots && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mr-1.5 flex gap-[5px]", children: DOT_COLORS.map((color, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: chunkZU5Q77OT_cjs.cn("h-[9px] w-[9px] rounded-full", color) }, i)) }),
|
|
71
|
-
url != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: url }),
|
|
72
|
-
meta != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-zinc-400 dark:text-zinc-500", children: meta })
|
|
73
|
-
]
|
|
74
|
-
}
|
|
75
|
-
),
|
|
76
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
77
|
-
"div",
|
|
78
|
-
{
|
|
79
|
-
ref: viewportRef,
|
|
80
|
-
className: chunkZU5Q77OT_cjs.cn("relative overflow-hidden", bodyClassName),
|
|
81
|
-
style: scaled ? { height: contentHeight } : void 0,
|
|
82
|
-
children: scaled ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
83
|
-
"div",
|
|
84
|
-
{
|
|
85
|
-
ref: stageRef,
|
|
86
|
-
className: "absolute left-0 top-0 origin-top-left",
|
|
87
|
-
style: { width, transform: `scale(${s})` },
|
|
88
|
-
children
|
|
89
|
-
}
|
|
90
|
-
) : children
|
|
91
|
-
}
|
|
92
|
-
)
|
|
93
|
-
]
|
|
94
|
-
}
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
);
|
|
98
|
-
FauxClient.displayName = "FauxClient";
|
|
99
|
-
|
|
100
|
-
exports.FauxClient = FauxClient;
|
|
101
|
-
//# sourceMappingURL=chunk-WCWZADR2.cjs.map
|
|
102
|
-
//# sourceMappingURL=chunk-WCWZADR2.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/FauxClient/FauxClient.tsx"],"names":["forwardRef","useRef","useState","useLayoutEffect","jsxs","cn","jsx"],"mappings":"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","file":"chunk-WCWZADR2.cjs","sourcesContent":["import { forwardRef, useLayoutEffect, useRef, useState } from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport type { FauxClientProps } from \"./FauxClient.types\";\n\nconst DOT_COLORS = [\"bg-rose-300\", \"bg-amber-300\", \"bg-green-300\"];\n\n/**\n * A frame that mimics a browser window or device, rendering **real UI** inside.\n * With a logical `width` + `scale=\"fit\"` it renders a full-size page/app and\n * scales it down to fit any container (live thumbnails, device mockups) — the\n * content is real and interactive, just zoomed.\n *\n * Omit `width` for chrome around natural-size content (e.g. a code/preview card).\n */\nexport const FauxClient = forwardRef<HTMLDivElement, FauxClientProps>(\n (\n {\n variant = \"browser\",\n url,\n meta,\n dots = true,\n width,\n scale = \"fit\",\n className,\n barClassName,\n bodyClassName,\n children,\n ...rest\n },\n ref,\n ) => {\n const scaled = typeof width === \"number\";\n const viewportRef = useRef<HTMLDivElement>(null);\n const stageRef = useRef<HTMLDivElement>(null);\n const [s, setS] = useState(typeof scale === \"number\" ? scale : 1);\n const [contentHeight, setContentHeight] = useState<number | undefined>(undefined);\n\n useLayoutEffect(() => {\n if (!scaled) return;\n const viewport = viewportRef.current;\n const stage = stageRef.current;\n if (!viewport || !stage) return;\n if (typeof ResizeObserver === \"undefined\") return;\n\n const measure = () => {\n const avail = viewport.clientWidth;\n const next = scale === \"fit\" ? (width && width > 0 ? avail / width : 1) : scale;\n // offsetHeight is the pre-transform layout height (CSS transforms don't affect it).\n const naturalHeight = stage.offsetHeight;\n setS(next);\n setContentHeight(naturalHeight * next);\n };\n\n measure();\n const ro = new ResizeObserver(measure);\n ro.observe(viewport);\n ro.observe(stage);\n return () => ro.disconnect();\n }, [scaled, scale, width]);\n\n const isBrowser = variant === \"browser\";\n const isDevice = variant === \"device\";\n\n return (\n <div\n ref={ref}\n data-react-fancy-faux-client=\"\"\n className={cn(\n \"overflow-hidden bg-white dark:bg-zinc-900\",\n isBrowser && \"rounded-2xl border border-zinc-200 shadow-xl dark:border-zinc-800\",\n isDevice && \"rounded-[2rem] border-[10px] border-zinc-800 shadow-2xl dark:border-zinc-700\",\n variant === \"bare\" && \"rounded-lg border border-zinc-200 dark:border-zinc-800\",\n className,\n )}\n {...rest}\n >\n {isBrowser && (\n <div\n className={cn(\n \"flex items-center gap-2 border-b border-zinc-200 bg-zinc-50 px-3.5 py-2.5 font-mono text-[11.5px] text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-400\",\n barClassName,\n )}\n >\n {dots && (\n <div className=\"mr-1.5 flex gap-[5px]\">\n {DOT_COLORS.map((color, i) => (\n <span key={i} className={cn(\"h-[9px] w-[9px] rounded-full\", color)} />\n ))}\n </div>\n )}\n {url != null && <span className=\"min-w-0 flex-1 truncate\">{url}</span>}\n {meta != null && <span className=\"shrink-0 text-zinc-400 dark:text-zinc-500\">{meta}</span>}\n </div>\n )}\n\n <div\n ref={viewportRef}\n className={cn(\"relative overflow-hidden\", bodyClassName)}\n style={scaled ? { height: contentHeight } : undefined}\n >\n {scaled ? (\n <div\n ref={stageRef}\n className=\"absolute left-0 top-0 origin-top-left\"\n style={{ width, transform: `scale(${s})` }}\n >\n {children}\n </div>\n ) : (\n children\n )}\n </div>\n </div>\n );\n },\n);\n\nFauxClient.displayName = \"FauxClient\";\n"]}
|