@particle-academy/react-fancy 5.45.0 → 5.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/dist/{chunk-4TFTLPAC.js → chunk-A5ENSMZP.js} +107 -71
- package/dist/chunk-A5ENSMZP.js.map +1 -0
- package/dist/{chunk-HM7SW53Z.cjs → chunk-S3WWYBL4.cjs} +106 -70
- package/dist/chunk-S3WWYBL4.cjs.map +1 -0
- package/dist/faux-client.cjs +2 -2
- package/dist/faux-client.d.cts +6 -0
- package/dist/faux-client.d.ts +6 -0
- package/dist/faux-client.js +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-4TFTLPAC.js.map +0 -1
- package/dist/chunk-HM7SW53Z.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [5.46.0] - 2026-10-19
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`FauxClient` `fill`**: the frame takes its parent's height and gives the
|
|
19
|
+
content whatever the chrome leaves, for content that sizes itself to its box
|
|
20
|
+
(a slide viewer, a spreadsheet) rather than to what is in it. Without it, a
|
|
21
|
+
viewer at `height: 100%` inside the frame collapsed to nothing.
|
|
22
|
+
**Nothing to do:** it defaults to `false`.
|
|
23
|
+
|
|
24
|
+
## [5.45.1] - 2026-10-19
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **`FauxClient`'s controls stay out of sight until wanted.** They are hidden,
|
|
29
|
+
and fade in when the pointer is over the frame or focus is inside it; on a
|
|
30
|
+
touch screen, which has no hover, they are always shown. In a browser frame
|
|
31
|
+
they sit inline in the address bar as ghost icons; on a device or bare frame
|
|
32
|
+
they are a slim pill in the top-right corner, leaving the bottom corners to
|
|
33
|
+
the content (a slide viewer's page count lives there). 5.45.0 drew them as a
|
|
34
|
+
bordered box over the content's bottom-right corner, permanently.
|
|
35
|
+
**Nothing to do:** same props, same `data-*` handles.
|
|
36
|
+
|
|
14
37
|
## [5.45.0] - 2026-10-19
|
|
15
38
|
|
|
16
39
|
### Added
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useControllableState } from './chunk-EDYAXGUM.js';
|
|
2
2
|
import { cn } from './chunk-MMW2JGKZ.js';
|
|
3
3
|
import { forwardRef, useRef, useState, useLayoutEffect, useEffect, useCallback } from 'react';
|
|
4
|
-
import { jsxs,
|
|
4
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
5
5
|
|
|
6
6
|
var DOT_COLORS = ["bg-rose-300", "bg-amber-300", "bg-green-300"];
|
|
7
7
|
var ZOOM_STEP = 1.25;
|
|
@@ -21,6 +21,7 @@ var FauxClient = forwardRef(
|
|
|
21
21
|
minZoom = 0.5,
|
|
22
22
|
maxZoom = 4,
|
|
23
23
|
onFullscreenChange,
|
|
24
|
+
fill = false,
|
|
24
25
|
className,
|
|
25
26
|
barClassName,
|
|
26
27
|
bodyClassName,
|
|
@@ -82,6 +83,76 @@ var FauxClient = forwardRef(
|
|
|
82
83
|
const isDevice = variant === "device";
|
|
83
84
|
const effective = fit * zoom;
|
|
84
85
|
const zoomed = zoom !== 1;
|
|
86
|
+
const hasControls = showZoom || showFullscreen;
|
|
87
|
+
const controlsBar = hasControls ? /* @__PURE__ */ jsxs(
|
|
88
|
+
"div",
|
|
89
|
+
{
|
|
90
|
+
"data-react-fancy-faux-client-controls": "",
|
|
91
|
+
className: cn(
|
|
92
|
+
"pointer-events-auto flex shrink-0 items-center gap-px opacity-0 transition-opacity duration-150",
|
|
93
|
+
"group-hover/faux-client:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100",
|
|
94
|
+
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"
|
|
95
|
+
),
|
|
96
|
+
children: [
|
|
97
|
+
showZoom && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
98
|
+
/* @__PURE__ */ jsx(
|
|
99
|
+
"button",
|
|
100
|
+
{
|
|
101
|
+
type: "button",
|
|
102
|
+
"data-react-fancy-faux-client-zoom-out": "",
|
|
103
|
+
"aria-label": "Zoom out",
|
|
104
|
+
title: "Zoom out",
|
|
105
|
+
disabled: zoom <= minZoom,
|
|
106
|
+
onClick: () => zoomBy(1 / ZOOM_STEP),
|
|
107
|
+
className: controlButton(isBrowser),
|
|
108
|
+
children: /* @__PURE__ */ jsx(ZoomOutIcon, {})
|
|
109
|
+
}
|
|
110
|
+
),
|
|
111
|
+
/* @__PURE__ */ jsxs(
|
|
112
|
+
"button",
|
|
113
|
+
{
|
|
114
|
+
type: "button",
|
|
115
|
+
"data-react-fancy-faux-client-zoom-reset": "",
|
|
116
|
+
"aria-label": "Reset zoom",
|
|
117
|
+
title: "Reset zoom",
|
|
118
|
+
onClick: () => setZoom(1),
|
|
119
|
+
className: cn(controlButton(isBrowser), "w-auto min-w-[2.25rem] px-1 font-sans text-[10.5px] tabular-nums"),
|
|
120
|
+
children: [
|
|
121
|
+
Math.round(zoom * 100),
|
|
122
|
+
"%"
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
),
|
|
126
|
+
/* @__PURE__ */ jsx(
|
|
127
|
+
"button",
|
|
128
|
+
{
|
|
129
|
+
type: "button",
|
|
130
|
+
"data-react-fancy-faux-client-zoom-in": "",
|
|
131
|
+
"aria-label": "Zoom in",
|
|
132
|
+
title: "Zoom in",
|
|
133
|
+
disabled: zoom >= maxZoom,
|
|
134
|
+
onClick: () => zoomBy(ZOOM_STEP),
|
|
135
|
+
className: controlButton(isBrowser),
|
|
136
|
+
children: /* @__PURE__ */ jsx(ZoomInIcon, {})
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
] }),
|
|
140
|
+
showFullscreen && /* @__PURE__ */ jsx(
|
|
141
|
+
"button",
|
|
142
|
+
{
|
|
143
|
+
type: "button",
|
|
144
|
+
"data-react-fancy-faux-client-fullscreen": "",
|
|
145
|
+
"aria-label": fullscreen ? "Exit full screen" : "Full screen",
|
|
146
|
+
"aria-pressed": fullscreen,
|
|
147
|
+
title: fullscreen ? "Exit full screen" : "Full screen",
|
|
148
|
+
onClick: toggleFullscreen,
|
|
149
|
+
className: controlButton(isBrowser),
|
|
150
|
+
children: fullscreen ? /* @__PURE__ */ jsx(ExitFullscreenIcon, {}) : /* @__PURE__ */ jsx(FullscreenIcon, {})
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
]
|
|
154
|
+
}
|
|
155
|
+
) : null;
|
|
85
156
|
return /* @__PURE__ */ jsxs(
|
|
86
157
|
"div",
|
|
87
158
|
{
|
|
@@ -89,12 +160,15 @@ var FauxClient = forwardRef(
|
|
|
89
160
|
"data-react-fancy-faux-client": "",
|
|
90
161
|
"data-fullscreen": showFullscreen ? String(fullscreen) : void 0,
|
|
91
162
|
className: cn(
|
|
92
|
-
"relative overflow-hidden bg-white dark:bg-zinc-900",
|
|
163
|
+
"group/faux-client relative overflow-hidden bg-white dark:bg-zinc-900",
|
|
93
164
|
isBrowser && "rounded-2xl border border-zinc-200 shadow-xl dark:border-zinc-800",
|
|
94
165
|
isDevice && "rounded-[2rem] border-[10px] border-zinc-800 shadow-2xl dark:border-zinc-700",
|
|
95
166
|
variant === "bare" && "rounded-lg border border-zinc-200 dark:border-zinc-800",
|
|
96
|
-
//
|
|
97
|
-
|
|
167
|
+
// Fill (or full screen): the frame takes its parent's height and its
|
|
168
|
+
// body takes whatever the chrome leaves.
|
|
169
|
+
(fill || fullscreen) && "flex flex-col",
|
|
170
|
+
fill && "h-full",
|
|
171
|
+
fullscreen && "rounded-none",
|
|
98
172
|
className
|
|
99
173
|
),
|
|
100
174
|
...rest,
|
|
@@ -109,7 +183,8 @@ var FauxClient = forwardRef(
|
|
|
109
183
|
children: [
|
|
110
184
|
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)) }),
|
|
111
185
|
url != null && /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: url }),
|
|
112
|
-
meta != null && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-zinc-400 dark:text-zinc-500", children: meta })
|
|
186
|
+
meta != null && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-zinc-400 dark:text-zinc-500", children: meta }),
|
|
187
|
+
hasControls && /* @__PURE__ */ jsx("div", { className: cn(url == null && meta == null && "ml-auto"), children: controlsBar })
|
|
113
188
|
]
|
|
114
189
|
}
|
|
115
190
|
),
|
|
@@ -121,7 +196,7 @@ var FauxClient = forwardRef(
|
|
|
121
196
|
"relative",
|
|
122
197
|
// Zoomed in, the content is bigger than the frame: scroll to it.
|
|
123
198
|
zoomed ? "overflow-auto" : "overflow-hidden",
|
|
124
|
-
fullscreen && "min-h-0 flex-1",
|
|
199
|
+
(fill || fullscreen) && "min-h-0 flex-1",
|
|
125
200
|
bodyClassName
|
|
126
201
|
),
|
|
127
202
|
style: scaled && !fullscreen && naturalHeight !== void 0 ? { height: naturalHeight * fit } : void 0,
|
|
@@ -147,81 +222,42 @@ var FauxClient = forwardRef(
|
|
|
147
222
|
"div",
|
|
148
223
|
{
|
|
149
224
|
"data-react-fancy-faux-client-content": "",
|
|
150
|
-
className: cn(fullscreen && "h-full"),
|
|
225
|
+
className: cn((fill || fullscreen) && "h-full"),
|
|
151
226
|
style: zoomed ? { zoom: String(zoom) } : void 0,
|
|
152
227
|
children
|
|
153
228
|
}
|
|
154
229
|
)
|
|
155
230
|
}
|
|
156
231
|
),
|
|
157
|
-
|
|
158
|
-
"div",
|
|
159
|
-
{
|
|
160
|
-
"data-react-fancy-faux-client-controls": "",
|
|
161
|
-
className: "absolute bottom-2 right-2 z-10 flex items-center gap-0.5 rounded-md border border-zinc-200 bg-white/90 p-0.5 text-zinc-700 shadow-sm backdrop-blur dark:border-zinc-700 dark:bg-zinc-900/90 dark:text-zinc-200",
|
|
162
|
-
children: [
|
|
163
|
-
showZoom && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
164
|
-
/* @__PURE__ */ jsx(
|
|
165
|
-
"button",
|
|
166
|
-
{
|
|
167
|
-
type: "button",
|
|
168
|
-
"data-react-fancy-faux-client-zoom-out": "",
|
|
169
|
-
"aria-label": "Zoom out",
|
|
170
|
-
disabled: zoom <= minZoom,
|
|
171
|
-
onClick: () => zoomBy(1 / ZOOM_STEP),
|
|
172
|
-
className: "flex h-6 w-6 items-center justify-center rounded text-base leading-none hover:bg-zinc-100 disabled:opacity-40 dark:hover:bg-zinc-800",
|
|
173
|
-
children: "\u2212"
|
|
174
|
-
}
|
|
175
|
-
),
|
|
176
|
-
/* @__PURE__ */ jsxs(
|
|
177
|
-
"button",
|
|
178
|
-
{
|
|
179
|
-
type: "button",
|
|
180
|
-
"data-react-fancy-faux-client-zoom-reset": "",
|
|
181
|
-
"aria-label": "Reset zoom",
|
|
182
|
-
onClick: () => setZoom(1),
|
|
183
|
-
className: "min-w-[3rem] rounded px-1 text-center text-xs tabular-nums hover:bg-zinc-100 dark:hover:bg-zinc-800",
|
|
184
|
-
children: [
|
|
185
|
-
Math.round(zoom * 100),
|
|
186
|
-
"%"
|
|
187
|
-
]
|
|
188
|
-
}
|
|
189
|
-
),
|
|
190
|
-
/* @__PURE__ */ jsx(
|
|
191
|
-
"button",
|
|
192
|
-
{
|
|
193
|
-
type: "button",
|
|
194
|
-
"data-react-fancy-faux-client-zoom-in": "",
|
|
195
|
-
"aria-label": "Zoom in",
|
|
196
|
-
disabled: zoom >= maxZoom,
|
|
197
|
-
onClick: () => zoomBy(ZOOM_STEP),
|
|
198
|
-
className: "flex h-6 w-6 items-center justify-center rounded text-base leading-none hover:bg-zinc-100 disabled:opacity-40 dark:hover:bg-zinc-800",
|
|
199
|
-
children: "+"
|
|
200
|
-
}
|
|
201
|
-
)
|
|
202
|
-
] }),
|
|
203
|
-
showFullscreen && /* @__PURE__ */ jsx(
|
|
204
|
-
"button",
|
|
205
|
-
{
|
|
206
|
-
type: "button",
|
|
207
|
-
"data-react-fancy-faux-client-fullscreen": "",
|
|
208
|
-
"aria-label": fullscreen ? "Exit full screen" : "Full screen",
|
|
209
|
-
"aria-pressed": fullscreen,
|
|
210
|
-
title: fullscreen ? "Exit full screen" : "Full screen",
|
|
211
|
-
onClick: toggleFullscreen,
|
|
212
|
-
className: "flex h-6 w-6 items-center justify-center rounded hover:bg-zinc-100 dark:hover:bg-zinc-800",
|
|
213
|
-
children: fullscreen ? /* @__PURE__ */ jsx(ExitFullscreenIcon, {}) : /* @__PURE__ */ jsx(FullscreenIcon, {})
|
|
214
|
-
}
|
|
215
|
-
)
|
|
216
|
-
]
|
|
217
|
-
}
|
|
218
|
-
)
|
|
232
|
+
hasControls && !isBrowser && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute right-2 top-2 z-10", children: controlsBar })
|
|
219
233
|
]
|
|
220
234
|
}
|
|
221
235
|
);
|
|
222
236
|
}
|
|
223
237
|
);
|
|
224
238
|
FauxClient.displayName = "FauxClient";
|
|
239
|
+
function controlButton(inBar) {
|
|
240
|
+
return cn(
|
|
241
|
+
"flex h-6 w-6 items-center justify-center rounded-full transition-colors disabled:pointer-events-none disabled:opacity-30",
|
|
242
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/50",
|
|
243
|
+
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"
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
function ZoomOutIcon() {
|
|
247
|
+
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: [
|
|
248
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
249
|
+
/* @__PURE__ */ jsx("path", { d: "m20 20-3.5-3.5" }),
|
|
250
|
+
/* @__PURE__ */ jsx("path", { d: "M8 11h6" })
|
|
251
|
+
] });
|
|
252
|
+
}
|
|
253
|
+
function ZoomInIcon() {
|
|
254
|
+
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: [
|
|
255
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
256
|
+
/* @__PURE__ */ jsx("path", { d: "m20 20-3.5-3.5" }),
|
|
257
|
+
/* @__PURE__ */ jsx("path", { d: "M8 11h6" }),
|
|
258
|
+
/* @__PURE__ */ jsx("path", { d: "M11 8v6" })
|
|
259
|
+
] });
|
|
260
|
+
}
|
|
225
261
|
function FullscreenIcon() {
|
|
226
262
|
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: [
|
|
227
263
|
/* @__PURE__ */ jsx("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
|
|
@@ -240,5 +276,5 @@ function ExitFullscreenIcon() {
|
|
|
240
276
|
}
|
|
241
277
|
|
|
242
278
|
export { FauxClient };
|
|
243
|
-
//# sourceMappingURL=chunk-
|
|
244
|
-
//# sourceMappingURL=chunk-
|
|
279
|
+
//# sourceMappingURL=chunk-A5ENSMZP.js.map
|
|
280
|
+
//# sourceMappingURL=chunk-A5ENSMZP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/FauxClient/FauxClient.tsx"],"names":[],"mappings":"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","file":"chunk-A5ENSMZP.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 fill = false,\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 // Fill (or full screen): the frame takes its parent's height and its\n // body takes whatever the chrome leaves.\n (fill || fullscreen) && \"flex flex-col\",\n fill && \"h-full\",\n fullscreen && \"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 (fill || 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((fill || 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"]}
|
|
@@ -23,6 +23,7 @@ var FauxClient = react.forwardRef(
|
|
|
23
23
|
minZoom = 0.5,
|
|
24
24
|
maxZoom = 4,
|
|
25
25
|
onFullscreenChange,
|
|
26
|
+
fill = false,
|
|
26
27
|
className,
|
|
27
28
|
barClassName,
|
|
28
29
|
bodyClassName,
|
|
@@ -84,6 +85,76 @@ var FauxClient = react.forwardRef(
|
|
|
84
85
|
const isDevice = variant === "device";
|
|
85
86
|
const effective = fit * zoom;
|
|
86
87
|
const zoomed = zoom !== 1;
|
|
88
|
+
const hasControls = showZoom || showFullscreen;
|
|
89
|
+
const controlsBar = hasControls ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
90
|
+
"div",
|
|
91
|
+
{
|
|
92
|
+
"data-react-fancy-faux-client-controls": "",
|
|
93
|
+
className: chunkZU5Q77OT_cjs.cn(
|
|
94
|
+
"pointer-events-auto flex shrink-0 items-center gap-px opacity-0 transition-opacity duration-150",
|
|
95
|
+
"group-hover/faux-client:opacity-100 focus-within:opacity-100 [@media(hover:none)]:opacity-100",
|
|
96
|
+
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"
|
|
97
|
+
),
|
|
98
|
+
children: [
|
|
99
|
+
showZoom && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
100
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
101
|
+
"button",
|
|
102
|
+
{
|
|
103
|
+
type: "button",
|
|
104
|
+
"data-react-fancy-faux-client-zoom-out": "",
|
|
105
|
+
"aria-label": "Zoom out",
|
|
106
|
+
title: "Zoom out",
|
|
107
|
+
disabled: zoom <= minZoom,
|
|
108
|
+
onClick: () => zoomBy(1 / ZOOM_STEP),
|
|
109
|
+
className: controlButton(isBrowser),
|
|
110
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ZoomOutIcon, {})
|
|
111
|
+
}
|
|
112
|
+
),
|
|
113
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
114
|
+
"button",
|
|
115
|
+
{
|
|
116
|
+
type: "button",
|
|
117
|
+
"data-react-fancy-faux-client-zoom-reset": "",
|
|
118
|
+
"aria-label": "Reset zoom",
|
|
119
|
+
title: "Reset zoom",
|
|
120
|
+
onClick: () => setZoom(1),
|
|
121
|
+
className: chunkZU5Q77OT_cjs.cn(controlButton(isBrowser), "w-auto min-w-[2.25rem] px-1 font-sans text-[10.5px] tabular-nums"),
|
|
122
|
+
children: [
|
|
123
|
+
Math.round(zoom * 100),
|
|
124
|
+
"%"
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
),
|
|
128
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
129
|
+
"button",
|
|
130
|
+
{
|
|
131
|
+
type: "button",
|
|
132
|
+
"data-react-fancy-faux-client-zoom-in": "",
|
|
133
|
+
"aria-label": "Zoom in",
|
|
134
|
+
title: "Zoom in",
|
|
135
|
+
disabled: zoom >= maxZoom,
|
|
136
|
+
onClick: () => zoomBy(ZOOM_STEP),
|
|
137
|
+
className: controlButton(isBrowser),
|
|
138
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(ZoomInIcon, {})
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
] }),
|
|
142
|
+
showFullscreen && /* @__PURE__ */ jsxRuntime.jsx(
|
|
143
|
+
"button",
|
|
144
|
+
{
|
|
145
|
+
type: "button",
|
|
146
|
+
"data-react-fancy-faux-client-fullscreen": "",
|
|
147
|
+
"aria-label": fullscreen ? "Exit full screen" : "Full screen",
|
|
148
|
+
"aria-pressed": fullscreen,
|
|
149
|
+
title: fullscreen ? "Exit full screen" : "Full screen",
|
|
150
|
+
onClick: toggleFullscreen,
|
|
151
|
+
className: controlButton(isBrowser),
|
|
152
|
+
children: fullscreen ? /* @__PURE__ */ jsxRuntime.jsx(ExitFullscreenIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(FullscreenIcon, {})
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
) : null;
|
|
87
158
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
88
159
|
"div",
|
|
89
160
|
{
|
|
@@ -91,12 +162,15 @@ var FauxClient = react.forwardRef(
|
|
|
91
162
|
"data-react-fancy-faux-client": "",
|
|
92
163
|
"data-fullscreen": showFullscreen ? String(fullscreen) : void 0,
|
|
93
164
|
className: chunkZU5Q77OT_cjs.cn(
|
|
94
|
-
"relative overflow-hidden bg-white dark:bg-zinc-900",
|
|
165
|
+
"group/faux-client relative overflow-hidden bg-white dark:bg-zinc-900",
|
|
95
166
|
isBrowser && "rounded-2xl border border-zinc-200 shadow-xl dark:border-zinc-800",
|
|
96
167
|
isDevice && "rounded-[2rem] border-[10px] border-zinc-800 shadow-2xl dark:border-zinc-700",
|
|
97
168
|
variant === "bare" && "rounded-lg border border-zinc-200 dark:border-zinc-800",
|
|
98
|
-
//
|
|
99
|
-
|
|
169
|
+
// Fill (or full screen): the frame takes its parent's height and its
|
|
170
|
+
// body takes whatever the chrome leaves.
|
|
171
|
+
(fill || fullscreen) && "flex flex-col",
|
|
172
|
+
fill && "h-full",
|
|
173
|
+
fullscreen && "rounded-none",
|
|
100
174
|
className
|
|
101
175
|
),
|
|
102
176
|
...rest,
|
|
@@ -111,7 +185,8 @@ var FauxClient = react.forwardRef(
|
|
|
111
185
|
children: [
|
|
112
186
|
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)) }),
|
|
113
187
|
url != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate", children: url }),
|
|
114
|
-
meta != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-zinc-400 dark:text-zinc-500", children: meta })
|
|
188
|
+
meta != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-zinc-400 dark:text-zinc-500", children: meta }),
|
|
189
|
+
hasControls && /* @__PURE__ */ jsxRuntime.jsx("div", { className: chunkZU5Q77OT_cjs.cn(url == null && meta == null && "ml-auto"), children: controlsBar })
|
|
115
190
|
]
|
|
116
191
|
}
|
|
117
192
|
),
|
|
@@ -123,7 +198,7 @@ var FauxClient = react.forwardRef(
|
|
|
123
198
|
"relative",
|
|
124
199
|
// Zoomed in, the content is bigger than the frame: scroll to it.
|
|
125
200
|
zoomed ? "overflow-auto" : "overflow-hidden",
|
|
126
|
-
fullscreen && "min-h-0 flex-1",
|
|
201
|
+
(fill || fullscreen) && "min-h-0 flex-1",
|
|
127
202
|
bodyClassName
|
|
128
203
|
),
|
|
129
204
|
style: scaled && !fullscreen && naturalHeight !== void 0 ? { height: naturalHeight * fit } : void 0,
|
|
@@ -149,81 +224,42 @@ var FauxClient = react.forwardRef(
|
|
|
149
224
|
"div",
|
|
150
225
|
{
|
|
151
226
|
"data-react-fancy-faux-client-content": "",
|
|
152
|
-
className: chunkZU5Q77OT_cjs.cn(fullscreen && "h-full"),
|
|
227
|
+
className: chunkZU5Q77OT_cjs.cn((fill || fullscreen) && "h-full"),
|
|
153
228
|
style: zoomed ? { zoom: String(zoom) } : void 0,
|
|
154
229
|
children
|
|
155
230
|
}
|
|
156
231
|
)
|
|
157
232
|
}
|
|
158
233
|
),
|
|
159
|
-
|
|
160
|
-
"div",
|
|
161
|
-
{
|
|
162
|
-
"data-react-fancy-faux-client-controls": "",
|
|
163
|
-
className: "absolute bottom-2 right-2 z-10 flex items-center gap-0.5 rounded-md border border-zinc-200 bg-white/90 p-0.5 text-zinc-700 shadow-sm backdrop-blur dark:border-zinc-700 dark:bg-zinc-900/90 dark:text-zinc-200",
|
|
164
|
-
children: [
|
|
165
|
-
showZoom && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
166
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
167
|
-
"button",
|
|
168
|
-
{
|
|
169
|
-
type: "button",
|
|
170
|
-
"data-react-fancy-faux-client-zoom-out": "",
|
|
171
|
-
"aria-label": "Zoom out",
|
|
172
|
-
disabled: zoom <= minZoom,
|
|
173
|
-
onClick: () => zoomBy(1 / ZOOM_STEP),
|
|
174
|
-
className: "flex h-6 w-6 items-center justify-center rounded text-base leading-none hover:bg-zinc-100 disabled:opacity-40 dark:hover:bg-zinc-800",
|
|
175
|
-
children: "\u2212"
|
|
176
|
-
}
|
|
177
|
-
),
|
|
178
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
179
|
-
"button",
|
|
180
|
-
{
|
|
181
|
-
type: "button",
|
|
182
|
-
"data-react-fancy-faux-client-zoom-reset": "",
|
|
183
|
-
"aria-label": "Reset zoom",
|
|
184
|
-
onClick: () => setZoom(1),
|
|
185
|
-
className: "min-w-[3rem] rounded px-1 text-center text-xs tabular-nums hover:bg-zinc-100 dark:hover:bg-zinc-800",
|
|
186
|
-
children: [
|
|
187
|
-
Math.round(zoom * 100),
|
|
188
|
-
"%"
|
|
189
|
-
]
|
|
190
|
-
}
|
|
191
|
-
),
|
|
192
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
193
|
-
"button",
|
|
194
|
-
{
|
|
195
|
-
type: "button",
|
|
196
|
-
"data-react-fancy-faux-client-zoom-in": "",
|
|
197
|
-
"aria-label": "Zoom in",
|
|
198
|
-
disabled: zoom >= maxZoom,
|
|
199
|
-
onClick: () => zoomBy(ZOOM_STEP),
|
|
200
|
-
className: "flex h-6 w-6 items-center justify-center rounded text-base leading-none hover:bg-zinc-100 disabled:opacity-40 dark:hover:bg-zinc-800",
|
|
201
|
-
children: "+"
|
|
202
|
-
}
|
|
203
|
-
)
|
|
204
|
-
] }),
|
|
205
|
-
showFullscreen && /* @__PURE__ */ jsxRuntime.jsx(
|
|
206
|
-
"button",
|
|
207
|
-
{
|
|
208
|
-
type: "button",
|
|
209
|
-
"data-react-fancy-faux-client-fullscreen": "",
|
|
210
|
-
"aria-label": fullscreen ? "Exit full screen" : "Full screen",
|
|
211
|
-
"aria-pressed": fullscreen,
|
|
212
|
-
title: fullscreen ? "Exit full screen" : "Full screen",
|
|
213
|
-
onClick: toggleFullscreen,
|
|
214
|
-
className: "flex h-6 w-6 items-center justify-center rounded hover:bg-zinc-100 dark:hover:bg-zinc-800",
|
|
215
|
-
children: fullscreen ? /* @__PURE__ */ jsxRuntime.jsx(ExitFullscreenIcon, {}) : /* @__PURE__ */ jsxRuntime.jsx(FullscreenIcon, {})
|
|
216
|
-
}
|
|
217
|
-
)
|
|
218
|
-
]
|
|
219
|
-
}
|
|
220
|
-
)
|
|
234
|
+
hasControls && !isBrowser && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute right-2 top-2 z-10", children: controlsBar })
|
|
221
235
|
]
|
|
222
236
|
}
|
|
223
237
|
);
|
|
224
238
|
}
|
|
225
239
|
);
|
|
226
240
|
FauxClient.displayName = "FauxClient";
|
|
241
|
+
function controlButton(inBar) {
|
|
242
|
+
return chunkZU5Q77OT_cjs.cn(
|
|
243
|
+
"flex h-6 w-6 items-center justify-center rounded-full transition-colors disabled:pointer-events-none disabled:opacity-30",
|
|
244
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/50",
|
|
245
|
+
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"
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
function ZoomOutIcon() {
|
|
249
|
+
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: [
|
|
250
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
251
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m20 20-3.5-3.5" }),
|
|
252
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 11h6" })
|
|
253
|
+
] });
|
|
254
|
+
}
|
|
255
|
+
function ZoomInIcon() {
|
|
256
|
+
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: [
|
|
257
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "11", cy: "11", r: "7" }),
|
|
258
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m20 20-3.5-3.5" }),
|
|
259
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 11h6" }),
|
|
260
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M11 8v6" })
|
|
261
|
+
] });
|
|
262
|
+
}
|
|
227
263
|
function FullscreenIcon() {
|
|
228
264
|
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: [
|
|
229
265
|
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
|
|
@@ -242,5 +278,5 @@ function ExitFullscreenIcon() {
|
|
|
242
278
|
}
|
|
243
279
|
|
|
244
280
|
exports.FauxClient = FauxClient;
|
|
245
|
-
//# sourceMappingURL=chunk-
|
|
246
|
-
//# sourceMappingURL=chunk-
|
|
281
|
+
//# sourceMappingURL=chunk-S3WWYBL4.cjs.map
|
|
282
|
+
//# sourceMappingURL=chunk-S3WWYBL4.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-S3WWYBL4.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 fill = false,\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 // Fill (or full screen): the frame takes its parent's height and its\n // body takes whatever the chrome leaves.\n (fill || fullscreen) && \"flex flex-col\",\n fill && \"h-full\",\n fullscreen && \"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 (fill || 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((fill || 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,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkS3WWYBL4_cjs = require('./chunk-S3WWYBL4.cjs');
|
|
4
4
|
require('./chunk-SNHU2AI4.cjs');
|
|
5
5
|
require('./chunk-ZU5Q77OT.cjs');
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ require('./chunk-ZU5Q77OT.cjs');
|
|
|
8
8
|
|
|
9
9
|
Object.defineProperty(exports, "FauxClient", {
|
|
10
10
|
enumerable: true,
|
|
11
|
-
get: function () { return
|
|
11
|
+
get: function () { return chunkS3WWYBL4_cjs.FauxClient; }
|
|
12
12
|
});
|
|
13
13
|
//# sourceMappingURL=faux-client.cjs.map
|
|
14
14
|
//# sourceMappingURL=faux-client.cjs.map
|
package/dist/faux-client.d.cts
CHANGED
|
@@ -41,6 +41,12 @@ interface FauxClientProps extends Omit<HTMLAttributes<HTMLDivElement>, "children
|
|
|
41
41
|
* `data-fullscreen="true"` while it is, for styling the content.
|
|
42
42
|
*/
|
|
43
43
|
onFullscreenChange?: (fullscreen: boolean) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Take the parent's height, and give the content whatever the chrome leaves.
|
|
46
|
+
* For content that sizes itself to its box (a slide viewer, a spreadsheet)
|
|
47
|
+
* rather than to what is in it. Default `false`.
|
|
48
|
+
*/
|
|
49
|
+
fill?: boolean;
|
|
44
50
|
className?: string;
|
|
45
51
|
/** Class for the browser bar. */
|
|
46
52
|
barClassName?: string;
|
package/dist/faux-client.d.ts
CHANGED
|
@@ -41,6 +41,12 @@ interface FauxClientProps extends Omit<HTMLAttributes<HTMLDivElement>, "children
|
|
|
41
41
|
* `data-fullscreen="true"` while it is, for styling the content.
|
|
42
42
|
*/
|
|
43
43
|
onFullscreenChange?: (fullscreen: boolean) => void;
|
|
44
|
+
/**
|
|
45
|
+
* Take the parent's height, and give the content whatever the chrome leaves.
|
|
46
|
+
* For content that sizes itself to its box (a slide viewer, a spreadsheet)
|
|
47
|
+
* rather than to what is in it. Default `false`.
|
|
48
|
+
*/
|
|
49
|
+
fill?: boolean;
|
|
44
50
|
className?: string;
|
|
45
51
|
/** Class for the browser bar. */
|
|
46
52
|
barClassName?: string;
|
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 chunkS3WWYBL4_cjs = require('./chunk-S3WWYBL4.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 chunkS3WWYBL4_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-A5ENSMZP.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
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/FauxClient/FauxClient.tsx"],"names":[],"mappings":"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","file":"chunk-4TFTLPAC.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\n return (\n <div\n ref={setRefs}\n data-react-fancy-faux-client=\"\"\n data-fullscreen={showFullscreen ? String(fullscreen) : undefined}\n className={cn(\n \"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 </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 {(showZoom || showFullscreen) && (\n <div\n data-react-fancy-faux-client-controls=\"\"\n className=\"absolute bottom-2 right-2 z-10 flex items-center gap-0.5 rounded-md border border-zinc-200 bg-white/90 p-0.5 text-zinc-700 shadow-sm backdrop-blur dark:border-zinc-700 dark:bg-zinc-900/90 dark:text-zinc-200\"\n >\n {showZoom && (\n <>\n <button\n type=\"button\"\n data-react-fancy-faux-client-zoom-out=\"\"\n aria-label=\"Zoom out\"\n disabled={zoom <= minZoom}\n onClick={() => zoomBy(1 / ZOOM_STEP)}\n className=\"flex h-6 w-6 items-center justify-center rounded text-base leading-none hover:bg-zinc-100 disabled:opacity-40 dark:hover:bg-zinc-800\"\n >\n −\n </button>\n <button\n type=\"button\"\n data-react-fancy-faux-client-zoom-reset=\"\"\n aria-label=\"Reset zoom\"\n onClick={() => setZoom(1)}\n className=\"min-w-[3rem] rounded px-1 text-center text-xs tabular-nums hover:bg-zinc-100 dark:hover:bg-zinc-800\"\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 disabled={zoom >= maxZoom}\n onClick={() => zoomBy(ZOOM_STEP)}\n className=\"flex h-6 w-6 items-center justify-center rounded text-base leading-none hover:bg-zinc-100 disabled:opacity-40 dark:hover:bg-zinc-800\"\n >\n +\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=\"flex h-6 w-6 items-center justify-center rounded hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n >\n {fullscreen ? <ExitFullscreenIcon /> : <FullscreenIcon />}\n </button>\n )}\n </div>\n )}\n </div>\n );\n },\n);\n\nFauxClient.displayName = \"FauxClient\";\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"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/FauxClient/FauxClient.tsx"],"names":["forwardRef","useRef","useState","useControllableState","useLayoutEffect","useEffect","useCallback","jsxs","cn","jsx","Fragment"],"mappings":";;;;;;;AAKA,IAAM,UAAA,GAAa,CAAC,aAAA,EAAe,cAAA,EAAgB,cAAc,CAAA;AAGjE,IAAM,SAAA,GAAY,IAAA;AAElB,IAAM,KAAA,GAAQ,CAAC,CAAA,EAAW,GAAA,EAAa,GAAA,KAAgB,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,CAAC,CAAC,CAAA;AAa9E,IAAM,UAAA,GAAaA,gBAAA;AAAA,EACxB,CACE;AAAA,IACE,OAAA,GAAU,SAAA;AAAA,IACV,GAAA;AAAA,IACA,IAAA;AAAA,IACA,IAAA,GAAO,IAAA;AAAA,IACP,KAAA;AAAA,IACA,KAAA,GAAQ,KAAA;AAAA,IACR,QAAA,GAAW,KAAA;AAAA,IACX,IAAA,EAAM,cAAA;AAAA,IACN,WAAA,GAAc,CAAA;AAAA,IACd,YAAA;AAAA,IACA,OAAA,GAAU,GAAA;AAAA,IACV,OAAA,GAAU,CAAA;AAAA,IACV,kBAAA;AAAA,IACA,SAAA;AAAA,IACA,YAAA;AAAA,IACA,aAAA;AAAA,IACA,QAAA;AAAA,IACA,GAAG;AAAA,KAEL,GAAA,KACG;AACH,IAAA,MAAM,MAAA,GAAS,OAAO,KAAA,KAAU,QAAA;AAChC,IAAA,MAAM,OAAA,GAAUC,aAA8B,IAAI,CAAA;AAClD,IAAA,MAAM,WAAA,GAAcA,aAAuB,IAAI,CAAA;AAC/C,IAAA,MAAM,QAAA,GAAWA,aAAuB,IAAI,CAAA;AAC5C,IAAA,MAAM,CAAC,KAAK,MAAM,CAAA,GAAIC,eAAS,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,CAAC,CAAA;AACpE,IAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAIA,eAA6B,MAAS,CAAA;AAEhF,IAAA,MAAM,WAAW,QAAA,KAAa,IAAA,IAAS,OAAO,QAAA,KAAa,QAAA,IAAY,SAAS,IAAA,KAAS,KAAA;AACzF,IAAA,MAAM,iBAAiB,QAAA,KAAa,IAAA,IAAS,OAAO,QAAA,KAAa,QAAA,IAAY,SAAS,UAAA,KAAe,KAAA;AACrG,IAAA,MAAM,CAAC,IAAA,EAAM,OAAO,IAAIC,sCAAA,CAAqB,cAAA,EAAgB,aAAa,YAAY,CAAA;AACtF,IAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAID,eAAS,KAAK,CAAA;AAElD,IAAAE,qBAAA,CAAgB,MAAM;AACpB,MAAA,IAAI,CAAC,MAAA,EAAQ;AACb,MAAA,MAAM,WAAW,WAAA,CAAY,OAAA;AAC7B,MAAA,MAAM,QAAQ,QAAA,CAAS,OAAA;AACvB,MAAA,IAAI,CAAC,QAAA,IAAY,CAAC,KAAA,EAAO;AACzB,MAAA,IAAI,OAAO,mBAAmB,WAAA,EAAa;AAE3C,MAAA,MAAM,UAAU,MAAM;AACpB,QAAA,MAAM,QAAQ,QAAA,CAAS,WAAA;AACvB,QAAA,MAAA,CAAO,KAAA,KAAU,QAAS,KAAA,IAAS,KAAA,GAAQ,IAAI,KAAA,GAAQ,KAAA,GAAQ,IAAK,KAAK,CAAA;AAEzE,QAAA,gBAAA,CAAiB,MAAM,YAAY,CAAA;AAAA,MACrC,CAAA;AAEA,MAAA,OAAA,EAAQ;AACR,MAAA,MAAM,EAAA,GAAK,IAAI,cAAA,CAAe,OAAO,CAAA;AACrC,MAAA,EAAA,CAAG,QAAQ,QAAQ,CAAA;AACnB,MAAA,EAAA,CAAG,QAAQ,KAAK,CAAA;AAChB,MAAA,OAAO,MAAM,GAAG,UAAA,EAAW;AAAA,IAC7B,CAAA,EAAG,CAAC,MAAA,EAAQ,KAAA,EAAO,KAAK,CAAC,CAAA;AAIzB,IAAAC,eAAA,CAAU,MAAM;AACd,MAAA,IAAI,CAAC,cAAA,IAAkB,OAAO,QAAA,KAAa,WAAA,EAAa;AACxD,MAAA,MAAM,OAAO,MAAM;AACjB,QAAA,MAAM,KAAK,QAAA,CAAS,iBAAA,IAAqB,IAAA,IAAQ,QAAA,CAAS,sBAAsB,OAAA,CAAQ,OAAA;AACxF,QAAA,aAAA,CAAc,EAAE,CAAA;AAChB,QAAA,kBAAA,GAAqB,EAAE,CAAA;AAAA,MACzB,CAAA;AACA,MAAA,QAAA,CAAS,gBAAA,CAAiB,oBAAoB,IAAI,CAAA;AAClD,MAAA,OAAO,MAAM,QAAA,CAAS,mBAAA,CAAoB,kBAAA,EAAoB,IAAI,CAAA;AAAA,IACpE,CAAA,EAAG,CAAC,cAAA,EAAgB,kBAAkB,CAAC,CAAA;AAEvC,IAAA,MAAM,gBAAA,GAAmBC,kBAAY,MAAM;AACzC,MAAA,MAAM,KAAK,OAAA,CAAQ,OAAA;AACnB,MAAA,IAAI,CAAC,EAAA,IAAM,OAAO,QAAA,KAAa,WAAA,EAAa;AAC5C,MAAA,IAAI,QAAA,CAAS,iBAAA,KAAsB,EAAA,EAAI,KAAK,SAAS,cAAA,IAAiB;AAAA,WACjE,KAAK,GAAG,iBAAA,IAAoB;AAAA,IACnC,CAAA,EAAG,EAAE,CAAA;AAEL,IAAA,MAAM,MAAA,GAAS,CAAC,MAAA,KAAmB;AAEjC,MAAA,OAAA,CAAQ,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,IAAA,GAAO,MAAA,GAAS,GAAG,CAAA,GAAI,GAAA,EAAK,OAAA,EAAS,OAAO,CAAC,CAAA;AAAA,IACxE,CAAA;AAEA,IAAA,MAAM,OAAA,GAAU,CAAC,EAAA,KAA8B;AAC7C,MAAA,OAAA,CAAQ,OAAA,GAAU,EAAA;AAClB,MAAA,IAAI,OAAO,GAAA,KAAQ,UAAA,EAAY,GAAA,CAAI,EAAE,CAAA;AAAA,WAAA,IAC5B,GAAA,MAAS,OAAA,GAAU,EAAA;AAAA,IAC9B,CAAA;AAEA,IAAA,MAAM,YAAY,OAAA,KAAY,SAAA;AAC9B,IAAA,MAAM,WAAW,OAAA,KAAY,QAAA;AAC7B,IAAA,MAAM,YAAY,GAAA,GAAM,IAAA;AACxB,IAAA,MAAM,SAAS,IAAA,KAAS,CAAA;AAExB,IAAA,uBACEC,eAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,GAAA,EAAK,OAAA;AAAA,QACL,8BAAA,EAA6B,EAAA;AAAA,QAC7B,iBAAA,EAAiB,cAAA,GAAiB,MAAA,CAAO,UAAU,CAAA,GAAI,MAAA;AAAA,QACvD,SAAA,EAAWC,oBAAA;AAAA,UACT,oDAAA;AAAA,UACA,SAAA,IAAa,mEAAA;AAAA,UACb,QAAA,IAAY,8EAAA;AAAA,UACZ,YAAY,MAAA,IAAU,wDAAA;AAAA;AAAA,UAEtB,UAAA,IAAc,4BAAA;AAAA,UACd;AAAA,SACF;AAAA,QACC,GAAG,IAAA;AAAA,QAEH,QAAA,EAAA;AAAA,UAAA,SAAA,oBACCD,eAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,SAAA,EAAWC,oBAAA;AAAA,gBACT,0KAAA;AAAA,gBACA;AAAA,eACF;AAAA,cAEC,QAAA,EAAA;AAAA,gBAAA,IAAA,mCACE,KAAA,EAAA,EAAI,SAAA,EAAU,yBACZ,QAAA,EAAA,UAAA,CAAW,GAAA,CAAI,CAAC,KAAA,EAAO,CAAA,qBACtBC,cAAA,CAAC,MAAA,EAAA,EAAa,WAAWD,oBAAA,CAAG,8BAAA,EAAgC,KAAK,CAAA,EAAA,EAAtD,CAAyD,CACrE,CAAA,EACH,CAAA;AAAA,gBAED,OAAO,IAAA,oBAAQC,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,2BAA2B,QAAA,EAAA,GAAA,EAAI,CAAA;AAAA,gBAC9D,QAAQ,IAAA,oBAAQA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,6CAA6C,QAAA,EAAA,IAAA,EAAK;AAAA;AAAA;AAAA,WACrF;AAAA,0BAGFA,cAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,GAAA,EAAK,WAAA;AAAA,cACL,SAAA,EAAWD,oBAAA;AAAA,gBACT,UAAA;AAAA;AAAA,gBAEA,SAAS,eAAA,GAAkB,iBAAA;AAAA,gBAC3B,UAAA,IAAc,gBAAA;AAAA,gBACd;AAAA,eACF;AAAA,cAEA,KAAA,EAAO,MAAA,IAAU,CAAC,UAAA,IAAc,aAAA,KAAkB,SAAY,EAAE,MAAA,EAAQ,aAAA,GAAgB,GAAA,EAAI,GAAI,MAAA;AAAA,cAE/F,QAAA,EAAA,MAAA;AAAA;AAAA,gCAECC,cAAA;AAAA,kBAAC,KAAA;AAAA,kBAAA;AAAA,oBACC,KAAA,EACE,aAAA,KAAkB,MAAA,IAAa,KAAA,KAAU,SACrC,EAAE,KAAA,EAAO,KAAA,GAAQ,SAAA,EAAW,MAAA,EAAQ,aAAA,GAAgB,SAAA,EAAW,QAAA,EAAU,YAAW,GACpF,MAAA;AAAA,oBAGN,QAAA,kBAAAA,cAAA;AAAA,sBAAC,KAAA;AAAA,sBAAA;AAAA,wBACC,GAAA,EAAK,QAAA;AAAA,wBACL,sCAAA,EAAqC,EAAA;AAAA,wBACrC,SAAA,EAAU,uCAAA;AAAA,wBACV,OAAO,EAAE,KAAA,EAAO,SAAA,EAAW,CAAA,MAAA,EAAS,SAAS,CAAA,CAAA,CAAA,EAAI;AAAA,wBAEhD;AAAA;AAAA;AACH;AAAA;AACF,kCAEAA,cAAA;AAAA,gBAAC,KAAA;AAAA,gBAAA;AAAA,kBACC,sCAAA,EAAqC,EAAA;AAAA,kBACrC,SAAA,EAAWD,oBAAA,CAAG,UAAA,IAAc,QAAQ,CAAA;AAAA,kBAGpC,OAAO,MAAA,GAAS,EAAE,MAAM,MAAA,CAAO,IAAI,GAAE,GAAI,MAAA;AAAA,kBAExC;AAAA;AAAA;AACH;AAAA,WAEJ;AAAA,UAAA,CAEE,YAAY,cAAA,qBACZD,eAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,uCAAA,EAAsC,EAAA;AAAA,cACtC,SAAA,EAAU,gNAAA;AAAA,cAET,QAAA,EAAA;AAAA,gBAAA,QAAA,oBACCA,eAAA,CAAAG,mBAAA,EAAA,EACE,QAAA,EAAA;AAAA,kCAAAD,cAAA;AAAA,oBAAC,QAAA;AAAA,oBAAA;AAAA,sBACC,IAAA,EAAK,QAAA;AAAA,sBACL,uCAAA,EAAsC,EAAA;AAAA,sBACtC,YAAA,EAAW,UAAA;AAAA,sBACX,UAAU,IAAA,IAAQ,OAAA;AAAA,sBAClB,OAAA,EAAS,MAAM,MAAA,CAAO,CAAA,GAAI,SAAS,CAAA;AAAA,sBACnC,SAAA,EAAU,sIAAA;AAAA,sBACX,QAAA,EAAA;AAAA;AAAA,mBAED;AAAA,kCACAF,eAAA;AAAA,oBAAC,QAAA;AAAA,oBAAA;AAAA,sBACC,IAAA,EAAK,QAAA;AAAA,sBACL,yCAAA,EAAwC,EAAA;AAAA,sBACxC,YAAA,EAAW,YAAA;AAAA,sBACX,OAAA,EAAS,MAAM,OAAA,CAAQ,CAAC,CAAA;AAAA,sBACxB,SAAA,EAAU,qGAAA;AAAA,sBAET,QAAA,EAAA;AAAA,wBAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,wBAAE;AAAA;AAAA;AAAA,mBAC1B;AAAA,kCACAE,cAAA;AAAA,oBAAC,QAAA;AAAA,oBAAA;AAAA,sBACC,IAAA,EAAK,QAAA;AAAA,sBACL,sCAAA,EAAqC,EAAA;AAAA,sBACrC,YAAA,EAAW,SAAA;AAAA,sBACX,UAAU,IAAA,IAAQ,OAAA;AAAA,sBAClB,OAAA,EAAS,MAAM,MAAA,CAAO,SAAS,CAAA;AAAA,sBAC/B,SAAA,EAAU,sIAAA;AAAA,sBACX,QAAA,EAAA;AAAA;AAAA;AAED,iBAAA,EACF,CAAA;AAAA,gBAED,cAAA,oBACCA,cAAA;AAAA,kBAAC,QAAA;AAAA,kBAAA;AAAA,oBACC,IAAA,EAAK,QAAA;AAAA,oBACL,yCAAA,EAAwC,EAAA;AAAA,oBACxC,YAAA,EAAY,aAAa,kBAAA,GAAqB,aAAA;AAAA,oBAC9C,cAAA,EAAc,UAAA;AAAA,oBACd,KAAA,EAAO,aAAa,kBAAA,GAAqB,aAAA;AAAA,oBACzC,OAAA,EAAS,gBAAA;AAAA,oBACT,SAAA,EAAU,2FAAA;AAAA,oBAET,QAAA,EAAA,UAAA,mBAAaA,cAAA,CAAC,kBAAA,EAAA,EAAmB,CAAA,kCAAM,cAAA,EAAA,EAAe;AAAA;AAAA;AACzD;AAAA;AAAA;AAEJ;AAAA;AAAA,KAEJ;AAAA,EAEJ;AACF;AAEA,UAAA,CAAW,WAAA,GAAc,YAAA;AAGzB,SAAS,cAAA,GAAiB;AACxB,EAAA,uCACG,KAAA,EAAA,EAAI,KAAA,EAAM,MAAK,MAAA,EAAO,IAAA,EAAK,SAAQ,WAAA,EAAY,IAAA,EAAK,QAAO,MAAA,EAAO,cAAA,EAAe,aAAY,GAAA,EAAI,aAAA,EAAc,SAAQ,cAAA,EAAe,OAAA,EAAQ,eAAY,MAAA,EACzJ,QAAA,EAAA;AAAA,oBAAAA,cAAA,CAAC,MAAA,EAAA,EAAK,GAAE,wBAAA,EAAyB,CAAA;AAAA,oBACjCA,cAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,0BAAA,EAA2B,CAAA;AAAA,oBACnCA,cAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,yBAAA,EAA0B,CAAA;AAAA,oBAClCA,cAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,2BAAA,EAA4B;AAAA,GAAA,EACtC,CAAA;AAEJ;AAEA,SAAS,kBAAA,GAAqB;AAC5B,EAAA,uCACG,KAAA,EAAA,EAAI,KAAA,EAAM,MAAK,MAAA,EAAO,IAAA,EAAK,SAAQ,WAAA,EAAY,IAAA,EAAK,QAAO,MAAA,EAAO,cAAA,EAAe,aAAY,GAAA,EAAI,aAAA,EAAc,SAAQ,cAAA,EAAe,OAAA,EAAQ,eAAY,MAAA,EACzJ,QAAA,EAAA;AAAA,oBAAAA,cAAA,CAAC,MAAA,EAAA,EAAK,GAAE,wBAAA,EAAyB,CAAA;AAAA,oBACjCA,cAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,0BAAA,EAA2B,CAAA;AAAA,oBACnCA,cAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,yBAAA,EAA0B,CAAA;AAAA,oBAClCA,cAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,2BAAA,EAA4B;AAAA,GAAA,EACtC,CAAA;AAEJ","file":"chunk-HM7SW53Z.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\n return (\n <div\n ref={setRefs}\n data-react-fancy-faux-client=\"\"\n data-fullscreen={showFullscreen ? String(fullscreen) : undefined}\n className={cn(\n \"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 </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 {(showZoom || showFullscreen) && (\n <div\n data-react-fancy-faux-client-controls=\"\"\n className=\"absolute bottom-2 right-2 z-10 flex items-center gap-0.5 rounded-md border border-zinc-200 bg-white/90 p-0.5 text-zinc-700 shadow-sm backdrop-blur dark:border-zinc-700 dark:bg-zinc-900/90 dark:text-zinc-200\"\n >\n {showZoom && (\n <>\n <button\n type=\"button\"\n data-react-fancy-faux-client-zoom-out=\"\"\n aria-label=\"Zoom out\"\n disabled={zoom <= minZoom}\n onClick={() => zoomBy(1 / ZOOM_STEP)}\n className=\"flex h-6 w-6 items-center justify-center rounded text-base leading-none hover:bg-zinc-100 disabled:opacity-40 dark:hover:bg-zinc-800\"\n >\n −\n </button>\n <button\n type=\"button\"\n data-react-fancy-faux-client-zoom-reset=\"\"\n aria-label=\"Reset zoom\"\n onClick={() => setZoom(1)}\n className=\"min-w-[3rem] rounded px-1 text-center text-xs tabular-nums hover:bg-zinc-100 dark:hover:bg-zinc-800\"\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 disabled={zoom >= maxZoom}\n onClick={() => zoomBy(ZOOM_STEP)}\n className=\"flex h-6 w-6 items-center justify-center rounded text-base leading-none hover:bg-zinc-100 disabled:opacity-40 dark:hover:bg-zinc-800\"\n >\n +\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=\"flex h-6 w-6 items-center justify-center rounded hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n >\n {fullscreen ? <ExitFullscreenIcon /> : <FullscreenIcon />}\n </button>\n )}\n </div>\n )}\n </div>\n );\n },\n);\n\nFauxClient.displayName = \"FauxClient\";\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"]}
|