@toriistudio/v0-playground 0.6.0 → 0.7.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/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- // src/components/Playground/Playground.tsx
2
- import { useEffect as useEffect5, useMemo as useMemo4, useState as useState5 } from "react";
1
+ // src/components/Playground.tsx
2
+ import { useEffect as useEffect7, useMemo as useMemo5, useState as useState5 } from "react";
3
3
  import { Check as Check3, Copy as Copy2 } from "lucide-react";
4
4
 
5
5
  // src/context/ResizableLayout.tsx
@@ -411,7 +411,8 @@ var ControlsProvider = ({ children }) => {
411
411
  const [schema, setSchema] = useState2({});
412
412
  const [values, setValues] = useState2({});
413
413
  const [config, setConfig] = useState2({
414
- showCopyButton: true
414
+ showCopyButton: true,
415
+ showCodeSnippet: false
415
416
  });
416
417
  const [componentName, setComponentName] = useState2();
417
418
  const [channelName, setChannelName] = useState2(null);
@@ -434,17 +435,30 @@ var ControlsProvider = ({ children }) => {
434
435
  setComponentName(opts.componentName);
435
436
  }
436
437
  if (opts?.config) {
437
- const { addAdvancedPaletteControl, ...otherConfig } = opts.config;
438
- setConfig((prev) => ({
439
- ...prev,
440
- ...otherConfig,
441
- ...Object.prototype.hasOwnProperty.call(
438
+ const {
439
+ addAdvancedPaletteControl,
440
+ addMediaUploadControl,
441
+ ...otherConfig
442
+ } = opts.config;
443
+ setConfig((prev) => {
444
+ const nextConfig = {
445
+ ...prev,
446
+ ...otherConfig
447
+ };
448
+ if (Object.prototype.hasOwnProperty.call(
442
449
  opts.config,
443
450
  "addAdvancedPaletteControl"
444
- ) ? {
445
- addAdvancedPaletteControl: addAdvancedPaletteControl ? resolveAdvancedPaletteConfig(addAdvancedPaletteControl) : void 0
446
- } : {}
447
- }));
451
+ )) {
452
+ nextConfig.addAdvancedPaletteControl = addAdvancedPaletteControl ? resolveAdvancedPaletteConfig(addAdvancedPaletteControl) : void 0;
453
+ }
454
+ if (Object.prototype.hasOwnProperty.call(
455
+ opts.config,
456
+ "addMediaUploadControl"
457
+ )) {
458
+ nextConfig.addMediaUploadControl = addMediaUploadControl ? { ...addMediaUploadControl } : void 0;
459
+ }
460
+ return nextConfig;
461
+ });
448
462
  }
449
463
  setSchema((prevSchema) => ({ ...prevSchema, ...newSchema }));
450
464
  setValues((prevValues) => {
@@ -609,7 +623,7 @@ var useControls = (schema, options) => {
609
623
  resolvedAdvancedConfig.onPaletteChange(clonePalette(palette));
610
624
  }, [ctx.values, resolvedAdvancedConfig]);
611
625
  const typedValues = ctx.values;
612
- const jsx14 = useCallback(() => {
626
+ const jsx15 = useCallback(() => {
613
627
  if (!options?.componentName) return "";
614
628
  const props = Object.entries(typedValues).map(([key, val]) => {
615
629
  if (typeof val === "string") return `${key}="${val}"`;
@@ -623,13 +637,19 @@ var useControls = (schema, options) => {
623
637
  controls: ctx.values,
624
638
  schema: ctx.schema,
625
639
  setValue: ctx.setValue,
626
- jsx: jsx14
640
+ jsx: jsx15
627
641
  };
628
642
  };
629
643
  var useUrlSyncedControls = useControls;
630
644
 
631
- // src/components/ControlPanel/ControlPanel.tsx
632
- import { useState as useState4, useMemo as useMemo3, useCallback as useCallback3 } from "react";
645
+ // src/components/ControlPanel.tsx
646
+ import {
647
+ useState as useState4,
648
+ useMemo as useMemo4,
649
+ useCallback as useCallback4,
650
+ useEffect as useEffect6,
651
+ useRef as useRef5
652
+ } from "react";
633
653
  import {
634
654
  Check as Check2,
635
655
  Copy,
@@ -911,7 +931,7 @@ Button.displayName = "Button";
911
931
  // src/constants/layout.ts
912
932
  var MOBILE_CONTROL_PANEL_PEEK = 112;
913
933
 
914
- // src/components/AdvancedPaletteControl/AdvancedPaletteControl.tsx
934
+ // src/components/AdvancedPaletteControl.tsx
915
935
  import {
916
936
  useCallback as useCallback2,
917
937
  useEffect as useEffect4,
@@ -1064,15 +1084,539 @@ var AdvancedPaletteControl = ({
1064
1084
  };
1065
1085
  var AdvancedPaletteControl_default = AdvancedPaletteControl;
1066
1086
 
1067
- // src/components/ControlPanel/ControlPanel.tsx
1068
- import { Fragment, jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
1087
+ // src/components/MediaUploadControl.tsx
1088
+ import {
1089
+ useCallback as useCallback3,
1090
+ useEffect as useEffect5,
1091
+ useId,
1092
+ useMemo as useMemo3,
1093
+ useRef as useRef4,
1094
+ useSyncExternalStore
1095
+ } from "react";
1096
+ import { X } from "lucide-react";
1097
+
1098
+ // src/state/mediaSelectionStore.ts
1099
+ var snapshot = {
1100
+ media: null,
1101
+ error: null
1102
+ };
1103
+ var listeners = /* @__PURE__ */ new Set();
1104
+ var emitChange = () => {
1105
+ for (const listener of listeners) {
1106
+ listener();
1107
+ }
1108
+ };
1109
+ var mediaSelectionStore = {
1110
+ subscribe: (listener) => {
1111
+ listeners.add(listener);
1112
+ return () => {
1113
+ listeners.delete(listener);
1114
+ };
1115
+ },
1116
+ getSnapshot: () => snapshot,
1117
+ setSnapshot: (next) => {
1118
+ snapshot = next;
1119
+ emitChange();
1120
+ }
1121
+ };
1122
+
1123
+ // src/components/MediaUploadControl.tsx
1124
+ import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
1125
+ var DEFAULT_PRESET_MEDIA = [
1126
+ {
1127
+ src: "https://res.cloudinary.com/dz8kk1l4r/image/upload/v1763233793/astronaut_q84mbj.png",
1128
+ label: "Astronaut",
1129
+ type: "image"
1130
+ },
1131
+ {
1132
+ src: "https://res.cloudinary.com/dz8kk1l4r/image/upload/v1763233793/surreal-head_r0ozcd.png",
1133
+ label: "Futuristic",
1134
+ type: "image"
1135
+ },
1136
+ {
1137
+ src: "https://res.cloudinary.com/dz8kk1l4r/image/upload/v1763233797/futuristic_bpwdzt.png",
1138
+ label: "Surreal",
1139
+ type: "image"
1140
+ },
1141
+ {
1142
+ src: "https://res.cloudinary.com/dz8kk1l4r/image/upload/v1763233793/portrait_hd7dyc.png",
1143
+ label: "Portrait",
1144
+ type: "image"
1145
+ }
1146
+ ];
1147
+ function MediaUploadControl({
1148
+ onSelectMedia,
1149
+ onClear,
1150
+ presetMedia,
1151
+ maxPresetCount
1152
+ }) {
1153
+ const inputId = useId();
1154
+ const inputRef = useRef4(null);
1155
+ const uploadedUrlRef = useRef4(null);
1156
+ const { media, error } = useSyncExternalStore(
1157
+ mediaSelectionStore.subscribe,
1158
+ mediaSelectionStore.getSnapshot,
1159
+ mediaSelectionStore.getSnapshot
1160
+ );
1161
+ const VIDEO_EXTENSIONS = useMemo3(
1162
+ () => [".mp4", ".webm", ".ogg", ".ogv", ".mov", ".m4v"],
1163
+ []
1164
+ );
1165
+ const setSelection = useCallback3(
1166
+ (next) => {
1167
+ mediaSelectionStore.setSnapshot(next);
1168
+ },
1169
+ []
1170
+ );
1171
+ const handleFileChange = (event) => {
1172
+ const file = event.target.files?.[0];
1173
+ if (!file) {
1174
+ return;
1175
+ }
1176
+ if (uploadedUrlRef.current) {
1177
+ URL.revokeObjectURL(uploadedUrlRef.current);
1178
+ uploadedUrlRef.current = null;
1179
+ }
1180
+ const objectUrl = URL.createObjectURL(file);
1181
+ uploadedUrlRef.current = objectUrl;
1182
+ const lowerName = file.name?.toLowerCase() ?? "";
1183
+ const hasVideoExtension = VIDEO_EXTENSIONS.some(
1184
+ (ext) => lowerName.endsWith(ext)
1185
+ );
1186
+ const isVideo = file.type.startsWith("video/") || hasVideoExtension;
1187
+ if (isVideo) {
1188
+ setSelection({
1189
+ media: null,
1190
+ error: "Videos are not supported in this effect yet."
1191
+ });
1192
+ return;
1193
+ }
1194
+ const nextMedia = { src: objectUrl, type: "image" };
1195
+ setSelection({ media: nextMedia, error: null });
1196
+ onSelectMedia(nextMedia);
1197
+ };
1198
+ const handleClearSelection = () => {
1199
+ if (uploadedUrlRef.current) {
1200
+ URL.revokeObjectURL(uploadedUrlRef.current);
1201
+ uploadedUrlRef.current = null;
1202
+ }
1203
+ setSelection({ media: null, error: null });
1204
+ onClear();
1205
+ };
1206
+ const handlePresetSelect = (entry) => {
1207
+ if (entry.type === "video") {
1208
+ setSelection({
1209
+ media: null,
1210
+ error: "Videos are not supported in this effect yet."
1211
+ });
1212
+ return;
1213
+ }
1214
+ const nextMedia = { src: entry.src, type: entry.type };
1215
+ setSelection({ media: nextMedia, error: null });
1216
+ onSelectMedia(nextMedia);
1217
+ };
1218
+ useEffect5(() => {
1219
+ return () => {
1220
+ if (uploadedUrlRef.current) {
1221
+ URL.revokeObjectURL(uploadedUrlRef.current);
1222
+ uploadedUrlRef.current = null;
1223
+ }
1224
+ };
1225
+ }, []);
1226
+ const presets = useMemo3(() => {
1227
+ const source = presetMedia ?? DEFAULT_PRESET_MEDIA;
1228
+ if (typeof maxPresetCount === "number" && Number.isFinite(maxPresetCount)) {
1229
+ const safeCount = Math.max(0, Math.floor(maxPresetCount));
1230
+ return source.slice(0, safeCount);
1231
+ }
1232
+ return source;
1233
+ }, [presetMedia, maxPresetCount]);
1234
+ return /* @__PURE__ */ jsxs5(
1235
+ "div",
1236
+ {
1237
+ style: {
1238
+ display: "flex",
1239
+ flexDirection: "column",
1240
+ gap: "0.5rem"
1241
+ },
1242
+ children: [
1243
+ /* @__PURE__ */ jsx10("label", { htmlFor: inputId, style: { fontSize: "0.85rem", fontWeight: 500 }, children: "Upload media" }),
1244
+ /* @__PURE__ */ jsx10(
1245
+ "input",
1246
+ {
1247
+ id: inputId,
1248
+ type: "file",
1249
+ accept: "image/*",
1250
+ ref: inputRef,
1251
+ style: { display: "none" },
1252
+ onChange: handleFileChange
1253
+ }
1254
+ ),
1255
+ /* @__PURE__ */ jsxs5(
1256
+ "div",
1257
+ {
1258
+ style: {
1259
+ display: "flex",
1260
+ alignItems: "center",
1261
+ gap: "0.75rem"
1262
+ },
1263
+ children: [
1264
+ /* @__PURE__ */ jsx10(
1265
+ "button",
1266
+ {
1267
+ type: "button",
1268
+ onClick: () => inputRef.current?.click(),
1269
+ style: {
1270
+ padding: "0.35rem 0.75rem",
1271
+ borderRadius: "0.4rem",
1272
+ border: "1px solid rgba(255, 255, 255, 0.25)",
1273
+ background: "rgba(255, 255, 255, 0.08)",
1274
+ color: "inherit",
1275
+ cursor: "pointer"
1276
+ },
1277
+ children: "Choose file"
1278
+ }
1279
+ ),
1280
+ media ? /* @__PURE__ */ jsx10(
1281
+ "div",
1282
+ {
1283
+ style: {
1284
+ width: 36,
1285
+ height: 36,
1286
+ borderRadius: "0.35rem",
1287
+ overflow: "hidden",
1288
+ border: "1px solid rgba(255, 255, 255, 0.15)"
1289
+ },
1290
+ children: /* @__PURE__ */ jsx10(
1291
+ "img",
1292
+ {
1293
+ src: media.src,
1294
+ alt: "Thumbnail",
1295
+ style: {
1296
+ width: "100%",
1297
+ height: "100%",
1298
+ objectFit: "cover",
1299
+ display: "block"
1300
+ }
1301
+ }
1302
+ )
1303
+ }
1304
+ ) : null,
1305
+ media ? /* @__PURE__ */ jsx10(
1306
+ "button",
1307
+ {
1308
+ type: "button",
1309
+ onClick: handleClearSelection,
1310
+ style: {
1311
+ display: "flex",
1312
+ alignItems: "center",
1313
+ justifyContent: "center",
1314
+ padding: "0.3rem",
1315
+ borderRadius: "0.4rem",
1316
+ border: "1px solid rgba(255,255,255,0.2)",
1317
+ background: "transparent",
1318
+ color: "inherit",
1319
+ cursor: "pointer"
1320
+ },
1321
+ "aria-label": "Clear selection",
1322
+ title: "Clear selection",
1323
+ children: /* @__PURE__ */ jsx10(X, { size: 16, strokeWidth: 2 })
1324
+ }
1325
+ ) : null
1326
+ ]
1327
+ }
1328
+ ),
1329
+ presets.length > 0 ? /* @__PURE__ */ jsx10(
1330
+ "div",
1331
+ {
1332
+ style: {
1333
+ display: "grid",
1334
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
1335
+ gap: "0.5rem"
1336
+ },
1337
+ children: presets.map((entry) => {
1338
+ const isSelected = media?.src === entry.src && media?.type === entry.type;
1339
+ return /* @__PURE__ */ jsxs5(
1340
+ "button",
1341
+ {
1342
+ type: "button",
1343
+ onClick: () => handlePresetSelect(entry),
1344
+ style: {
1345
+ width: "100%",
1346
+ borderRadius: "0.4rem",
1347
+ border: "1px solid rgba(255,255,255,0.25)",
1348
+ outline: isSelected ? "2px solid #fff" : "none",
1349
+ outlineOffset: 2,
1350
+ padding: 0,
1351
+ overflow: "hidden",
1352
+ background: "transparent",
1353
+ cursor: "pointer"
1354
+ },
1355
+ children: [
1356
+ /* @__PURE__ */ jsx10(
1357
+ "img",
1358
+ {
1359
+ src: entry.src,
1360
+ alt: entry.label,
1361
+ style: {
1362
+ width: "100%",
1363
+ height: 100,
1364
+ objectFit: "cover",
1365
+ display: "block"
1366
+ }
1367
+ }
1368
+ ),
1369
+ /* @__PURE__ */ jsx10(
1370
+ "span",
1371
+ {
1372
+ style: {
1373
+ display: "block",
1374
+ padding: "0.35rem",
1375
+ fontSize: "0.75rem",
1376
+ textAlign: "left",
1377
+ background: "rgba(0,0,0,0.45)"
1378
+ },
1379
+ children: entry.label
1380
+ }
1381
+ )
1382
+ ]
1383
+ },
1384
+ `${entry.src}-${entry.type}`
1385
+ );
1386
+ })
1387
+ }
1388
+ ) : null,
1389
+ error ? /* @__PURE__ */ jsx10("p", { style: { color: "#ff9da4", fontSize: "0.8rem" }, children: error }) : null
1390
+ ]
1391
+ }
1392
+ );
1393
+ }
1394
+
1395
+ // src/components/ControlPanel.tsx
1396
+ import { Fragment, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
1397
+ var splitPropsString = (input) => {
1398
+ const props = [];
1399
+ let current = "";
1400
+ let curlyDepth = 0;
1401
+ let squareDepth = 0;
1402
+ let parenDepth = 0;
1403
+ let inSingleQuote = false;
1404
+ let inDoubleQuote = false;
1405
+ let inBacktick = false;
1406
+ let escapeNext = false;
1407
+ for (const char of input) {
1408
+ if (escapeNext) {
1409
+ current += char;
1410
+ escapeNext = false;
1411
+ continue;
1412
+ }
1413
+ if (char === "\\") {
1414
+ current += char;
1415
+ escapeNext = true;
1416
+ continue;
1417
+ }
1418
+ if (char === "'" && !inDoubleQuote && !inBacktick) {
1419
+ inSingleQuote = !inSingleQuote;
1420
+ current += char;
1421
+ continue;
1422
+ }
1423
+ if (char === '"' && !inSingleQuote && !inBacktick) {
1424
+ inDoubleQuote = !inDoubleQuote;
1425
+ current += char;
1426
+ continue;
1427
+ }
1428
+ if (char === "`" && !inSingleQuote && !inDoubleQuote) {
1429
+ inBacktick = !inBacktick;
1430
+ current += char;
1431
+ continue;
1432
+ }
1433
+ if (!inSingleQuote && !inDoubleQuote && !inBacktick) {
1434
+ if (char === "{") {
1435
+ curlyDepth += 1;
1436
+ } else if (char === "}") {
1437
+ curlyDepth = Math.max(0, curlyDepth - 1);
1438
+ } else if (char === "[") {
1439
+ squareDepth += 1;
1440
+ } else if (char === "]") {
1441
+ squareDepth = Math.max(0, squareDepth - 1);
1442
+ } else if (char === "(") {
1443
+ parenDepth += 1;
1444
+ } else if (char === ")") {
1445
+ parenDepth = Math.max(0, parenDepth - 1);
1446
+ }
1447
+ }
1448
+ const atTopLevel = !inSingleQuote && !inDoubleQuote && !inBacktick && curlyDepth === 0 && squareDepth === 0 && parenDepth === 0;
1449
+ if (atTopLevel && /\s/.test(char)) {
1450
+ if (current.trim()) {
1451
+ props.push(current.trim());
1452
+ }
1453
+ current = "";
1454
+ continue;
1455
+ }
1456
+ current += char;
1457
+ }
1458
+ if (current.trim()) {
1459
+ props.push(current.trim());
1460
+ }
1461
+ return props;
1462
+ };
1463
+ var formatJsxCodeSnippet = (input) => {
1464
+ const trimmed = input.trim();
1465
+ if (!trimmed) return "";
1466
+ if (trimmed.includes("\n")) {
1467
+ return trimmed;
1468
+ }
1469
+ if (!trimmed.startsWith("<") || !trimmed.endsWith(">")) {
1470
+ return trimmed;
1471
+ }
1472
+ if (!trimmed.endsWith("/>")) {
1473
+ return trimmed;
1474
+ }
1475
+ const inner = trimmed.slice(1, -2).trim();
1476
+ const firstSpaceIndex = inner.indexOf(" ");
1477
+ if (firstSpaceIndex === -1) {
1478
+ return `<${inner} />`;
1479
+ }
1480
+ const componentName = inner.slice(0, firstSpaceIndex);
1481
+ const propsString = inner.slice(firstSpaceIndex + 1).trim();
1482
+ if (!propsString) {
1483
+ return `<${componentName} />`;
1484
+ }
1485
+ const propsList = splitPropsString(propsString);
1486
+ if (propsList.length === 0) {
1487
+ return `<${componentName} ${propsString} />`;
1488
+ }
1489
+ const formattedProps = propsList.map((prop) => ` ${prop}`).join("\n");
1490
+ return `<${componentName}
1491
+ ${formattedProps}
1492
+ />`;
1493
+ };
1494
+ var isWhitespace = (char) => /\s/.test(char);
1495
+ var isAttrNameChar = (char) => /[A-Za-z0-9_$\-.:]/.test(char);
1496
+ var isAlphaStart = (char) => /[A-Za-z_$]/.test(char);
1497
+ var tokenizeJsx = (input) => {
1498
+ const tokens = [];
1499
+ let i = 0;
1500
+ while (i < input.length) {
1501
+ const char = input[i];
1502
+ if (char === "<") {
1503
+ tokens.push({ type: "punctuation", value: "<" });
1504
+ i += 1;
1505
+ if (input[i] === "/") {
1506
+ tokens.push({ type: "punctuation", value: "/" });
1507
+ i += 1;
1508
+ }
1509
+ const start = i;
1510
+ while (i < input.length && isAttrNameChar(input[i])) {
1511
+ i += 1;
1512
+ }
1513
+ if (i > start) {
1514
+ tokens.push({ type: "tag", value: input.slice(start, i) });
1515
+ }
1516
+ continue;
1517
+ }
1518
+ if (char === "/" && input[i + 1] === ">") {
1519
+ tokens.push({ type: "punctuation", value: "/>" });
1520
+ i += 2;
1521
+ continue;
1522
+ }
1523
+ if (char === ">") {
1524
+ tokens.push({ type: "punctuation", value: ">" });
1525
+ i += 1;
1526
+ continue;
1527
+ }
1528
+ if (char === "=") {
1529
+ tokens.push({ type: "punctuation", value: "=" });
1530
+ i += 1;
1531
+ continue;
1532
+ }
1533
+ if (char === '"' || char === "'" || char === "`") {
1534
+ const quote = char;
1535
+ let j = i + 1;
1536
+ let value = quote;
1537
+ while (j < input.length) {
1538
+ const current = input[j];
1539
+ value += current;
1540
+ if (current === quote && input[j - 1] !== "\\") {
1541
+ break;
1542
+ }
1543
+ j += 1;
1544
+ }
1545
+ tokens.push({ type: "string", value });
1546
+ i = j + 1;
1547
+ continue;
1548
+ }
1549
+ if (char === "{") {
1550
+ let depth = 1;
1551
+ let j = i + 1;
1552
+ while (j < input.length && depth > 0) {
1553
+ if (input[j] === "{") {
1554
+ depth += 1;
1555
+ } else if (input[j] === "}") {
1556
+ depth -= 1;
1557
+ }
1558
+ j += 1;
1559
+ }
1560
+ const expression = input.slice(i, j);
1561
+ tokens.push({ type: "expression", value: expression });
1562
+ i = j;
1563
+ continue;
1564
+ }
1565
+ if (isAlphaStart(char)) {
1566
+ const start = i;
1567
+ i += 1;
1568
+ while (i < input.length && isAttrNameChar(input[i])) {
1569
+ i += 1;
1570
+ }
1571
+ const word = input.slice(start, i);
1572
+ let k = i;
1573
+ while (k < input.length && isWhitespace(input[k])) {
1574
+ k += 1;
1575
+ }
1576
+ if (input[k] === "=") {
1577
+ tokens.push({ type: "attrName", value: word });
1578
+ } else {
1579
+ tokens.push({ type: "plain", value: word });
1580
+ }
1581
+ continue;
1582
+ }
1583
+ tokens.push({ type: "plain", value: char });
1584
+ i += 1;
1585
+ }
1586
+ return tokens;
1587
+ };
1588
+ var TOKEN_CLASS_MAP = {
1589
+ tag: "text-sky-300",
1590
+ attrName: "text-amber-200",
1591
+ string: "text-emerald-300",
1592
+ expression: "text-purple-300",
1593
+ punctuation: "text-stone-400"
1594
+ };
1595
+ var highlightJsx = (input) => {
1596
+ const tokens = tokenizeJsx(input);
1597
+ const nodes = [];
1598
+ tokens.forEach((token, index) => {
1599
+ if (token.type === "plain") {
1600
+ nodes.push(token.value);
1601
+ } else {
1602
+ nodes.push(
1603
+ /* @__PURE__ */ jsx11("span", { className: TOKEN_CLASS_MAP[token.type], children: token.value }, `token-${index}`)
1604
+ );
1605
+ }
1606
+ });
1607
+ return nodes;
1608
+ };
1069
1609
  var ControlPanel = () => {
1070
1610
  const [copied, setCopied] = useState4(false);
1611
+ const [codeCopied, setCodeCopied] = useState4(false);
1612
+ const [isCodeVisible, setIsCodeVisible] = useState4(false);
1071
1613
  const [folderStates, setFolderStates] = useState4({});
1614
+ const codeCopyTimeoutRef = useRef5(null);
1072
1615
  const { leftPanelWidth, isDesktop, isHydrated } = useResizableLayout();
1073
1616
  const { schema, setValue, values, componentName, config } = useControlsContext();
1617
+ const isControlsOnlyView = typeof window !== "undefined" && new URLSearchParams(window.location.search).get(CONTROLS_ONLY_PARAM) === "true";
1074
1618
  const previewUrl = usePreviewUrl(values);
1075
- const buildUrl = useCallback3(
1619
+ const buildUrl = useCallback4(
1076
1620
  (modifier) => {
1077
1621
  if (!previewUrl) return "";
1078
1622
  const [path, search = ""] = previewUrl.split("?");
@@ -1083,13 +1627,13 @@ var ControlPanel = () => {
1083
1627
  },
1084
1628
  [previewUrl]
1085
1629
  );
1086
- const presentationUrl = useMemo3(() => {
1630
+ const presentationUrl = useMemo4(() => {
1087
1631
  if (!previewUrl) return "";
1088
1632
  return buildUrl((params) => {
1089
1633
  params.set(PRESENTATION_PARAM, "true");
1090
1634
  });
1091
1635
  }, [buildUrl, previewUrl]);
1092
- const controlsOnlyUrl = useMemo3(() => {
1636
+ const controlsOnlyUrl = useMemo4(() => {
1093
1637
  if (!previewUrl) return "";
1094
1638
  return buildUrl((params) => {
1095
1639
  params.delete(NO_CONTROLS_PARAM);
@@ -1097,7 +1641,7 @@ var ControlPanel = () => {
1097
1641
  params.set(CONTROLS_ONLY_PARAM, "true");
1098
1642
  });
1099
1643
  }, [buildUrl, previewUrl]);
1100
- const handlePresentationClick = useCallback3(() => {
1644
+ const handlePresentationClick = useCallback4(() => {
1101
1645
  if (typeof window === "undefined" || !presentationUrl) return;
1102
1646
  window.open(presentationUrl, "_blank", "noopener,noreferrer");
1103
1647
  if (controlsOnlyUrl) {
@@ -1105,10 +1649,7 @@ var ControlPanel = () => {
1105
1649
  const viewportHeight = window.innerHeight || 900;
1106
1650
  const controlsWidth = Math.max(
1107
1651
  320,
1108
- Math.min(
1109
- 600,
1110
- Math.round(viewportWidth * leftPanelWidth / 100)
1111
- )
1652
+ Math.min(600, Math.round(viewportWidth * leftPanelWidth / 100))
1112
1653
  );
1113
1654
  const controlsHeight = Math.max(600, viewportHeight);
1114
1655
  const controlsFeatures = [
@@ -1124,7 +1665,7 @@ var ControlPanel = () => {
1124
1665
  window.open(controlsOnlyUrl, "v0-controls", controlsFeatures);
1125
1666
  }
1126
1667
  }, [controlsOnlyUrl, leftPanelWidth, presentationUrl]);
1127
- const jsx14 = useMemo3(() => {
1668
+ const jsx15 = useMemo4(() => {
1128
1669
  if (!componentName) return "";
1129
1670
  const props = Object.entries(values).map(([key, val]) => {
1130
1671
  if (typeof val === "string") return `${key}="${val}"`;
@@ -1169,7 +1710,7 @@ var ControlPanel = () => {
1169
1710
  const advancedConfig = config?.addAdvancedPaletteControl;
1170
1711
  let advancedPaletteControlNode = null;
1171
1712
  if (advancedConfig) {
1172
- const advancedNode = /* @__PURE__ */ jsx10(
1713
+ const advancedNode = /* @__PURE__ */ jsx11(
1173
1714
  AdvancedPaletteControl_default,
1174
1715
  {
1175
1716
  config: advancedConfig
@@ -1195,12 +1736,52 @@ var ControlPanel = () => {
1195
1736
  advancedPaletteControlNode = advancedNode;
1196
1737
  }
1197
1738
  }
1198
- const rootButtonControls = rootControls.filter(
1199
- ([, control]) => control.type === "button"
1200
- );
1201
- const rootNormalControls = rootControls.filter(
1202
- ([, control]) => control.type !== "button"
1203
- );
1739
+ const mediaUploadConfig = config?.addMediaUploadControl;
1740
+ let mediaUploadControlNode = null;
1741
+ if (mediaUploadConfig) {
1742
+ const mediaUploadNode = /* @__PURE__ */ jsx11(
1743
+ MediaUploadControl,
1744
+ {
1745
+ onSelectMedia: (media) => {
1746
+ mediaUploadConfig.onSelectMedia?.(media);
1747
+ },
1748
+ onClear: () => {
1749
+ mediaUploadConfig.onClear?.();
1750
+ },
1751
+ presetMedia: mediaUploadConfig.presetMedia,
1752
+ maxPresetCount: mediaUploadConfig.maxPresetCount
1753
+ },
1754
+ "mediaUploadControl"
1755
+ );
1756
+ const mediaFolder = mediaUploadConfig.folder?.trim();
1757
+ if (mediaFolder) {
1758
+ const placement = mediaUploadConfig.folderPlacement ?? "bottom";
1759
+ ensureFolder(mediaFolder);
1760
+ if (!folderControls.has(mediaFolder)) {
1761
+ folderControls.set(mediaFolder, []);
1762
+ }
1763
+ const existingPlacement = folderPlacement.get(mediaFolder);
1764
+ if (!existingPlacement || placement === "top") {
1765
+ folderPlacement.set(mediaFolder, placement);
1766
+ }
1767
+ if (!folderExtras.has(mediaFolder)) {
1768
+ folderExtras.set(mediaFolder, []);
1769
+ }
1770
+ folderExtras.get(mediaFolder).push(mediaUploadNode);
1771
+ } else {
1772
+ mediaUploadControlNode = mediaUploadNode;
1773
+ }
1774
+ }
1775
+ const rootButtonControls = [];
1776
+ const rootNormalControls = [];
1777
+ rootControls.forEach((entry) => {
1778
+ const [key, control] = entry;
1779
+ if (control.type === "button") {
1780
+ rootButtonControls.push([key, control]);
1781
+ } else {
1782
+ rootNormalControls.push(entry);
1783
+ }
1784
+ });
1204
1785
  const folderGroups = folderOrder.map((folder) => ({
1205
1786
  folder,
1206
1787
  entries: folderControls.get(folder) ?? [],
@@ -1209,7 +1790,7 @@ var ControlPanel = () => {
1209
1790
  })).filter((group) => group.entries.length > 0 || group.extras.length > 0);
1210
1791
  const hasRootButtonControls = rootButtonControls.length > 0;
1211
1792
  const hasAnyFolders = folderGroups.length > 0;
1212
- const jsonToComponentString = useCallback3(
1793
+ const jsonToComponentString = useCallback4(
1213
1794
  ({
1214
1795
  componentName: componentNameOverride,
1215
1796
  props
@@ -1240,16 +1821,75 @@ var ControlPanel = () => {
1240
1821
  componentName,
1241
1822
  values,
1242
1823
  schema,
1243
- jsx: jsx14,
1824
+ jsx: jsx15,
1244
1825
  jsonToComponentString
1245
- }) ?? jsx14;
1826
+ }) ?? jsx15;
1246
1827
  const shouldShowCopyButton = config?.showCopyButton !== false && Boolean(copyText);
1828
+ const baseSnippet = copyText || jsx15;
1829
+ const formattedCode = useMemo4(
1830
+ () => formatJsxCodeSnippet(baseSnippet),
1831
+ [baseSnippet]
1832
+ );
1833
+ const hasCodeSnippet = Boolean(config?.showCodeSnippet && formattedCode);
1834
+ const highlightedCode = useMemo4(
1835
+ () => formattedCode ? highlightJsx(formattedCode) : null,
1836
+ [formattedCode]
1837
+ );
1838
+ useEffect6(() => {
1839
+ if (!hasCodeSnippet) {
1840
+ setIsCodeVisible(false);
1841
+ }
1842
+ }, [hasCodeSnippet]);
1843
+ useEffect6(() => {
1844
+ setCodeCopied(false);
1845
+ if (codeCopyTimeoutRef.current) {
1846
+ clearTimeout(codeCopyTimeoutRef.current);
1847
+ codeCopyTimeoutRef.current = null;
1848
+ }
1849
+ }, [formattedCode]);
1850
+ useEffect6(() => {
1851
+ return () => {
1852
+ if (codeCopyTimeoutRef.current) {
1853
+ clearTimeout(codeCopyTimeoutRef.current);
1854
+ }
1855
+ };
1856
+ }, []);
1857
+ const handleToggleCodeVisibility = useCallback4(() => {
1858
+ setIsCodeVisible((prev) => {
1859
+ const next = !prev;
1860
+ if (!next) {
1861
+ setCodeCopied(false);
1862
+ if (codeCopyTimeoutRef.current) {
1863
+ clearTimeout(codeCopyTimeoutRef.current);
1864
+ codeCopyTimeoutRef.current = null;
1865
+ }
1866
+ }
1867
+ return next;
1868
+ });
1869
+ }, []);
1870
+ const handleCodeCopy = useCallback4(() => {
1871
+ if (!formattedCode) return;
1872
+ if (typeof navigator === "undefined" || !navigator.clipboard || typeof navigator.clipboard.writeText !== "function") {
1873
+ return;
1874
+ }
1875
+ navigator.clipboard.writeText(formattedCode).then(() => {
1876
+ setCodeCopied(true);
1877
+ if (codeCopyTimeoutRef.current) {
1878
+ clearTimeout(codeCopyTimeoutRef.current);
1879
+ }
1880
+ codeCopyTimeoutRef.current = setTimeout(() => {
1881
+ setCodeCopied(false);
1882
+ codeCopyTimeoutRef.current = null;
1883
+ }, 3e3);
1884
+ }).catch(() => {
1885
+ });
1886
+ }, [formattedCode]);
1247
1887
  const labelize = (key) => key.replace(/([A-Z])/g, " $1").replace(/[\-_]/g, " ").replace(/\s+/g, " ").trim().replace(/(^|\s)\S/g, (s) => s.toUpperCase());
1248
- const renderButtonControl = (key, control, variant) => /* @__PURE__ */ jsx10(
1888
+ const renderButtonControl = (key, control, variant) => /* @__PURE__ */ jsx11(
1249
1889
  "div",
1250
1890
  {
1251
1891
  className: variant === "root" ? "flex-1 [&_[data-slot=button]]:w-full" : "[&_[data-slot=button]]:w-full",
1252
- children: control.render ? control.render() : /* @__PURE__ */ jsx10(
1892
+ children: control.render ? control.render() : /* @__PURE__ */ jsx11(
1253
1893
  "button",
1254
1894
  {
1255
1895
  onClick: control.onClick,
@@ -1267,9 +1907,9 @@ var ControlPanel = () => {
1267
1907
  const value = values[key];
1268
1908
  switch (control.type) {
1269
1909
  case "boolean":
1270
- return /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
1271
- /* @__PURE__ */ jsx10(Label, { htmlFor: key, className: "cursor-pointer", children: labelize(key) }),
1272
- /* @__PURE__ */ jsx10(
1910
+ return /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between", children: [
1911
+ /* @__PURE__ */ jsx11(Label, { htmlFor: key, className: "cursor-pointer", children: labelize(key) }),
1912
+ /* @__PURE__ */ jsx11(
1273
1913
  Switch,
1274
1914
  {
1275
1915
  id: key,
@@ -1280,10 +1920,10 @@ var ControlPanel = () => {
1280
1920
  )
1281
1921
  ] }, key);
1282
1922
  case "number":
1283
- return /* @__PURE__ */ jsxs5("div", { className: "space-y-3 w-full", children: [
1284
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
1285
- /* @__PURE__ */ jsx10(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
1286
- /* @__PURE__ */ jsx10(
1923
+ return /* @__PURE__ */ jsxs6("div", { className: "space-y-3 w-full", children: [
1924
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between", children: [
1925
+ /* @__PURE__ */ jsx11(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
1926
+ /* @__PURE__ */ jsx11(
1287
1927
  Input,
1288
1928
  {
1289
1929
  type: "number",
@@ -1300,7 +1940,7 @@ var ControlPanel = () => {
1300
1940
  }
1301
1941
  )
1302
1942
  ] }),
1303
- /* @__PURE__ */ jsx10(
1943
+ /* @__PURE__ */ jsx11(
1304
1944
  Slider,
1305
1945
  {
1306
1946
  id: key,
@@ -1314,9 +1954,9 @@ var ControlPanel = () => {
1314
1954
  )
1315
1955
  ] }, key);
1316
1956
  case "string":
1317
- return /* @__PURE__ */ jsxs5("div", { className: "space-y-2 w-full", children: [
1318
- /* @__PURE__ */ jsx10(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
1319
- /* @__PURE__ */ jsx10(
1957
+ return /* @__PURE__ */ jsxs6("div", { className: "space-y-2 w-full", children: [
1958
+ /* @__PURE__ */ jsx11(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
1959
+ /* @__PURE__ */ jsx11(
1320
1960
  Input,
1321
1961
  {
1322
1962
  id: key,
@@ -1328,9 +1968,9 @@ var ControlPanel = () => {
1328
1968
  )
1329
1969
  ] }, key);
1330
1970
  case "color":
1331
- return /* @__PURE__ */ jsxs5("div", { className: "space-y-2 w-full", children: [
1332
- /* @__PURE__ */ jsx10(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
1333
- /* @__PURE__ */ jsx10(
1971
+ return /* @__PURE__ */ jsxs6("div", { className: "space-y-2 w-full", children: [
1972
+ /* @__PURE__ */ jsx11(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
1973
+ /* @__PURE__ */ jsx11(
1334
1974
  "input",
1335
1975
  {
1336
1976
  type: "color",
@@ -1342,11 +1982,11 @@ var ControlPanel = () => {
1342
1982
  )
1343
1983
  ] }, key);
1344
1984
  case "select":
1345
- return /* @__PURE__ */ jsx10("div", { className: "space-y-2", children: /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3", children: [
1346
- /* @__PURE__ */ jsx10(Label, { className: "min-w-fit", htmlFor: key, children: labelize(key) }),
1347
- /* @__PURE__ */ jsxs5(Select, { value, onValueChange: (val) => setValue(key, val), children: [
1348
- /* @__PURE__ */ jsx10(SelectTrigger, { className: "flex-1 cursor-pointer", children: /* @__PURE__ */ jsx10(SelectValue, { placeholder: "Select option" }) }),
1349
- /* @__PURE__ */ jsx10(SelectContent, { className: "cursor-pointer z-[9999]", children: Object.entries(control.options).map(([label]) => /* @__PURE__ */ jsx10(
1985
+ return /* @__PURE__ */ jsx11("div", { className: "space-y-2", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-3", children: [
1986
+ /* @__PURE__ */ jsx11(Label, { className: "min-w-fit", htmlFor: key, children: labelize(key) }),
1987
+ /* @__PURE__ */ jsxs6(Select, { value, onValueChange: (val) => setValue(key, val), children: [
1988
+ /* @__PURE__ */ jsx11(SelectTrigger, { className: "flex-1 cursor-pointer", children: /* @__PURE__ */ jsx11(SelectValue, { placeholder: "Select option" }) }),
1989
+ /* @__PURE__ */ jsx11(SelectContent, { className: "cursor-pointer z-[9999]", children: Object.entries(control.options).map(([label]) => /* @__PURE__ */ jsx11(
1350
1990
  SelectItem,
1351
1991
  {
1352
1992
  value: label,
@@ -1363,12 +2003,12 @@ var ControlPanel = () => {
1363
2003
  };
1364
2004
  const renderFolder = (folder, entries, extras = []) => {
1365
2005
  const isOpen = folderStates[folder] ?? true;
1366
- return /* @__PURE__ */ jsxs5(
2006
+ return /* @__PURE__ */ jsxs6(
1367
2007
  "div",
1368
2008
  {
1369
2009
  className: "border border-stone-700/60 rounded-lg bg-stone-900/70",
1370
2010
  children: [
1371
- /* @__PURE__ */ jsxs5(
2011
+ /* @__PURE__ */ jsxs6(
1372
2012
  "button",
1373
2013
  {
1374
2014
  type: "button",
@@ -1378,8 +2018,8 @@ var ControlPanel = () => {
1378
2018
  })),
1379
2019
  className: "w-full flex items-center justify-between px-4 py-3 text-left font-semibold text-stone-200 tracking-wide",
1380
2020
  children: [
1381
- /* @__PURE__ */ jsx10("span", { children: folder }),
1382
- /* @__PURE__ */ jsx10(
2021
+ /* @__PURE__ */ jsx11("span", { children: folder }),
2022
+ /* @__PURE__ */ jsx11(
1383
2023
  ChevronDown2,
1384
2024
  {
1385
2025
  className: `w-4 h-4 transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`
@@ -1388,7 +2028,7 @@ var ControlPanel = () => {
1388
2028
  ]
1389
2029
  }
1390
2030
  ),
1391
- isOpen && /* @__PURE__ */ jsxs5("div", { className: "px-4 pb-4 pt-0 space-y-5", children: [
2031
+ isOpen && /* @__PURE__ */ jsxs6("div", { className: "px-4 pb-4 pt-0 space-y-5", children: [
1392
2032
  entries.map(
1393
2033
  ([key, control]) => renderControl(key, control, "folder")
1394
2034
  ),
@@ -1410,7 +2050,7 @@ var ControlPanel = () => {
1410
2050
  height: "auto",
1411
2051
  flex: "0 0 auto"
1412
2052
  };
1413
- if (isHydrated) {
2053
+ if (isHydrated && !isControlsOnlyView) {
1414
2054
  if (isDesktop) {
1415
2055
  Object.assign(panelStyle, {
1416
2056
  position: "absolute",
@@ -1427,47 +2067,87 @@ var ControlPanel = () => {
1427
2067
  });
1428
2068
  }
1429
2069
  }
1430
- return /* @__PURE__ */ jsx10(
2070
+ return /* @__PURE__ */ jsx11(
1431
2071
  "div",
1432
2072
  {
1433
2073
  className: `order-2 md:order-1 w-full md:h-auto p-2 md:p-4 bg-stone-900 font-mono text-stone-300 transition-opacity duration-300 z-max ${!isHydrated ? "opacity-0" : "opacity-100"}`,
1434
2074
  onPointerDown: (e) => e.stopPropagation(),
1435
2075
  onTouchStart: (e) => e.stopPropagation(),
1436
2076
  style: panelStyle,
1437
- children: /* @__PURE__ */ jsxs5("div", { className: "dark mb-10 space-y-6 p-4 md:p-6 bg-stone-900/95 backdrop-blur-md border-2 border-stone-700 rounded-xl shadow-lg", children: [
1438
- /* @__PURE__ */ jsx10("div", { className: "space-y-1", children: /* @__PURE__ */ jsx10("h1", { className: "text-lg text-stone-100 font-semibold", children: config?.mainLabel ?? "Controls" }) }),
1439
- /* @__PURE__ */ jsxs5("div", { className: "space-y-6", children: [
2077
+ children: /* @__PURE__ */ jsxs6("div", { className: "dark mb-10 space-y-6 p-4 md:p-6 bg-stone-900/95 backdrop-blur-md border-2 border-stone-700 rounded-xl shadow-lg", children: [
2078
+ /* @__PURE__ */ jsx11("div", { className: "space-y-1", children: /* @__PURE__ */ jsx11("h1", { className: "text-lg text-stone-100 font-semibold", children: config?.mainLabel ?? "Controls" }) }),
2079
+ /* @__PURE__ */ jsxs6("div", { className: "space-y-6", children: [
1440
2080
  topFolderSections,
1441
- hasRootButtonControls && /* @__PURE__ */ jsx10("div", { className: "flex flex-wrap gap-2", children: rootButtonControls.map(
2081
+ hasRootButtonControls && /* @__PURE__ */ jsx11("div", { className: "flex flex-wrap gap-2", children: rootButtonControls.map(
1442
2082
  ([key, control]) => renderButtonControl(key, control, "root")
1443
2083
  ) }),
1444
2084
  advancedPaletteControlNode,
2085
+ mediaUploadControlNode,
1445
2086
  rootNormalControls.map(
1446
2087
  ([key, control]) => renderControl(key, control, "root")
1447
2088
  ),
1448
2089
  bottomFolderSections,
1449
- shouldShowCopyButton && /* @__PURE__ */ jsx10("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ jsx10(
2090
+ hasCodeSnippet && /* @__PURE__ */ jsxs6("div", { className: "border border-stone-700/60 rounded-lg bg-stone-900/70", children: [
2091
+ /* @__PURE__ */ jsxs6(
2092
+ "button",
2093
+ {
2094
+ type: "button",
2095
+ onClick: handleToggleCodeVisibility,
2096
+ className: "w-full flex items-center justify-between px-4 py-3 text-left font-semibold text-stone-200 tracking-wide",
2097
+ "aria-expanded": isCodeVisible,
2098
+ children: [
2099
+ /* @__PURE__ */ jsx11("span", { children: isCodeVisible ? "Hide Code" : "Show Code" }),
2100
+ /* @__PURE__ */ jsx11(
2101
+ ChevronDown2,
2102
+ {
2103
+ className: `w-4 h-4 transition-transform duration-200 ${isCodeVisible ? "rotate-180" : ""}`
2104
+ }
2105
+ )
2106
+ ]
2107
+ }
2108
+ ),
2109
+ isCodeVisible && /* @__PURE__ */ jsxs6("div", { className: "relative border-t border-stone-700/60 bg-stone-950/60 px-4 py-4 rounded-b-lg", children: [
2110
+ /* @__PURE__ */ jsx11(
2111
+ "button",
2112
+ {
2113
+ type: "button",
2114
+ onClick: handleCodeCopy,
2115
+ className: "absolute top-3 right-3 flex items-center gap-1 rounded-md border border-stone-700 bg-stone-800 px-2 py-1 text-xs font-medium text-white shadow hover:bg-stone-700",
2116
+ children: codeCopied ? /* @__PURE__ */ jsxs6(Fragment, { children: [
2117
+ /* @__PURE__ */ jsx11(Check2, { className: "h-3.5 w-3.5" }),
2118
+ "Copied"
2119
+ ] }) : /* @__PURE__ */ jsxs6(Fragment, { children: [
2120
+ /* @__PURE__ */ jsx11(Copy, { className: "h-3.5 w-3.5" }),
2121
+ "Copy"
2122
+ ] })
2123
+ }
2124
+ ),
2125
+ /* @__PURE__ */ jsx11("pre", { className: "whitespace-pre overflow-x-auto text-xs md:text-sm text-stone-200 pr-14", children: /* @__PURE__ */ jsx11("code", { className: "block text-stone-200", children: highlightedCode ?? formattedCode }) })
2126
+ ] })
2127
+ ] }),
2128
+ shouldShowCopyButton && /* @__PURE__ */ jsx11("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ jsx11(
1450
2129
  "button",
1451
2130
  {
1452
2131
  onClick: () => {
1453
- if (!copyText) return;
1454
- navigator.clipboard.writeText(copyText);
2132
+ const copyPayload = formattedCode || baseSnippet;
2133
+ if (!copyPayload) return;
2134
+ navigator.clipboard.writeText(copyPayload);
1455
2135
  setCopied(true);
1456
2136
  setTimeout(() => setCopied(false), 5e3);
1457
2137
  },
1458
2138
  className: "w-full px-4 py-2 text-sm bg-stone-800 hover:bg-stone-700 text-white rounded-md flex items-center justify-center gap-2 shadow",
1459
- children: copied ? /* @__PURE__ */ jsxs5(Fragment, { children: [
1460
- /* @__PURE__ */ jsx10(Check2, { className: "w-4 h-4" }),
2139
+ children: copied ? /* @__PURE__ */ jsxs6(Fragment, { children: [
2140
+ /* @__PURE__ */ jsx11(Check2, { className: "w-4 h-4" }),
1461
2141
  "Copied"
1462
- ] }) : /* @__PURE__ */ jsxs5(Fragment, { children: [
1463
- /* @__PURE__ */ jsx10(Copy, { className: "w-4 h-4" }),
2142
+ ] }) : /* @__PURE__ */ jsxs6(Fragment, { children: [
2143
+ /* @__PURE__ */ jsx11(Copy, { className: "w-4 h-4" }),
1464
2144
  "Copy to Clipboard"
1465
2145
  ] })
1466
2146
  }
1467
2147
  ) }, "control-panel-jsx")
1468
2148
  ] }),
1469
- previewUrl && /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2", children: [
1470
- /* @__PURE__ */ jsx10(Button, { asChild: true, className: "w-full", children: /* @__PURE__ */ jsxs5(
2149
+ previewUrl && /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-2", children: [
2150
+ /* @__PURE__ */ jsx11(Button, { asChild: true, className: "w-full", children: /* @__PURE__ */ jsxs6(
1471
2151
  "a",
1472
2152
  {
1473
2153
  href: previewUrl,
@@ -1475,12 +2155,12 @@ var ControlPanel = () => {
1475
2155
  rel: "noopener noreferrer",
1476
2156
  className: "w-full px-4 py-2 text-sm text-center bg-stone-900 hover:bg-stone-800 text-white rounded-md border border-stone-700",
1477
2157
  children: [
1478
- /* @__PURE__ */ jsx10(SquareArrowOutUpRight, {}),
2158
+ /* @__PURE__ */ jsx11(SquareArrowOutUpRight, {}),
1479
2159
  " Open in a New Tab"
1480
2160
  ]
1481
2161
  }
1482
2162
  ) }),
1483
- config?.showPresentationButton && presentationUrl && /* @__PURE__ */ jsxs5(
2163
+ config?.showPresentationButton && presentationUrl && /* @__PURE__ */ jsxs6(
1484
2164
  Button,
1485
2165
  {
1486
2166
  type: "button",
@@ -1488,7 +2168,7 @@ var ControlPanel = () => {
1488
2168
  variant: "secondary",
1489
2169
  className: "w-full bg-stone-800 text-white hover:bg-stone-700 border border-stone-700",
1490
2170
  children: [
1491
- /* @__PURE__ */ jsx10(Presentation, {}),
2171
+ /* @__PURE__ */ jsx11(Presentation, {}),
1492
2172
  " Presentation Mode"
1493
2173
  ]
1494
2174
  }
@@ -1500,16 +2180,16 @@ var ControlPanel = () => {
1500
2180
  };
1501
2181
  var ControlPanel_default = ControlPanel;
1502
2182
 
1503
- // src/components/PreviewContainer/PreviewContainer.tsx
1504
- import { useRef as useRef4 } from "react";
2183
+ // src/components/PreviewContainer.tsx
2184
+ import { useRef as useRef6 } from "react";
1505
2185
 
1506
- // src/components/Grid/Grid.tsx
1507
- import { jsx as jsx11 } from "react/jsx-runtime";
2186
+ // src/components/Grid.tsx
2187
+ import { jsx as jsx12 } from "react/jsx-runtime";
1508
2188
  function Grid() {
1509
- return /* @__PURE__ */ jsx11(
2189
+ return /* @__PURE__ */ jsx12(
1510
2190
  "div",
1511
2191
  {
1512
- className: "absolute inset-0 w-screen h-screen z-[0] blur-[1px]",
2192
+ className: "absolute inset-0 w-full h-full z-[0] blur-[1px]",
1513
2193
  style: {
1514
2194
  backgroundImage: `
1515
2195
  linear-gradient(to right,rgb(13, 13, 13) 1px, transparent 1px),
@@ -1523,40 +2203,40 @@ function Grid() {
1523
2203
  }
1524
2204
  var Grid_default = Grid;
1525
2205
 
1526
- // src/components/PreviewContainer/PreviewContainer.tsx
1527
- import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
2206
+ // src/components/PreviewContainer.tsx
2207
+ import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
1528
2208
  var PreviewContainer = ({ children, hideControls }) => {
1529
2209
  const { config } = useControlsContext();
1530
2210
  const { leftPanelWidth, isDesktop, isHydrated, containerRef } = useResizableLayout();
1531
- const previewRef = useRef4(null);
1532
- return /* @__PURE__ */ jsx12(
2211
+ const previewRef = useRef6(null);
2212
+ return /* @__PURE__ */ jsx13(
1533
2213
  "div",
1534
2214
  {
1535
2215
  ref: previewRef,
1536
- className: "order-1 md:order-2 flex-1 bg-black overflow-auto flex items-center justify-center relative",
2216
+ className: "order-1 md:order-2 flex-1 md:flex-none bg-black overflow-auto flex items-center justify-center relative",
1537
2217
  style: isHydrated && isDesktop && !hideControls ? {
1538
2218
  width: `${100 - leftPanelWidth}%`,
1539
2219
  marginLeft: `${leftPanelWidth}%`
1540
2220
  } : {},
1541
- children: /* @__PURE__ */ jsxs6("div", { className: "w-screen h-screen", children: [
1542
- config?.showGrid && /* @__PURE__ */ jsx12(Grid_default, {}),
1543
- /* @__PURE__ */ jsx12("div", { className: "w-screen h-screen flex items-center justify-center relative", children })
2221
+ children: /* @__PURE__ */ jsxs7("div", { className: "w-full h-screen", children: [
2222
+ config?.showGrid && /* @__PURE__ */ jsx13(Grid_default, {}),
2223
+ /* @__PURE__ */ jsx13("div", { className: "w-full h-full flex items-center justify-center relative", children })
1544
2224
  ] })
1545
2225
  }
1546
2226
  );
1547
2227
  };
1548
2228
  var PreviewContainer_default = PreviewContainer;
1549
2229
 
1550
- // src/components/Playground/Playground.tsx
1551
- import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
1552
- var HiddenPreview = ({ children }) => /* @__PURE__ */ jsx13("div", { "aria-hidden": "true", className: "hidden", children });
2230
+ // src/components/Playground.tsx
2231
+ import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
2232
+ var HiddenPreview = ({ children }) => /* @__PURE__ */ jsx14("div", { "aria-hidden": "true", className: "hidden", children });
1553
2233
  function Playground({ children }) {
1554
2234
  const [isHydrated, setIsHydrated] = useState5(false);
1555
2235
  const [copied, setCopied] = useState5(false);
1556
- useEffect5(() => {
2236
+ useEffect7(() => {
1557
2237
  setIsHydrated(true);
1558
2238
  }, []);
1559
- const { showControls, isPresentationMode, isControlsOnly } = useMemo4(() => {
2239
+ const { showControls, isPresentationMode, isControlsOnly } = useMemo5(() => {
1560
2240
  if (typeof window === "undefined") {
1561
2241
  return {
1562
2242
  showControls: true,
@@ -1583,54 +2263,54 @@ function Playground({ children }) {
1583
2263
  setTimeout(() => setCopied(false), 2e3);
1584
2264
  };
1585
2265
  if (!isHydrated) return null;
1586
- return /* @__PURE__ */ jsx13(ResizableLayout, { hideControls: layoutHideControls, children: /* @__PURE__ */ jsxs7(ControlsProvider, { children: [
1587
- shouldShowShareButton && /* @__PURE__ */ jsxs7(
2266
+ return /* @__PURE__ */ jsx14(ResizableLayout, { hideControls: layoutHideControls, children: /* @__PURE__ */ jsxs8(ControlsProvider, { children: [
2267
+ shouldShowShareButton && /* @__PURE__ */ jsxs8(
1588
2268
  "button",
1589
2269
  {
1590
2270
  onClick: handleCopy,
1591
2271
  className: "absolute top-4 right-4 z-50 flex items-center gap-1 rounded bg-black/70 px-3 py-1 text-white hover:bg-black",
1592
2272
  children: [
1593
- copied ? /* @__PURE__ */ jsx13(Check3, { size: 16 }) : /* @__PURE__ */ jsx13(Copy2, { size: 16 }),
2273
+ copied ? /* @__PURE__ */ jsx14(Check3, { size: 16 }) : /* @__PURE__ */ jsx14(Copy2, { size: 16 }),
1594
2274
  copied ? "Copied!" : "Share"
1595
2275
  ]
1596
2276
  }
1597
2277
  ),
1598
- isControlsOnly ? /* @__PURE__ */ jsx13(HiddenPreview, { children }) : /* @__PURE__ */ jsx13(PreviewContainer_default, { hideControls: layoutHideControls, children }),
1599
- showControls && /* @__PURE__ */ jsx13(ControlPanel_default, {})
2278
+ isControlsOnly ? /* @__PURE__ */ jsx14(HiddenPreview, { children }) : /* @__PURE__ */ jsx14(PreviewContainer_default, { hideControls: layoutHideControls, children }),
2279
+ showControls && /* @__PURE__ */ jsx14(ControlPanel_default, {})
1600
2280
  ] }) });
1601
2281
  }
1602
2282
 
1603
2283
  // src/hooks/useAdvancedPaletteControls.ts
1604
- import { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef5, useState as useState6 } from "react";
2284
+ import { useCallback as useCallback5, useEffect as useEffect8, useMemo as useMemo6, useRef as useRef7, useState as useState6 } from "react";
1605
2285
  var cloneForCallbacks = (palette) => clonePalette(palette);
1606
2286
  var useAdvancedPaletteControls = (options = {}) => {
1607
- const resolvedDefaultPalette = useMemo5(
2287
+ const resolvedDefaultPalette = useMemo6(
1608
2288
  () => createAdvancedPalette(options.defaultPalette),
1609
2289
  [options.defaultPalette]
1610
2290
  );
1611
- const resolvedFallbackPalette = useMemo5(
2291
+ const resolvedFallbackPalette = useMemo6(
1612
2292
  () => options.fallbackPalette ? createAdvancedPalette(options.fallbackPalette) : resolvedDefaultPalette,
1613
2293
  [options.fallbackPalette, resolvedDefaultPalette]
1614
2294
  );
1615
2295
  const [palette, setPaletteState] = useState6(
1616
2296
  () => clonePalette(resolvedDefaultPalette)
1617
2297
  );
1618
- const defaultSignatureRef = useRef5(
2298
+ const defaultSignatureRef = useRef7(
1619
2299
  createPaletteSignature(resolvedDefaultPalette)
1620
2300
  );
1621
- useEffect6(() => {
2301
+ useEffect8(() => {
1622
2302
  const nextSignature = createPaletteSignature(resolvedDefaultPalette);
1623
2303
  if (defaultSignatureRef.current === nextSignature) return;
1624
2304
  defaultSignatureRef.current = nextSignature;
1625
2305
  setPaletteState(clonePalette(resolvedDefaultPalette));
1626
2306
  }, [resolvedDefaultPalette]);
1627
- const notifyChange = useCallback4(
2307
+ const notifyChange = useCallback5(
1628
2308
  (nextPalette) => {
1629
2309
  options.onChange?.(cloneForCallbacks(nextPalette));
1630
2310
  },
1631
2311
  [options.onChange]
1632
2312
  );
1633
- const setPalette = useCallback4(
2313
+ const setPalette = useCallback5(
1634
2314
  (source) => {
1635
2315
  const nextPalette = createAdvancedPalette(
1636
2316
  source ?? resolvedDefaultPalette
@@ -1640,7 +2320,7 @@ var useAdvancedPaletteControls = (options = {}) => {
1640
2320
  },
1641
2321
  [notifyChange, resolvedDefaultPalette]
1642
2322
  );
1643
- const updatePalette = useCallback4(
2323
+ const updatePalette = useCallback5(
1644
2324
  (updater) => {
1645
2325
  setPaletteState((current) => {
1646
2326
  const nextSource = updater(clonePalette(current));
@@ -1653,18 +2333,18 @@ var useAdvancedPaletteControls = (options = {}) => {
1653
2333
  },
1654
2334
  [notifyChange, resolvedDefaultPalette]
1655
2335
  );
1656
- const resetPalette = useCallback4(() => {
2336
+ const resetPalette = useCallback5(() => {
1657
2337
  setPaletteState(clonePalette(resolvedDefaultPalette));
1658
2338
  notifyChange(resolvedDefaultPalette);
1659
2339
  }, [notifyChange, resolvedDefaultPalette]);
1660
- const handleControlPaletteChange = useCallback4(
2340
+ const handleControlPaletteChange = useCallback5(
1661
2341
  (nextPalette) => {
1662
2342
  setPaletteState(clonePalette(nextPalette));
1663
2343
  notifyChange(nextPalette);
1664
2344
  },
1665
2345
  [notifyChange]
1666
2346
  );
1667
- const controlConfig = useMemo5(
2347
+ const controlConfig = useMemo6(
1668
2348
  () => ({
1669
2349
  ...options.control ?? {},
1670
2350
  defaultPalette: resolvedDefaultPalette,
@@ -1672,7 +2352,7 @@ var useAdvancedPaletteControls = (options = {}) => {
1672
2352
  }),
1673
2353
  [handleControlPaletteChange, options.control, resolvedDefaultPalette]
1674
2354
  );
1675
- const hexColors = useMemo5(
2355
+ const hexColors = useMemo6(
1676
2356
  () => advancedPaletteToHexColors(palette, {
1677
2357
  sectionOrder: options.sectionOrder,
1678
2358
  fallbackPalette: resolvedFallbackPalette,
@@ -1685,11 +2365,11 @@ var useAdvancedPaletteControls = (options = {}) => {
1685
2365
  resolvedFallbackPalette
1686
2366
  ]
1687
2367
  );
1688
- const paletteSignature = useMemo5(
2368
+ const paletteSignature = useMemo6(
1689
2369
  () => createPaletteSignature(palette),
1690
2370
  [palette]
1691
2371
  );
1692
- const paletteGradient = useMemo5(
2372
+ const paletteGradient = useMemo6(
1693
2373
  () => computePaletteGradient(palette, options.gradientSteps),
1694
2374
  [options.gradientSteps, palette]
1695
2375
  );