@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.js CHANGED
@@ -49,9 +49,9 @@ __export(src_exports, {
49
49
  });
50
50
  module.exports = __toCommonJS(src_exports);
51
51
 
52
- // src/components/Playground/Playground.tsx
53
- var import_react7 = require("react");
54
- var import_lucide_react4 = require("lucide-react");
52
+ // src/components/Playground.tsx
53
+ var import_react8 = require("react");
54
+ var import_lucide_react5 = require("lucide-react");
55
55
 
56
56
  // src/context/ResizableLayout.tsx
57
57
  var import_react = require("react");
@@ -448,7 +448,8 @@ var ControlsProvider = ({ children }) => {
448
448
  const [schema, setSchema] = (0, import_react2.useState)({});
449
449
  const [values, setValues] = (0, import_react2.useState)({});
450
450
  const [config, setConfig] = (0, import_react2.useState)({
451
- showCopyButton: true
451
+ showCopyButton: true,
452
+ showCodeSnippet: false
452
453
  });
453
454
  const [componentName, setComponentName] = (0, import_react2.useState)();
454
455
  const [channelName, setChannelName] = (0, import_react2.useState)(null);
@@ -471,17 +472,30 @@ var ControlsProvider = ({ children }) => {
471
472
  setComponentName(opts.componentName);
472
473
  }
473
474
  if (opts?.config) {
474
- const { addAdvancedPaletteControl, ...otherConfig } = opts.config;
475
- setConfig((prev) => ({
476
- ...prev,
477
- ...otherConfig,
478
- ...Object.prototype.hasOwnProperty.call(
475
+ const {
476
+ addAdvancedPaletteControl,
477
+ addMediaUploadControl,
478
+ ...otherConfig
479
+ } = opts.config;
480
+ setConfig((prev) => {
481
+ const nextConfig = {
482
+ ...prev,
483
+ ...otherConfig
484
+ };
485
+ if (Object.prototype.hasOwnProperty.call(
479
486
  opts.config,
480
487
  "addAdvancedPaletteControl"
481
- ) ? {
482
- addAdvancedPaletteControl: addAdvancedPaletteControl ? resolveAdvancedPaletteConfig(addAdvancedPaletteControl) : void 0
483
- } : {}
484
- }));
488
+ )) {
489
+ nextConfig.addAdvancedPaletteControl = addAdvancedPaletteControl ? resolveAdvancedPaletteConfig(addAdvancedPaletteControl) : void 0;
490
+ }
491
+ if (Object.prototype.hasOwnProperty.call(
492
+ opts.config,
493
+ "addMediaUploadControl"
494
+ )) {
495
+ nextConfig.addMediaUploadControl = addMediaUploadControl ? { ...addMediaUploadControl } : void 0;
496
+ }
497
+ return nextConfig;
498
+ });
485
499
  }
486
500
  setSchema((prevSchema) => ({ ...prevSchema, ...newSchema }));
487
501
  setValues((prevValues) => {
@@ -646,7 +660,7 @@ var useControls = (schema, options) => {
646
660
  resolvedAdvancedConfig.onPaletteChange(clonePalette(palette));
647
661
  }, [ctx.values, resolvedAdvancedConfig]);
648
662
  const typedValues = ctx.values;
649
- const jsx14 = (0, import_react2.useCallback)(() => {
663
+ const jsx15 = (0, import_react2.useCallback)(() => {
650
664
  if (!options?.componentName) return "";
651
665
  const props = Object.entries(typedValues).map(([key, val]) => {
652
666
  if (typeof val === "string") return `${key}="${val}"`;
@@ -660,14 +674,14 @@ var useControls = (schema, options) => {
660
674
  controls: ctx.values,
661
675
  schema: ctx.schema,
662
676
  setValue: ctx.setValue,
663
- jsx: jsx14
677
+ jsx: jsx15
664
678
  };
665
679
  };
666
680
  var useUrlSyncedControls = useControls;
667
681
 
668
- // src/components/ControlPanel/ControlPanel.tsx
669
- var import_react5 = require("react");
670
- var import_lucide_react3 = require("lucide-react");
682
+ // src/components/ControlPanel.tsx
683
+ var import_react6 = require("react");
684
+ var import_lucide_react4 = require("lucide-react");
671
685
 
672
686
  // src/hooks/usePreviewUrl.ts
673
687
  var import_react3 = require("react");
@@ -942,7 +956,7 @@ Button.displayName = "Button";
942
956
  // src/constants/layout.ts
943
957
  var MOBILE_CONTROL_PANEL_PEEK = 112;
944
958
 
945
- // src/components/AdvancedPaletteControl/AdvancedPaletteControl.tsx
959
+ // src/components/AdvancedPaletteControl.tsx
946
960
  var import_react4 = require("react");
947
961
  var import_jsx_runtime9 = require("react/jsx-runtime");
948
962
  var AdvancedPaletteControl = ({
@@ -1090,15 +1104,532 @@ var AdvancedPaletteControl = ({
1090
1104
  };
1091
1105
  var AdvancedPaletteControl_default = AdvancedPaletteControl;
1092
1106
 
1093
- // src/components/ControlPanel/ControlPanel.tsx
1107
+ // src/components/MediaUploadControl.tsx
1108
+ var import_react5 = require("react");
1109
+ var import_lucide_react3 = require("lucide-react");
1110
+
1111
+ // src/state/mediaSelectionStore.ts
1112
+ var snapshot = {
1113
+ media: null,
1114
+ error: null
1115
+ };
1116
+ var listeners = /* @__PURE__ */ new Set();
1117
+ var emitChange = () => {
1118
+ for (const listener of listeners) {
1119
+ listener();
1120
+ }
1121
+ };
1122
+ var mediaSelectionStore = {
1123
+ subscribe: (listener) => {
1124
+ listeners.add(listener);
1125
+ return () => {
1126
+ listeners.delete(listener);
1127
+ };
1128
+ },
1129
+ getSnapshot: () => snapshot,
1130
+ setSnapshot: (next) => {
1131
+ snapshot = next;
1132
+ emitChange();
1133
+ }
1134
+ };
1135
+
1136
+ // src/components/MediaUploadControl.tsx
1094
1137
  var import_jsx_runtime10 = require("react/jsx-runtime");
1138
+ var DEFAULT_PRESET_MEDIA = [
1139
+ {
1140
+ src: "https://res.cloudinary.com/dz8kk1l4r/image/upload/v1763233793/astronaut_q84mbj.png",
1141
+ label: "Astronaut",
1142
+ type: "image"
1143
+ },
1144
+ {
1145
+ src: "https://res.cloudinary.com/dz8kk1l4r/image/upload/v1763233793/surreal-head_r0ozcd.png",
1146
+ label: "Futuristic",
1147
+ type: "image"
1148
+ },
1149
+ {
1150
+ src: "https://res.cloudinary.com/dz8kk1l4r/image/upload/v1763233797/futuristic_bpwdzt.png",
1151
+ label: "Surreal",
1152
+ type: "image"
1153
+ },
1154
+ {
1155
+ src: "https://res.cloudinary.com/dz8kk1l4r/image/upload/v1763233793/portrait_hd7dyc.png",
1156
+ label: "Portrait",
1157
+ type: "image"
1158
+ }
1159
+ ];
1160
+ function MediaUploadControl({
1161
+ onSelectMedia,
1162
+ onClear,
1163
+ presetMedia,
1164
+ maxPresetCount
1165
+ }) {
1166
+ const inputId = (0, import_react5.useId)();
1167
+ const inputRef = (0, import_react5.useRef)(null);
1168
+ const uploadedUrlRef = (0, import_react5.useRef)(null);
1169
+ const { media, error } = (0, import_react5.useSyncExternalStore)(
1170
+ mediaSelectionStore.subscribe,
1171
+ mediaSelectionStore.getSnapshot,
1172
+ mediaSelectionStore.getSnapshot
1173
+ );
1174
+ const VIDEO_EXTENSIONS = (0, import_react5.useMemo)(
1175
+ () => [".mp4", ".webm", ".ogg", ".ogv", ".mov", ".m4v"],
1176
+ []
1177
+ );
1178
+ const setSelection = (0, import_react5.useCallback)(
1179
+ (next) => {
1180
+ mediaSelectionStore.setSnapshot(next);
1181
+ },
1182
+ []
1183
+ );
1184
+ const handleFileChange = (event) => {
1185
+ const file = event.target.files?.[0];
1186
+ if (!file) {
1187
+ return;
1188
+ }
1189
+ if (uploadedUrlRef.current) {
1190
+ URL.revokeObjectURL(uploadedUrlRef.current);
1191
+ uploadedUrlRef.current = null;
1192
+ }
1193
+ const objectUrl = URL.createObjectURL(file);
1194
+ uploadedUrlRef.current = objectUrl;
1195
+ const lowerName = file.name?.toLowerCase() ?? "";
1196
+ const hasVideoExtension = VIDEO_EXTENSIONS.some(
1197
+ (ext) => lowerName.endsWith(ext)
1198
+ );
1199
+ const isVideo = file.type.startsWith("video/") || hasVideoExtension;
1200
+ if (isVideo) {
1201
+ setSelection({
1202
+ media: null,
1203
+ error: "Videos are not supported in this effect yet."
1204
+ });
1205
+ return;
1206
+ }
1207
+ const nextMedia = { src: objectUrl, type: "image" };
1208
+ setSelection({ media: nextMedia, error: null });
1209
+ onSelectMedia(nextMedia);
1210
+ };
1211
+ const handleClearSelection = () => {
1212
+ if (uploadedUrlRef.current) {
1213
+ URL.revokeObjectURL(uploadedUrlRef.current);
1214
+ uploadedUrlRef.current = null;
1215
+ }
1216
+ setSelection({ media: null, error: null });
1217
+ onClear();
1218
+ };
1219
+ const handlePresetSelect = (entry) => {
1220
+ if (entry.type === "video") {
1221
+ setSelection({
1222
+ media: null,
1223
+ error: "Videos are not supported in this effect yet."
1224
+ });
1225
+ return;
1226
+ }
1227
+ const nextMedia = { src: entry.src, type: entry.type };
1228
+ setSelection({ media: nextMedia, error: null });
1229
+ onSelectMedia(nextMedia);
1230
+ };
1231
+ (0, import_react5.useEffect)(() => {
1232
+ return () => {
1233
+ if (uploadedUrlRef.current) {
1234
+ URL.revokeObjectURL(uploadedUrlRef.current);
1235
+ uploadedUrlRef.current = null;
1236
+ }
1237
+ };
1238
+ }, []);
1239
+ const presets = (0, import_react5.useMemo)(() => {
1240
+ const source = presetMedia ?? DEFAULT_PRESET_MEDIA;
1241
+ if (typeof maxPresetCount === "number" && Number.isFinite(maxPresetCount)) {
1242
+ const safeCount = Math.max(0, Math.floor(maxPresetCount));
1243
+ return source.slice(0, safeCount);
1244
+ }
1245
+ return source;
1246
+ }, [presetMedia, maxPresetCount]);
1247
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1248
+ "div",
1249
+ {
1250
+ style: {
1251
+ display: "flex",
1252
+ flexDirection: "column",
1253
+ gap: "0.5rem"
1254
+ },
1255
+ children: [
1256
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("label", { htmlFor: inputId, style: { fontSize: "0.85rem", fontWeight: 500 }, children: "Upload media" }),
1257
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1258
+ "input",
1259
+ {
1260
+ id: inputId,
1261
+ type: "file",
1262
+ accept: "image/*",
1263
+ ref: inputRef,
1264
+ style: { display: "none" },
1265
+ onChange: handleFileChange
1266
+ }
1267
+ ),
1268
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1269
+ "div",
1270
+ {
1271
+ style: {
1272
+ display: "flex",
1273
+ alignItems: "center",
1274
+ gap: "0.75rem"
1275
+ },
1276
+ children: [
1277
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1278
+ "button",
1279
+ {
1280
+ type: "button",
1281
+ onClick: () => inputRef.current?.click(),
1282
+ style: {
1283
+ padding: "0.35rem 0.75rem",
1284
+ borderRadius: "0.4rem",
1285
+ border: "1px solid rgba(255, 255, 255, 0.25)",
1286
+ background: "rgba(255, 255, 255, 0.08)",
1287
+ color: "inherit",
1288
+ cursor: "pointer"
1289
+ },
1290
+ children: "Choose file"
1291
+ }
1292
+ ),
1293
+ media ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1294
+ "div",
1295
+ {
1296
+ style: {
1297
+ width: 36,
1298
+ height: 36,
1299
+ borderRadius: "0.35rem",
1300
+ overflow: "hidden",
1301
+ border: "1px solid rgba(255, 255, 255, 0.15)"
1302
+ },
1303
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1304
+ "img",
1305
+ {
1306
+ src: media.src,
1307
+ alt: "Thumbnail",
1308
+ style: {
1309
+ width: "100%",
1310
+ height: "100%",
1311
+ objectFit: "cover",
1312
+ display: "block"
1313
+ }
1314
+ }
1315
+ )
1316
+ }
1317
+ ) : null,
1318
+ media ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1319
+ "button",
1320
+ {
1321
+ type: "button",
1322
+ onClick: handleClearSelection,
1323
+ style: {
1324
+ display: "flex",
1325
+ alignItems: "center",
1326
+ justifyContent: "center",
1327
+ padding: "0.3rem",
1328
+ borderRadius: "0.4rem",
1329
+ border: "1px solid rgba(255,255,255,0.2)",
1330
+ background: "transparent",
1331
+ color: "inherit",
1332
+ cursor: "pointer"
1333
+ },
1334
+ "aria-label": "Clear selection",
1335
+ title: "Clear selection",
1336
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react3.X, { size: 16, strokeWidth: 2 })
1337
+ }
1338
+ ) : null
1339
+ ]
1340
+ }
1341
+ ),
1342
+ presets.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1343
+ "div",
1344
+ {
1345
+ style: {
1346
+ display: "grid",
1347
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
1348
+ gap: "0.5rem"
1349
+ },
1350
+ children: presets.map((entry) => {
1351
+ const isSelected = media?.src === entry.src && media?.type === entry.type;
1352
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1353
+ "button",
1354
+ {
1355
+ type: "button",
1356
+ onClick: () => handlePresetSelect(entry),
1357
+ style: {
1358
+ width: "100%",
1359
+ borderRadius: "0.4rem",
1360
+ border: "1px solid rgba(255,255,255,0.25)",
1361
+ outline: isSelected ? "2px solid #fff" : "none",
1362
+ outlineOffset: 2,
1363
+ padding: 0,
1364
+ overflow: "hidden",
1365
+ background: "transparent",
1366
+ cursor: "pointer"
1367
+ },
1368
+ children: [
1369
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1370
+ "img",
1371
+ {
1372
+ src: entry.src,
1373
+ alt: entry.label,
1374
+ style: {
1375
+ width: "100%",
1376
+ height: 100,
1377
+ objectFit: "cover",
1378
+ display: "block"
1379
+ }
1380
+ }
1381
+ ),
1382
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1383
+ "span",
1384
+ {
1385
+ style: {
1386
+ display: "block",
1387
+ padding: "0.35rem",
1388
+ fontSize: "0.75rem",
1389
+ textAlign: "left",
1390
+ background: "rgba(0,0,0,0.45)"
1391
+ },
1392
+ children: entry.label
1393
+ }
1394
+ )
1395
+ ]
1396
+ },
1397
+ `${entry.src}-${entry.type}`
1398
+ );
1399
+ })
1400
+ }
1401
+ ) : null,
1402
+ error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { color: "#ff9da4", fontSize: "0.8rem" }, children: error }) : null
1403
+ ]
1404
+ }
1405
+ );
1406
+ }
1407
+
1408
+ // src/components/ControlPanel.tsx
1409
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1410
+ var splitPropsString = (input) => {
1411
+ const props = [];
1412
+ let current = "";
1413
+ let curlyDepth = 0;
1414
+ let squareDepth = 0;
1415
+ let parenDepth = 0;
1416
+ let inSingleQuote = false;
1417
+ let inDoubleQuote = false;
1418
+ let inBacktick = false;
1419
+ let escapeNext = false;
1420
+ for (const char of input) {
1421
+ if (escapeNext) {
1422
+ current += char;
1423
+ escapeNext = false;
1424
+ continue;
1425
+ }
1426
+ if (char === "\\") {
1427
+ current += char;
1428
+ escapeNext = true;
1429
+ continue;
1430
+ }
1431
+ if (char === "'" && !inDoubleQuote && !inBacktick) {
1432
+ inSingleQuote = !inSingleQuote;
1433
+ current += char;
1434
+ continue;
1435
+ }
1436
+ if (char === '"' && !inSingleQuote && !inBacktick) {
1437
+ inDoubleQuote = !inDoubleQuote;
1438
+ current += char;
1439
+ continue;
1440
+ }
1441
+ if (char === "`" && !inSingleQuote && !inDoubleQuote) {
1442
+ inBacktick = !inBacktick;
1443
+ current += char;
1444
+ continue;
1445
+ }
1446
+ if (!inSingleQuote && !inDoubleQuote && !inBacktick) {
1447
+ if (char === "{") {
1448
+ curlyDepth += 1;
1449
+ } else if (char === "}") {
1450
+ curlyDepth = Math.max(0, curlyDepth - 1);
1451
+ } else if (char === "[") {
1452
+ squareDepth += 1;
1453
+ } else if (char === "]") {
1454
+ squareDepth = Math.max(0, squareDepth - 1);
1455
+ } else if (char === "(") {
1456
+ parenDepth += 1;
1457
+ } else if (char === ")") {
1458
+ parenDepth = Math.max(0, parenDepth - 1);
1459
+ }
1460
+ }
1461
+ const atTopLevel = !inSingleQuote && !inDoubleQuote && !inBacktick && curlyDepth === 0 && squareDepth === 0 && parenDepth === 0;
1462
+ if (atTopLevel && /\s/.test(char)) {
1463
+ if (current.trim()) {
1464
+ props.push(current.trim());
1465
+ }
1466
+ current = "";
1467
+ continue;
1468
+ }
1469
+ current += char;
1470
+ }
1471
+ if (current.trim()) {
1472
+ props.push(current.trim());
1473
+ }
1474
+ return props;
1475
+ };
1476
+ var formatJsxCodeSnippet = (input) => {
1477
+ const trimmed = input.trim();
1478
+ if (!trimmed) return "";
1479
+ if (trimmed.includes("\n")) {
1480
+ return trimmed;
1481
+ }
1482
+ if (!trimmed.startsWith("<") || !trimmed.endsWith(">")) {
1483
+ return trimmed;
1484
+ }
1485
+ if (!trimmed.endsWith("/>")) {
1486
+ return trimmed;
1487
+ }
1488
+ const inner = trimmed.slice(1, -2).trim();
1489
+ const firstSpaceIndex = inner.indexOf(" ");
1490
+ if (firstSpaceIndex === -1) {
1491
+ return `<${inner} />`;
1492
+ }
1493
+ const componentName = inner.slice(0, firstSpaceIndex);
1494
+ const propsString = inner.slice(firstSpaceIndex + 1).trim();
1495
+ if (!propsString) {
1496
+ return `<${componentName} />`;
1497
+ }
1498
+ const propsList = splitPropsString(propsString);
1499
+ if (propsList.length === 0) {
1500
+ return `<${componentName} ${propsString} />`;
1501
+ }
1502
+ const formattedProps = propsList.map((prop) => ` ${prop}`).join("\n");
1503
+ return `<${componentName}
1504
+ ${formattedProps}
1505
+ />`;
1506
+ };
1507
+ var isWhitespace = (char) => /\s/.test(char);
1508
+ var isAttrNameChar = (char) => /[A-Za-z0-9_$\-.:]/.test(char);
1509
+ var isAlphaStart = (char) => /[A-Za-z_$]/.test(char);
1510
+ var tokenizeJsx = (input) => {
1511
+ const tokens = [];
1512
+ let i = 0;
1513
+ while (i < input.length) {
1514
+ const char = input[i];
1515
+ if (char === "<") {
1516
+ tokens.push({ type: "punctuation", value: "<" });
1517
+ i += 1;
1518
+ if (input[i] === "/") {
1519
+ tokens.push({ type: "punctuation", value: "/" });
1520
+ i += 1;
1521
+ }
1522
+ const start = i;
1523
+ while (i < input.length && isAttrNameChar(input[i])) {
1524
+ i += 1;
1525
+ }
1526
+ if (i > start) {
1527
+ tokens.push({ type: "tag", value: input.slice(start, i) });
1528
+ }
1529
+ continue;
1530
+ }
1531
+ if (char === "/" && input[i + 1] === ">") {
1532
+ tokens.push({ type: "punctuation", value: "/>" });
1533
+ i += 2;
1534
+ continue;
1535
+ }
1536
+ if (char === ">") {
1537
+ tokens.push({ type: "punctuation", value: ">" });
1538
+ i += 1;
1539
+ continue;
1540
+ }
1541
+ if (char === "=") {
1542
+ tokens.push({ type: "punctuation", value: "=" });
1543
+ i += 1;
1544
+ continue;
1545
+ }
1546
+ if (char === '"' || char === "'" || char === "`") {
1547
+ const quote = char;
1548
+ let j = i + 1;
1549
+ let value = quote;
1550
+ while (j < input.length) {
1551
+ const current = input[j];
1552
+ value += current;
1553
+ if (current === quote && input[j - 1] !== "\\") {
1554
+ break;
1555
+ }
1556
+ j += 1;
1557
+ }
1558
+ tokens.push({ type: "string", value });
1559
+ i = j + 1;
1560
+ continue;
1561
+ }
1562
+ if (char === "{") {
1563
+ let depth = 1;
1564
+ let j = i + 1;
1565
+ while (j < input.length && depth > 0) {
1566
+ if (input[j] === "{") {
1567
+ depth += 1;
1568
+ } else if (input[j] === "}") {
1569
+ depth -= 1;
1570
+ }
1571
+ j += 1;
1572
+ }
1573
+ const expression = input.slice(i, j);
1574
+ tokens.push({ type: "expression", value: expression });
1575
+ i = j;
1576
+ continue;
1577
+ }
1578
+ if (isAlphaStart(char)) {
1579
+ const start = i;
1580
+ i += 1;
1581
+ while (i < input.length && isAttrNameChar(input[i])) {
1582
+ i += 1;
1583
+ }
1584
+ const word = input.slice(start, i);
1585
+ let k = i;
1586
+ while (k < input.length && isWhitespace(input[k])) {
1587
+ k += 1;
1588
+ }
1589
+ if (input[k] === "=") {
1590
+ tokens.push({ type: "attrName", value: word });
1591
+ } else {
1592
+ tokens.push({ type: "plain", value: word });
1593
+ }
1594
+ continue;
1595
+ }
1596
+ tokens.push({ type: "plain", value: char });
1597
+ i += 1;
1598
+ }
1599
+ return tokens;
1600
+ };
1601
+ var TOKEN_CLASS_MAP = {
1602
+ tag: "text-sky-300",
1603
+ attrName: "text-amber-200",
1604
+ string: "text-emerald-300",
1605
+ expression: "text-purple-300",
1606
+ punctuation: "text-stone-400"
1607
+ };
1608
+ var highlightJsx = (input) => {
1609
+ const tokens = tokenizeJsx(input);
1610
+ const nodes = [];
1611
+ tokens.forEach((token, index) => {
1612
+ if (token.type === "plain") {
1613
+ nodes.push(token.value);
1614
+ } else {
1615
+ nodes.push(
1616
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: TOKEN_CLASS_MAP[token.type], children: token.value }, `token-${index}`)
1617
+ );
1618
+ }
1619
+ });
1620
+ return nodes;
1621
+ };
1095
1622
  var ControlPanel = () => {
1096
- const [copied, setCopied] = (0, import_react5.useState)(false);
1097
- const [folderStates, setFolderStates] = (0, import_react5.useState)({});
1623
+ const [copied, setCopied] = (0, import_react6.useState)(false);
1624
+ const [codeCopied, setCodeCopied] = (0, import_react6.useState)(false);
1625
+ const [isCodeVisible, setIsCodeVisible] = (0, import_react6.useState)(false);
1626
+ const [folderStates, setFolderStates] = (0, import_react6.useState)({});
1627
+ const codeCopyTimeoutRef = (0, import_react6.useRef)(null);
1098
1628
  const { leftPanelWidth, isDesktop, isHydrated } = useResizableLayout();
1099
1629
  const { schema, setValue, values, componentName, config } = useControlsContext();
1630
+ const isControlsOnlyView = typeof window !== "undefined" && new URLSearchParams(window.location.search).get(CONTROLS_ONLY_PARAM) === "true";
1100
1631
  const previewUrl = usePreviewUrl(values);
1101
- const buildUrl = (0, import_react5.useCallback)(
1632
+ const buildUrl = (0, import_react6.useCallback)(
1102
1633
  (modifier) => {
1103
1634
  if (!previewUrl) return "";
1104
1635
  const [path, search = ""] = previewUrl.split("?");
@@ -1109,13 +1640,13 @@ var ControlPanel = () => {
1109
1640
  },
1110
1641
  [previewUrl]
1111
1642
  );
1112
- const presentationUrl = (0, import_react5.useMemo)(() => {
1643
+ const presentationUrl = (0, import_react6.useMemo)(() => {
1113
1644
  if (!previewUrl) return "";
1114
1645
  return buildUrl((params) => {
1115
1646
  params.set(PRESENTATION_PARAM, "true");
1116
1647
  });
1117
1648
  }, [buildUrl, previewUrl]);
1118
- const controlsOnlyUrl = (0, import_react5.useMemo)(() => {
1649
+ const controlsOnlyUrl = (0, import_react6.useMemo)(() => {
1119
1650
  if (!previewUrl) return "";
1120
1651
  return buildUrl((params) => {
1121
1652
  params.delete(NO_CONTROLS_PARAM);
@@ -1123,7 +1654,7 @@ var ControlPanel = () => {
1123
1654
  params.set(CONTROLS_ONLY_PARAM, "true");
1124
1655
  });
1125
1656
  }, [buildUrl, previewUrl]);
1126
- const handlePresentationClick = (0, import_react5.useCallback)(() => {
1657
+ const handlePresentationClick = (0, import_react6.useCallback)(() => {
1127
1658
  if (typeof window === "undefined" || !presentationUrl) return;
1128
1659
  window.open(presentationUrl, "_blank", "noopener,noreferrer");
1129
1660
  if (controlsOnlyUrl) {
@@ -1131,10 +1662,7 @@ var ControlPanel = () => {
1131
1662
  const viewportHeight = window.innerHeight || 900;
1132
1663
  const controlsWidth = Math.max(
1133
1664
  320,
1134
- Math.min(
1135
- 600,
1136
- Math.round(viewportWidth * leftPanelWidth / 100)
1137
- )
1665
+ Math.min(600, Math.round(viewportWidth * leftPanelWidth / 100))
1138
1666
  );
1139
1667
  const controlsHeight = Math.max(600, viewportHeight);
1140
1668
  const controlsFeatures = [
@@ -1150,7 +1678,7 @@ var ControlPanel = () => {
1150
1678
  window.open(controlsOnlyUrl, "v0-controls", controlsFeatures);
1151
1679
  }
1152
1680
  }, [controlsOnlyUrl, leftPanelWidth, presentationUrl]);
1153
- const jsx14 = (0, import_react5.useMemo)(() => {
1681
+ const jsx15 = (0, import_react6.useMemo)(() => {
1154
1682
  if (!componentName) return "";
1155
1683
  const props = Object.entries(values).map(([key, val]) => {
1156
1684
  if (typeof val === "string") return `${key}="${val}"`;
@@ -1195,7 +1723,7 @@ var ControlPanel = () => {
1195
1723
  const advancedConfig = config?.addAdvancedPaletteControl;
1196
1724
  let advancedPaletteControlNode = null;
1197
1725
  if (advancedConfig) {
1198
- const advancedNode = /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1726
+ const advancedNode = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1199
1727
  AdvancedPaletteControl_default,
1200
1728
  {
1201
1729
  config: advancedConfig
@@ -1221,12 +1749,52 @@ var ControlPanel = () => {
1221
1749
  advancedPaletteControlNode = advancedNode;
1222
1750
  }
1223
1751
  }
1224
- const rootButtonControls = rootControls.filter(
1225
- ([, control]) => control.type === "button"
1226
- );
1227
- const rootNormalControls = rootControls.filter(
1228
- ([, control]) => control.type !== "button"
1229
- );
1752
+ const mediaUploadConfig = config?.addMediaUploadControl;
1753
+ let mediaUploadControlNode = null;
1754
+ if (mediaUploadConfig) {
1755
+ const mediaUploadNode = /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1756
+ MediaUploadControl,
1757
+ {
1758
+ onSelectMedia: (media) => {
1759
+ mediaUploadConfig.onSelectMedia?.(media);
1760
+ },
1761
+ onClear: () => {
1762
+ mediaUploadConfig.onClear?.();
1763
+ },
1764
+ presetMedia: mediaUploadConfig.presetMedia,
1765
+ maxPresetCount: mediaUploadConfig.maxPresetCount
1766
+ },
1767
+ "mediaUploadControl"
1768
+ );
1769
+ const mediaFolder = mediaUploadConfig.folder?.trim();
1770
+ if (mediaFolder) {
1771
+ const placement = mediaUploadConfig.folderPlacement ?? "bottom";
1772
+ ensureFolder(mediaFolder);
1773
+ if (!folderControls.has(mediaFolder)) {
1774
+ folderControls.set(mediaFolder, []);
1775
+ }
1776
+ const existingPlacement = folderPlacement.get(mediaFolder);
1777
+ if (!existingPlacement || placement === "top") {
1778
+ folderPlacement.set(mediaFolder, placement);
1779
+ }
1780
+ if (!folderExtras.has(mediaFolder)) {
1781
+ folderExtras.set(mediaFolder, []);
1782
+ }
1783
+ folderExtras.get(mediaFolder).push(mediaUploadNode);
1784
+ } else {
1785
+ mediaUploadControlNode = mediaUploadNode;
1786
+ }
1787
+ }
1788
+ const rootButtonControls = [];
1789
+ const rootNormalControls = [];
1790
+ rootControls.forEach((entry) => {
1791
+ const [key, control] = entry;
1792
+ if (control.type === "button") {
1793
+ rootButtonControls.push([key, control]);
1794
+ } else {
1795
+ rootNormalControls.push(entry);
1796
+ }
1797
+ });
1230
1798
  const folderGroups = folderOrder.map((folder) => ({
1231
1799
  folder,
1232
1800
  entries: folderControls.get(folder) ?? [],
@@ -1235,7 +1803,7 @@ var ControlPanel = () => {
1235
1803
  })).filter((group) => group.entries.length > 0 || group.extras.length > 0);
1236
1804
  const hasRootButtonControls = rootButtonControls.length > 0;
1237
1805
  const hasAnyFolders = folderGroups.length > 0;
1238
- const jsonToComponentString = (0, import_react5.useCallback)(
1806
+ const jsonToComponentString = (0, import_react6.useCallback)(
1239
1807
  ({
1240
1808
  componentName: componentNameOverride,
1241
1809
  props
@@ -1266,16 +1834,75 @@ var ControlPanel = () => {
1266
1834
  componentName,
1267
1835
  values,
1268
1836
  schema,
1269
- jsx: jsx14,
1837
+ jsx: jsx15,
1270
1838
  jsonToComponentString
1271
- }) ?? jsx14;
1839
+ }) ?? jsx15;
1272
1840
  const shouldShowCopyButton = config?.showCopyButton !== false && Boolean(copyText);
1841
+ const baseSnippet = copyText || jsx15;
1842
+ const formattedCode = (0, import_react6.useMemo)(
1843
+ () => formatJsxCodeSnippet(baseSnippet),
1844
+ [baseSnippet]
1845
+ );
1846
+ const hasCodeSnippet = Boolean(config?.showCodeSnippet && formattedCode);
1847
+ const highlightedCode = (0, import_react6.useMemo)(
1848
+ () => formattedCode ? highlightJsx(formattedCode) : null,
1849
+ [formattedCode]
1850
+ );
1851
+ (0, import_react6.useEffect)(() => {
1852
+ if (!hasCodeSnippet) {
1853
+ setIsCodeVisible(false);
1854
+ }
1855
+ }, [hasCodeSnippet]);
1856
+ (0, import_react6.useEffect)(() => {
1857
+ setCodeCopied(false);
1858
+ if (codeCopyTimeoutRef.current) {
1859
+ clearTimeout(codeCopyTimeoutRef.current);
1860
+ codeCopyTimeoutRef.current = null;
1861
+ }
1862
+ }, [formattedCode]);
1863
+ (0, import_react6.useEffect)(() => {
1864
+ return () => {
1865
+ if (codeCopyTimeoutRef.current) {
1866
+ clearTimeout(codeCopyTimeoutRef.current);
1867
+ }
1868
+ };
1869
+ }, []);
1870
+ const handleToggleCodeVisibility = (0, import_react6.useCallback)(() => {
1871
+ setIsCodeVisible((prev) => {
1872
+ const next = !prev;
1873
+ if (!next) {
1874
+ setCodeCopied(false);
1875
+ if (codeCopyTimeoutRef.current) {
1876
+ clearTimeout(codeCopyTimeoutRef.current);
1877
+ codeCopyTimeoutRef.current = null;
1878
+ }
1879
+ }
1880
+ return next;
1881
+ });
1882
+ }, []);
1883
+ const handleCodeCopy = (0, import_react6.useCallback)(() => {
1884
+ if (!formattedCode) return;
1885
+ if (typeof navigator === "undefined" || !navigator.clipboard || typeof navigator.clipboard.writeText !== "function") {
1886
+ return;
1887
+ }
1888
+ navigator.clipboard.writeText(formattedCode).then(() => {
1889
+ setCodeCopied(true);
1890
+ if (codeCopyTimeoutRef.current) {
1891
+ clearTimeout(codeCopyTimeoutRef.current);
1892
+ }
1893
+ codeCopyTimeoutRef.current = setTimeout(() => {
1894
+ setCodeCopied(false);
1895
+ codeCopyTimeoutRef.current = null;
1896
+ }, 3e3);
1897
+ }).catch(() => {
1898
+ });
1899
+ }, [formattedCode]);
1273
1900
  const labelize = (key) => key.replace(/([A-Z])/g, " $1").replace(/[\-_]/g, " ").replace(/\s+/g, " ").trim().replace(/(^|\s)\S/g, (s) => s.toUpperCase());
1274
- const renderButtonControl = (key, control, variant) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1901
+ const renderButtonControl = (key, control, variant) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1275
1902
  "div",
1276
1903
  {
1277
1904
  className: variant === "root" ? "flex-1 [&_[data-slot=button]]:w-full" : "[&_[data-slot=button]]:w-full",
1278
- children: control.render ? control.render() : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1905
+ children: control.render ? control.render() : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1279
1906
  "button",
1280
1907
  {
1281
1908
  onClick: control.onClick,
@@ -1293,9 +1920,9 @@ var ControlPanel = () => {
1293
1920
  const value = values[key];
1294
1921
  switch (control.type) {
1295
1922
  case "boolean":
1296
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center justify-between", children: [
1297
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Label, { htmlFor: key, className: "cursor-pointer", children: labelize(key) }),
1298
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1923
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center justify-between", children: [
1924
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Label, { htmlFor: key, className: "cursor-pointer", children: labelize(key) }),
1925
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1299
1926
  Switch,
1300
1927
  {
1301
1928
  id: key,
@@ -1306,10 +1933,10 @@ var ControlPanel = () => {
1306
1933
  )
1307
1934
  ] }, key);
1308
1935
  case "number":
1309
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-3 w-full", children: [
1310
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center justify-between", children: [
1311
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
1312
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1936
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "space-y-3 w-full", children: [
1937
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center justify-between", children: [
1938
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
1939
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1313
1940
  Input,
1314
1941
  {
1315
1942
  type: "number",
@@ -1326,7 +1953,7 @@ var ControlPanel = () => {
1326
1953
  }
1327
1954
  )
1328
1955
  ] }),
1329
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1956
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1330
1957
  Slider,
1331
1958
  {
1332
1959
  id: key,
@@ -1340,9 +1967,9 @@ var ControlPanel = () => {
1340
1967
  )
1341
1968
  ] }, key);
1342
1969
  case "string":
1343
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-2 w-full", children: [
1344
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
1345
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1970
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "space-y-2 w-full", children: [
1971
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
1972
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1346
1973
  Input,
1347
1974
  {
1348
1975
  id: key,
@@ -1354,9 +1981,9 @@ var ControlPanel = () => {
1354
1981
  )
1355
1982
  ] }, key);
1356
1983
  case "color":
1357
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-2 w-full", children: [
1358
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
1359
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1984
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "space-y-2 w-full", children: [
1985
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Label, { className: "text-stone-300", htmlFor: key, children: labelize(key) }),
1986
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1360
1987
  "input",
1361
1988
  {
1362
1989
  type: "color",
@@ -1368,11 +1995,11 @@ var ControlPanel = () => {
1368
1995
  )
1369
1996
  ] }, key);
1370
1997
  case "select":
1371
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "space-y-2", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-3", children: [
1372
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Label, { className: "min-w-fit", htmlFor: key, children: labelize(key) }),
1373
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Select, { value, onValueChange: (val) => setValue(key, val), children: [
1374
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SelectTrigger, { className: "flex-1 cursor-pointer", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SelectValue, { placeholder: "Select option" }) }),
1375
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(SelectContent, { className: "cursor-pointer z-[9999]", children: Object.entries(control.options).map(([label]) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1998
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "space-y-2", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-3", children: [
1999
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Label, { className: "min-w-fit", htmlFor: key, children: labelize(key) }),
2000
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Select, { value, onValueChange: (val) => setValue(key, val), children: [
2001
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SelectTrigger, { className: "flex-1 cursor-pointer", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SelectValue, { placeholder: "Select option" }) }),
2002
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(SelectContent, { className: "cursor-pointer z-[9999]", children: Object.entries(control.options).map(([label]) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1376
2003
  SelectItem,
1377
2004
  {
1378
2005
  value: label,
@@ -1389,12 +2016,12 @@ var ControlPanel = () => {
1389
2016
  };
1390
2017
  const renderFolder = (folder, entries, extras = []) => {
1391
2018
  const isOpen = folderStates[folder] ?? true;
1392
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2019
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1393
2020
  "div",
1394
2021
  {
1395
2022
  className: "border border-stone-700/60 rounded-lg bg-stone-900/70",
1396
2023
  children: [
1397
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2024
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1398
2025
  "button",
1399
2026
  {
1400
2027
  type: "button",
@@ -1404,9 +2031,9 @@ var ControlPanel = () => {
1404
2031
  })),
1405
2032
  className: "w-full flex items-center justify-between px-4 py-3 text-left font-semibold text-stone-200 tracking-wide",
1406
2033
  children: [
1407
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: folder }),
1408
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1409
- import_lucide_react3.ChevronDown,
2034
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: folder }),
2035
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2036
+ import_lucide_react4.ChevronDown,
1410
2037
  {
1411
2038
  className: `w-4 h-4 transition-transform duration-200 ${isOpen ? "rotate-180" : ""}`
1412
2039
  }
@@ -1414,7 +2041,7 @@ var ControlPanel = () => {
1414
2041
  ]
1415
2042
  }
1416
2043
  ),
1417
- isOpen && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "px-4 pb-4 pt-0 space-y-5", children: [
2044
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "px-4 pb-4 pt-0 space-y-5", children: [
1418
2045
  entries.map(
1419
2046
  ([key, control]) => renderControl(key, control, "folder")
1420
2047
  ),
@@ -1436,7 +2063,7 @@ var ControlPanel = () => {
1436
2063
  height: "auto",
1437
2064
  flex: "0 0 auto"
1438
2065
  };
1439
- if (isHydrated) {
2066
+ if (isHydrated && !isControlsOnlyView) {
1440
2067
  if (isDesktop) {
1441
2068
  Object.assign(panelStyle, {
1442
2069
  position: "absolute",
@@ -1453,47 +2080,87 @@ var ControlPanel = () => {
1453
2080
  });
1454
2081
  }
1455
2082
  }
1456
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2083
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1457
2084
  "div",
1458
2085
  {
1459
2086
  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"}`,
1460
2087
  onPointerDown: (e) => e.stopPropagation(),
1461
2088
  onTouchStart: (e) => e.stopPropagation(),
1462
2089
  style: panelStyle,
1463
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("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: [
1464
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "space-y-1", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { className: "text-lg text-stone-100 font-semibold", children: config?.mainLabel ?? "Controls" }) }),
1465
- /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "space-y-6", children: [
2090
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("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: [
2091
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "space-y-1", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h1", { className: "text-lg text-stone-100 font-semibold", children: config?.mainLabel ?? "Controls" }) }),
2092
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "space-y-6", children: [
1466
2093
  topFolderSections,
1467
- hasRootButtonControls && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-wrap gap-2", children: rootButtonControls.map(
2094
+ hasRootButtonControls && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "flex flex-wrap gap-2", children: rootButtonControls.map(
1468
2095
  ([key, control]) => renderButtonControl(key, control, "root")
1469
2096
  ) }),
1470
2097
  advancedPaletteControlNode,
2098
+ mediaUploadControlNode,
1471
2099
  rootNormalControls.map(
1472
2100
  ([key, control]) => renderControl(key, control, "root")
1473
2101
  ),
1474
2102
  bottomFolderSections,
1475
- shouldShowCopyButton && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2103
+ hasCodeSnippet && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "border border-stone-700/60 rounded-lg bg-stone-900/70", children: [
2104
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
2105
+ "button",
2106
+ {
2107
+ type: "button",
2108
+ onClick: handleToggleCodeVisibility,
2109
+ className: "w-full flex items-center justify-between px-4 py-3 text-left font-semibold text-stone-200 tracking-wide",
2110
+ "aria-expanded": isCodeVisible,
2111
+ children: [
2112
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { children: isCodeVisible ? "Hide Code" : "Show Code" }),
2113
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2114
+ import_lucide_react4.ChevronDown,
2115
+ {
2116
+ className: `w-4 h-4 transition-transform duration-200 ${isCodeVisible ? "rotate-180" : ""}`
2117
+ }
2118
+ )
2119
+ ]
2120
+ }
2121
+ ),
2122
+ isCodeVisible && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "relative border-t border-stone-700/60 bg-stone-950/60 px-4 py-4 rounded-b-lg", children: [
2123
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2124
+ "button",
2125
+ {
2126
+ type: "button",
2127
+ onClick: handleCodeCopy,
2128
+ 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",
2129
+ children: codeCopied ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2130
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Check, { className: "h-3.5 w-3.5" }),
2131
+ "Copied"
2132
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2133
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Copy, { className: "h-3.5 w-3.5" }),
2134
+ "Copy"
2135
+ ] })
2136
+ }
2137
+ ),
2138
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("pre", { className: "whitespace-pre overflow-x-auto text-xs md:text-sm text-stone-200 pr-14", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("code", { className: "block text-stone-200", children: highlightedCode ?? formattedCode }) })
2139
+ ] })
2140
+ ] }),
2141
+ shouldShowCopyButton && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "flex-1 pt-4", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1476
2142
  "button",
1477
2143
  {
1478
2144
  onClick: () => {
1479
- if (!copyText) return;
1480
- navigator.clipboard.writeText(copyText);
2145
+ const copyPayload = formattedCode || baseSnippet;
2146
+ if (!copyPayload) return;
2147
+ navigator.clipboard.writeText(copyPayload);
1481
2148
  setCopied(true);
1482
2149
  setTimeout(() => setCopied(false), 5e3);
1483
2150
  },
1484
2151
  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",
1485
- children: copied ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1486
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react3.Check, { className: "w-4 h-4" }),
2152
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2153
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Check, { className: "w-4 h-4" }),
1487
2154
  "Copied"
1488
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1489
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react3.Copy, { className: "w-4 h-4" }),
2155
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2156
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Copy, { className: "w-4 h-4" }),
1490
2157
  "Copy to Clipboard"
1491
2158
  ] })
1492
2159
  }
1493
2160
  ) }, "control-panel-jsx")
1494
2161
  ] }),
1495
- previewUrl && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-2", children: [
1496
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Button, { asChild: true, className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2162
+ previewUrl && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-col gap-2", children: [
2163
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Button, { asChild: true, className: "w-full", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1497
2164
  "a",
1498
2165
  {
1499
2166
  href: previewUrl,
@@ -1501,12 +2168,12 @@ var ControlPanel = () => {
1501
2168
  rel: "noopener noreferrer",
1502
2169
  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",
1503
2170
  children: [
1504
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react3.SquareArrowOutUpRight, {}),
2171
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.SquareArrowOutUpRight, {}),
1505
2172
  " Open in a New Tab"
1506
2173
  ]
1507
2174
  }
1508
2175
  ) }),
1509
- config?.showPresentationButton && presentationUrl && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2176
+ config?.showPresentationButton && presentationUrl && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1510
2177
  Button,
1511
2178
  {
1512
2179
  type: "button",
@@ -1514,7 +2181,7 @@ var ControlPanel = () => {
1514
2181
  variant: "secondary",
1515
2182
  className: "w-full bg-stone-800 text-white hover:bg-stone-700 border border-stone-700",
1516
2183
  children: [
1517
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react3.Presentation, {}),
2184
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react4.Presentation, {}),
1518
2185
  " Presentation Mode"
1519
2186
  ]
1520
2187
  }
@@ -1526,16 +2193,16 @@ var ControlPanel = () => {
1526
2193
  };
1527
2194
  var ControlPanel_default = ControlPanel;
1528
2195
 
1529
- // src/components/PreviewContainer/PreviewContainer.tsx
1530
- var import_react6 = require("react");
2196
+ // src/components/PreviewContainer.tsx
2197
+ var import_react7 = require("react");
1531
2198
 
1532
- // src/components/Grid/Grid.tsx
1533
- var import_jsx_runtime11 = require("react/jsx-runtime");
2199
+ // src/components/Grid.tsx
2200
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1534
2201
  function Grid() {
1535
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2202
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1536
2203
  "div",
1537
2204
  {
1538
- className: "absolute inset-0 w-screen h-screen z-[0] blur-[1px]",
2205
+ className: "absolute inset-0 w-full h-full z-[0] blur-[1px]",
1539
2206
  style: {
1540
2207
  backgroundImage: `
1541
2208
  linear-gradient(to right,rgb(13, 13, 13) 1px, transparent 1px),
@@ -1549,40 +2216,40 @@ function Grid() {
1549
2216
  }
1550
2217
  var Grid_default = Grid;
1551
2218
 
1552
- // src/components/PreviewContainer/PreviewContainer.tsx
1553
- var import_jsx_runtime12 = require("react/jsx-runtime");
2219
+ // src/components/PreviewContainer.tsx
2220
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1554
2221
  var PreviewContainer = ({ children, hideControls }) => {
1555
2222
  const { config } = useControlsContext();
1556
2223
  const { leftPanelWidth, isDesktop, isHydrated, containerRef } = useResizableLayout();
1557
- const previewRef = (0, import_react6.useRef)(null);
1558
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2224
+ const previewRef = (0, import_react7.useRef)(null);
2225
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1559
2226
  "div",
1560
2227
  {
1561
2228
  ref: previewRef,
1562
- className: "order-1 md:order-2 flex-1 bg-black overflow-auto flex items-center justify-center relative",
2229
+ className: "order-1 md:order-2 flex-1 md:flex-none bg-black overflow-auto flex items-center justify-center relative",
1563
2230
  style: isHydrated && isDesktop && !hideControls ? {
1564
2231
  width: `${100 - leftPanelWidth}%`,
1565
2232
  marginLeft: `${leftPanelWidth}%`
1566
2233
  } : {},
1567
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "w-screen h-screen", children: [
1568
- config?.showGrid && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Grid_default, {}),
1569
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "w-screen h-screen flex items-center justify-center relative", children })
2234
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "w-full h-screen", children: [
2235
+ config?.showGrid && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Grid_default, {}),
2236
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "w-full h-full flex items-center justify-center relative", children })
1570
2237
  ] })
1571
2238
  }
1572
2239
  );
1573
2240
  };
1574
2241
  var PreviewContainer_default = PreviewContainer;
1575
2242
 
1576
- // src/components/Playground/Playground.tsx
1577
- var import_jsx_runtime13 = require("react/jsx-runtime");
1578
- var HiddenPreview = ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { "aria-hidden": "true", className: "hidden", children });
2243
+ // src/components/Playground.tsx
2244
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2245
+ var HiddenPreview = ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { "aria-hidden": "true", className: "hidden", children });
1579
2246
  function Playground({ children }) {
1580
- const [isHydrated, setIsHydrated] = (0, import_react7.useState)(false);
1581
- const [copied, setCopied] = (0, import_react7.useState)(false);
1582
- (0, import_react7.useEffect)(() => {
2247
+ const [isHydrated, setIsHydrated] = (0, import_react8.useState)(false);
2248
+ const [copied, setCopied] = (0, import_react8.useState)(false);
2249
+ (0, import_react8.useEffect)(() => {
1583
2250
  setIsHydrated(true);
1584
2251
  }, []);
1585
- const { showControls, isPresentationMode, isControlsOnly } = (0, import_react7.useMemo)(() => {
2252
+ const { showControls, isPresentationMode, isControlsOnly } = (0, import_react8.useMemo)(() => {
1586
2253
  if (typeof window === "undefined") {
1587
2254
  return {
1588
2255
  showControls: true,
@@ -1609,54 +2276,54 @@ function Playground({ children }) {
1609
2276
  setTimeout(() => setCopied(false), 2e3);
1610
2277
  };
1611
2278
  if (!isHydrated) return null;
1612
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ResizableLayout, { hideControls: layoutHideControls, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(ControlsProvider, { children: [
1613
- shouldShowShareButton && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2279
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ResizableLayout, { hideControls: layoutHideControls, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(ControlsProvider, { children: [
2280
+ shouldShowShareButton && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1614
2281
  "button",
1615
2282
  {
1616
2283
  onClick: handleCopy,
1617
2284
  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",
1618
2285
  children: [
1619
- copied ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Check, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_lucide_react4.Copy, { size: 16 }),
2286
+ copied ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react5.Check, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_lucide_react5.Copy, { size: 16 }),
1620
2287
  copied ? "Copied!" : "Share"
1621
2288
  ]
1622
2289
  }
1623
2290
  ),
1624
- isControlsOnly ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(HiddenPreview, { children }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PreviewContainer_default, { hideControls: layoutHideControls, children }),
1625
- showControls && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ControlPanel_default, {})
2291
+ isControlsOnly ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(HiddenPreview, { children }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PreviewContainer_default, { hideControls: layoutHideControls, children }),
2292
+ showControls && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ControlPanel_default, {})
1626
2293
  ] }) });
1627
2294
  }
1628
2295
 
1629
2296
  // src/hooks/useAdvancedPaletteControls.ts
1630
- var import_react8 = require("react");
2297
+ var import_react9 = require("react");
1631
2298
  var cloneForCallbacks = (palette) => clonePalette(palette);
1632
2299
  var useAdvancedPaletteControls = (options = {}) => {
1633
- const resolvedDefaultPalette = (0, import_react8.useMemo)(
2300
+ const resolvedDefaultPalette = (0, import_react9.useMemo)(
1634
2301
  () => createAdvancedPalette(options.defaultPalette),
1635
2302
  [options.defaultPalette]
1636
2303
  );
1637
- const resolvedFallbackPalette = (0, import_react8.useMemo)(
2304
+ const resolvedFallbackPalette = (0, import_react9.useMemo)(
1638
2305
  () => options.fallbackPalette ? createAdvancedPalette(options.fallbackPalette) : resolvedDefaultPalette,
1639
2306
  [options.fallbackPalette, resolvedDefaultPalette]
1640
2307
  );
1641
- const [palette, setPaletteState] = (0, import_react8.useState)(
2308
+ const [palette, setPaletteState] = (0, import_react9.useState)(
1642
2309
  () => clonePalette(resolvedDefaultPalette)
1643
2310
  );
1644
- const defaultSignatureRef = (0, import_react8.useRef)(
2311
+ const defaultSignatureRef = (0, import_react9.useRef)(
1645
2312
  createPaletteSignature(resolvedDefaultPalette)
1646
2313
  );
1647
- (0, import_react8.useEffect)(() => {
2314
+ (0, import_react9.useEffect)(() => {
1648
2315
  const nextSignature = createPaletteSignature(resolvedDefaultPalette);
1649
2316
  if (defaultSignatureRef.current === nextSignature) return;
1650
2317
  defaultSignatureRef.current = nextSignature;
1651
2318
  setPaletteState(clonePalette(resolvedDefaultPalette));
1652
2319
  }, [resolvedDefaultPalette]);
1653
- const notifyChange = (0, import_react8.useCallback)(
2320
+ const notifyChange = (0, import_react9.useCallback)(
1654
2321
  (nextPalette) => {
1655
2322
  options.onChange?.(cloneForCallbacks(nextPalette));
1656
2323
  },
1657
2324
  [options.onChange]
1658
2325
  );
1659
- const setPalette = (0, import_react8.useCallback)(
2326
+ const setPalette = (0, import_react9.useCallback)(
1660
2327
  (source) => {
1661
2328
  const nextPalette = createAdvancedPalette(
1662
2329
  source ?? resolvedDefaultPalette
@@ -1666,7 +2333,7 @@ var useAdvancedPaletteControls = (options = {}) => {
1666
2333
  },
1667
2334
  [notifyChange, resolvedDefaultPalette]
1668
2335
  );
1669
- const updatePalette = (0, import_react8.useCallback)(
2336
+ const updatePalette = (0, import_react9.useCallback)(
1670
2337
  (updater) => {
1671
2338
  setPaletteState((current) => {
1672
2339
  const nextSource = updater(clonePalette(current));
@@ -1679,18 +2346,18 @@ var useAdvancedPaletteControls = (options = {}) => {
1679
2346
  },
1680
2347
  [notifyChange, resolvedDefaultPalette]
1681
2348
  );
1682
- const resetPalette = (0, import_react8.useCallback)(() => {
2349
+ const resetPalette = (0, import_react9.useCallback)(() => {
1683
2350
  setPaletteState(clonePalette(resolvedDefaultPalette));
1684
2351
  notifyChange(resolvedDefaultPalette);
1685
2352
  }, [notifyChange, resolvedDefaultPalette]);
1686
- const handleControlPaletteChange = (0, import_react8.useCallback)(
2353
+ const handleControlPaletteChange = (0, import_react9.useCallback)(
1687
2354
  (nextPalette) => {
1688
2355
  setPaletteState(clonePalette(nextPalette));
1689
2356
  notifyChange(nextPalette);
1690
2357
  },
1691
2358
  [notifyChange]
1692
2359
  );
1693
- const controlConfig = (0, import_react8.useMemo)(
2360
+ const controlConfig = (0, import_react9.useMemo)(
1694
2361
  () => ({
1695
2362
  ...options.control ?? {},
1696
2363
  defaultPalette: resolvedDefaultPalette,
@@ -1698,7 +2365,7 @@ var useAdvancedPaletteControls = (options = {}) => {
1698
2365
  }),
1699
2366
  [handleControlPaletteChange, options.control, resolvedDefaultPalette]
1700
2367
  );
1701
- const hexColors = (0, import_react8.useMemo)(
2368
+ const hexColors = (0, import_react9.useMemo)(
1702
2369
  () => advancedPaletteToHexColors(palette, {
1703
2370
  sectionOrder: options.sectionOrder,
1704
2371
  fallbackPalette: resolvedFallbackPalette,
@@ -1711,11 +2378,11 @@ var useAdvancedPaletteControls = (options = {}) => {
1711
2378
  resolvedFallbackPalette
1712
2379
  ]
1713
2380
  );
1714
- const paletteSignature = (0, import_react8.useMemo)(
2381
+ const paletteSignature = (0, import_react9.useMemo)(
1715
2382
  () => createPaletteSignature(palette),
1716
2383
  [palette]
1717
2384
  );
1718
- const paletteGradient = (0, import_react8.useMemo)(
2385
+ const paletteGradient = (0, import_react9.useMemo)(
1719
2386
  () => computePaletteGradient(palette, options.gradientSteps),
1720
2387
  [options.gradientSteps, palette]
1721
2388
  );