@particle-academy/react-fancy 5.45.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 CHANGED
@@ -11,6 +11,19 @@ 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
+
14
27
  ## [5.45.0] - 2026-10-19
15
28
 
16
29
  ### 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, jsx, Fragment } from 'react/jsx-runtime';
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;
@@ -82,6 +82,76 @@ var FauxClient = forwardRef(
82
82
  const isDevice = variant === "device";
83
83
  const effective = fit * zoom;
84
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;
85
155
  return /* @__PURE__ */ jsxs(
86
156
  "div",
87
157
  {
@@ -89,7 +159,7 @@ var FauxClient = forwardRef(
89
159
  "data-react-fancy-faux-client": "",
90
160
  "data-fullscreen": showFullscreen ? String(fullscreen) : void 0,
91
161
  className: cn(
92
- "relative overflow-hidden bg-white dark:bg-zinc-900",
162
+ "group/faux-client relative overflow-hidden bg-white dark:bg-zinc-900",
93
163
  isBrowser && "rounded-2xl border border-zinc-200 shadow-xl dark:border-zinc-800",
94
164
  isDevice && "rounded-[2rem] border-[10px] border-zinc-800 shadow-2xl dark:border-zinc-700",
95
165
  variant === "bare" && "rounded-lg border border-zinc-200 dark:border-zinc-800",
@@ -109,7 +179,8 @@ var FauxClient = forwardRef(
109
179
  children: [
110
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)) }),
111
181
  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 })
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 })
113
184
  ]
114
185
  }
115
186
  ),
@@ -154,74 +225,35 @@ var FauxClient = forwardRef(
154
225
  )
155
226
  }
156
227
  ),
157
- (showZoom || showFullscreen) && /* @__PURE__ */ jsxs(
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
- )
228
+ hasControls && !isBrowser && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute right-2 top-2 z-10", children: controlsBar })
219
229
  ]
220
230
  }
221
231
  );
222
232
  }
223
233
  );
224
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
+ }
225
257
  function FullscreenIcon() {
226
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: [
227
259
  /* @__PURE__ */ jsx("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
@@ -240,5 +272,5 @@ function ExitFullscreenIcon() {
240
272
  }
241
273
 
242
274
  export { FauxClient };
243
- //# sourceMappingURL=chunk-4TFTLPAC.js.map
244
- //# sourceMappingURL=chunk-4TFTLPAC.js.map
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"]}
@@ -84,6 +84,76 @@ var FauxClient = react.forwardRef(
84
84
  const isDevice = variant === "device";
85
85
  const effective = fit * zoom;
86
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;
87
157
  return /* @__PURE__ */ jsxRuntime.jsxs(
88
158
  "div",
89
159
  {
@@ -91,7 +161,7 @@ var FauxClient = react.forwardRef(
91
161
  "data-react-fancy-faux-client": "",
92
162
  "data-fullscreen": showFullscreen ? String(fullscreen) : void 0,
93
163
  className: chunkZU5Q77OT_cjs.cn(
94
- "relative overflow-hidden bg-white dark:bg-zinc-900",
164
+ "group/faux-client relative overflow-hidden bg-white dark:bg-zinc-900",
95
165
  isBrowser && "rounded-2xl border border-zinc-200 shadow-xl dark:border-zinc-800",
96
166
  isDevice && "rounded-[2rem] border-[10px] border-zinc-800 shadow-2xl dark:border-zinc-700",
97
167
  variant === "bare" && "rounded-lg border border-zinc-200 dark:border-zinc-800",
@@ -111,7 +181,8 @@ var FauxClient = react.forwardRef(
111
181
  children: [
112
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)) }),
113
183
  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 })
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 })
115
186
  ]
116
187
  }
117
188
  ),
@@ -156,74 +227,35 @@ var FauxClient = react.forwardRef(
156
227
  )
157
228
  }
158
229
  ),
159
- (showZoom || showFullscreen) && /* @__PURE__ */ jsxRuntime.jsxs(
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
- )
230
+ hasControls && !isBrowser && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute right-2 top-2 z-10", children: controlsBar })
221
231
  ]
222
232
  }
223
233
  );
224
234
  }
225
235
  );
226
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
+ }
227
259
  function FullscreenIcon() {
228
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: [
229
261
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
@@ -242,5 +274,5 @@ function ExitFullscreenIcon() {
242
274
  }
243
275
 
244
276
  exports.FauxClient = FauxClient;
245
- //# sourceMappingURL=chunk-HM7SW53Z.cjs.map
246
- //# sourceMappingURL=chunk-HM7SW53Z.cjs.map
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":";;;;;;;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;AACxB,IAAA,MAAM,cAAc,QAAA,IAAY,cAAA;AAYhC,IAAA,MAAM,cAAc,WAAA,mBAClBC,eAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,uCAAA,EAAsC,EAAA;AAAA,QACtC,SAAA,EAAWC,oBAAA;AAAA,UACT,iGAAA;AAAA,UACA,+FAAA;AAAA,UACA,YACI,wCAAA,GACA;AAAA,SACN;AAAA,QAEC,QAAA,EAAA;AAAA,UAAA,QAAA,oBACCD,eAAA,CAAAE,mBAAA,EAAA,EACE,QAAA,EAAA;AAAA,4BAAAC,cAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,uCAAA,EAAsC,EAAA;AAAA,gBACtC,YAAA,EAAW,UAAA;AAAA,gBACX,KAAA,EAAM,UAAA;AAAA,gBACN,UAAU,IAAA,IAAQ,OAAA;AAAA,gBAClB,OAAA,EAAS,MAAM,MAAA,CAAO,CAAA,GAAI,SAAS,CAAA;AAAA,gBACnC,SAAA,EAAW,cAAc,SAAS,CAAA;AAAA,gBAElC,yCAAC,WAAA,EAAA,EAAY;AAAA;AAAA,aACf;AAAA,4BACAH,eAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,yCAAA,EAAwC,EAAA;AAAA,gBACxC,YAAA,EAAW,YAAA;AAAA,gBACX,KAAA,EAAM,YAAA;AAAA,gBACN,OAAA,EAAS,MAAM,OAAA,CAAQ,CAAC,CAAA;AAAA,gBACxB,SAAA,EAAWC,oBAAA,CAAG,aAAA,CAAc,SAAS,GAAG,kEAAkE,CAAA;AAAA,gBAEzG,QAAA,EAAA;AAAA,kBAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AAAA,kBAAE;AAAA;AAAA;AAAA,aAC1B;AAAA,4BACAE,cAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,sCAAA,EAAqC,EAAA;AAAA,gBACrC,YAAA,EAAW,SAAA;AAAA,gBACX,KAAA,EAAM,SAAA;AAAA,gBACN,UAAU,IAAA,IAAQ,OAAA;AAAA,gBAClB,OAAA,EAAS,MAAM,MAAA,CAAO,SAAS,CAAA;AAAA,gBAC/B,SAAA,EAAW,cAAc,SAAS,CAAA;AAAA,gBAElC,yCAAC,UAAA,EAAA,EAAW;AAAA;AAAA;AACd,WAAA,EACF,CAAA;AAAA,UAED,cAAA,oBACCA,cAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,IAAA,EAAK,QAAA;AAAA,cACL,yCAAA,EAAwC,EAAA;AAAA,cACxC,YAAA,EAAY,aAAa,kBAAA,GAAqB,aAAA;AAAA,cAC9C,cAAA,EAAc,UAAA;AAAA,cACd,KAAA,EAAO,aAAa,kBAAA,GAAqB,aAAA;AAAA,cACzC,OAAA,EAAS,gBAAA;AAAA,cACT,SAAA,EAAW,cAAc,SAAS,CAAA;AAAA,cAEjC,QAAA,EAAA,UAAA,mBAAaA,cAAA,CAAC,kBAAA,EAAA,EAAmB,CAAA,kCAAM,cAAA,EAAA,EAAe;AAAA;AAAA;AACzD;AAAA;AAAA,KAEJ,GACE,IAAA;AAEJ,IAAA,uBACEH,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,sEAAA;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,qBACtBE,cAAA,CAAC,MAAA,EAAA,EAAa,WAAWF,oBAAA,CAAG,8BAAA,EAAgC,KAAK,CAAA,EAAA,EAAtD,CAAyD,CACrE,CAAA,EACH,CAAA;AAAA,gBAED,OAAO,IAAA,oBAAQE,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,CAAA;AAAA,gBAClF,WAAA,oBAAeA,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAWF,oBAAA,CAAG,GAAA,IAAO,IAAA,IAAQ,IAAA,IAAQ,IAAA,IAAQ,SAAS,CAAA,EAAI,QAAA,EAAA,WAAA,EAAY;AAAA;AAAA;AAAA,WAC7F;AAAA,0BAGFE,cAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,GAAA,EAAK,WAAA;AAAA,cACL,SAAA,EAAWF,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,gCAECE,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,EAAWF,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,UAEC,eAAe,CAAC,SAAA,mCACd,KAAA,EAAA,EAAI,SAAA,EAAU,mDAAmD,QAAA,EAAA,WAAA,EAAY;AAAA;AAAA;AAAA,KAElF;AAAA,EAEJ;AACF;AAEA,UAAA,CAAW,WAAA,GAAc,YAAA;AAGzB,SAAS,cAAc,KAAA,EAAwB;AAC7C,EAAA,OAAOA,oBAAA;AAAA,IACL,0HAAA;AAAA,IACA,gFAAA;AAAA,IACA,QACI,yFAAA,GACA;AAAA,GACN;AACF;AAEA,SAAS,WAAA,GAAc;AACrB,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,oBAAAE,cAAA,CAAC,YAAO,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK,GAAE,GAAA,EAAI,CAAA;AAAA,oBAC9BA,cAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,gBAAA,EAAiB,CAAA;AAAA,oBACzBA,cAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,SAAA,EAAU;AAAA,GAAA,EACpB,CAAA;AAEJ;AAEA,SAAS,UAAA,GAAa;AACpB,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,YAAO,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK,GAAE,GAAA,EAAI,CAAA;AAAA,oBAC9BA,cAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,gBAAA,EAAiB,CAAA;AAAA,oBACzBA,cAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,SAAA,EAAU,CAAA;AAAA,oBAClBA,cAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,SAAA,EAAU;AAAA,GAAA,EACpB,CAAA;AAEJ;AAGA,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-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"]}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkHM7SW53Z_cjs = require('./chunk-HM7SW53Z.cjs');
3
+ var chunkXL62UDVX_cjs = require('./chunk-XL62UDVX.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 chunkHM7SW53Z_cjs.FauxClient; }
11
+ get: function () { return chunkXL62UDVX_cjs.FauxClient; }
12
12
  });
13
13
  //# sourceMappingURL=faux-client.cjs.map
14
14
  //# sourceMappingURL=faux-client.cjs.map
@@ -1,4 +1,4 @@
1
- export { FauxClient } from './chunk-4TFTLPAC.js';
1
+ export { FauxClient } from './chunk-VDHHWSMT.js';
2
2
  import './chunk-EDYAXGUM.js';
3
3
  import './chunk-MMW2JGKZ.js';
4
4
  //# sourceMappingURL=faux-client.js.map
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 chunkHM7SW53Z_cjs = require('./chunk-HM7SW53Z.cjs');
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 chunkHM7SW53Z_cjs.FauxClient; }
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-4TFTLPAC.js';
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/react-fancy",
3
- "version": "5.45.0",
3
+ "version": "5.45.1",
4
4
  "description": "React UI component library for Human+ UX — controlled, agent-bridgeable primitives",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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":"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","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"]}