@kyro-cms/admin 0.9.6 → 0.9.7

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.
@@ -292,57 +292,32 @@ export function DetailView({
292
292
  ]}
293
293
  />
294
294
 
295
- {isSingleLayout ? (
296
- <div className="md:hidden">
297
- <ActionBar
298
- status={status}
299
- saveStatus={saveStatus}
300
- hasChanges={hasChanges}
301
- onSave={() => handleSave(false)}
302
- onPublish={handlePublish}
303
- onUnpublish={status === "published" ? handleUnpublish : undefined}
304
- onDuplicate={handleDuplicate}
305
- onViewHistory={() => {
306
- window.dispatchEvent(new CustomEvent('kyro:show-version-history'));
307
- }}
308
- onPreview={() =>
309
- window.open(`/preview/${slug}/${documentId}`, "_blank")
310
- }
311
- onDelete={handleDeleteTrigger}
312
- onBack={onBack}
313
- onToggleSidebar={() => window.dispatchEvent(new CustomEvent("toggle-sidebar"))}
314
- publishedAt={publishedAt}
315
- updatedAt={updatedAt}
316
- />
317
- </div>
318
- ) : (
319
- <ActionBar
320
- status={status}
321
- saveStatus={saveStatus}
322
- hasChanges={hasChanges}
323
- onSave={() => handleSave(false)}
324
- onPublish={handlePublish}
325
- onUnpublish={status === "published" ? handleUnpublish : undefined}
326
- onDuplicate={handleDuplicate}
327
- onViewHistory={() => {
328
- window.dispatchEvent(new CustomEvent('kyro:show-version-history'));
329
- }}
330
- onPreview={() =>
331
- window.open(`/preview/${slug}/${documentId}`, "_blank")
332
- }
333
- onDelete={handleDeleteTrigger}
334
- onBack={onBack}
335
- onToggleSidebar={() => window.dispatchEvent(new CustomEvent("toggle-sidebar"))}
336
- publishedAt={publishedAt}
337
- updatedAt={updatedAt}
338
- />
339
- )}
295
+ <ActionBar
296
+ status={status}
297
+ saveStatus={saveStatus}
298
+ hasChanges={hasChanges}
299
+ onSave={() => handleSave(false)}
300
+ onPublish={handlePublish}
301
+ onUnpublish={status === "published" ? handleUnpublish : undefined}
302
+ onDuplicate={handleDuplicate}
303
+ onViewHistory={() => {
304
+ window.dispatchEvent(new CustomEvent('kyro:show-version-history'));
305
+ }}
306
+ onPreview={() =>
307
+ window.open(`/preview/${slug}/${documentId}`, "_blank")
308
+ }
309
+ onDelete={handleDeleteTrigger}
310
+ onBack={onBack}
311
+ onToggleSidebar={() => window.dispatchEvent(new CustomEvent("toggle-sidebar"))}
312
+ publishedAt={publishedAt}
313
+ updatedAt={updatedAt}
314
+ />
340
315
 
341
316
  <div
342
317
  className={
343
318
  isSingleLayout
344
- ? "w-full pb-32 pt-4 md:pt-8"
345
- : "w-full mx-auto grid grid-cols-1 lg:grid-cols-[1fr_360px] gap-4 md:gap-8 pt-4 md:pt-0 pb-32"
319
+ ? "w-full pt-4 md:pt-8"
320
+ : "w-full mx-auto grid grid-cols-1 lg:grid-cols-[1fr_360px] gap-4 md:gap-8 pt-4 md:pt-0"
346
321
  }
347
322
  >
348
323
  <div className="space-y-4 md:space-y-8 min-w-0">
@@ -161,6 +161,7 @@ export function GraphQLPlayground({
161
161
  const [lastStatus, setLastStatus] = useState<number>(0);
162
162
  const [copied, setCopied] = useState(false);
163
163
  const [splitPos, setSplitPos] = useState(50);
164
+ const [mobilePanel, setMobilePanel] = useState<"editor" | "response">("editor");
164
165
  const [isDragging, setIsDragging] = useState(false);
165
166
  const containerRef = useRef<HTMLDivElement>(null);
166
167
  const cursorPos = useRef({ line: 1, col: 1 });
@@ -393,7 +394,7 @@ export function GraphQLPlayground({
393
394
  return (
394
395
  <div ref={containerRef} className="h-full flex flex-col bg-[var(--kyro-bg)] overflow-hidden rounded-lg border border-[var(--kyro-border)]">
395
396
  {/* Compact top bar */}
396
- <div className="flex items-center gap-2 px-3 py-2 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface)] shrink-0">
397
+ <div className="flex flex-wrap items-center gap-x-2 gap-y-1 px-3 py-2 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface)] shrink-0">
397
398
  <div className="flex items-center gap-1.5 min-w-0">
398
399
  <div className="w-7 h-7 rounded-lg bg-[var(--kyro-primary)]/10 flex items-center justify-center text-[var(--kyro-primary)] shrink-0">
399
400
  <Zap className="w-3.5 h-3.5" />
@@ -427,7 +428,7 @@ export function GraphQLPlayground({
427
428
  </button>
428
429
  </div>
429
430
  )}
430
- <div className="ml-auto flex items-center gap-1">
431
+ <div className="ml-auto flex items-center gap-1 flex-wrap justify-end">
431
432
  <button onClick={() => { setShowDocs(!showDocs); setRightTab("docs"); }} className={`p-1.5 rounded-lg transition-all ${rightTab === "docs" && showDocs ? "bg-[var(--kyro-primary)] text-white" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`} title="Schema docs">
432
433
  <Book className="w-3.5 h-3.5" />
433
434
  </button>
@@ -459,20 +460,35 @@ export function GraphQLPlayground({
459
460
  </div>
460
461
 
461
462
  {/* Main split area */}
462
- <div className="flex-1 flex overflow-hidden relative">
463
+ <div className="flex-1 flex flex-col md:flex-row overflow-hidden relative">
464
+ {/* Mobile panel switcher */}
465
+ <div className="flex md:hidden gap-1 px-3 py-1.5 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface)] shrink-0">
466
+ <button
467
+ onClick={() => setMobilePanel("editor")}
468
+ className={`flex-1 px-3 py-1.5 text-[10px] font-semibold rounded-md transition-all ${mobilePanel === "editor" ? "bg-[var(--kyro-primary)] text-white" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`}
469
+ >
470
+ Editor
471
+ </button>
472
+ <button
473
+ onClick={() => setMobilePanel("response")}
474
+ className={`flex-1 px-3 py-1.5 text-[10px] font-semibold rounded-md transition-all ${mobilePanel === "response" ? "bg-[var(--kyro-primary)] text-white" : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"}`}
475
+ >
476
+ Output
477
+ </button>
478
+ </div>
479
+
463
480
  {/* Left: editor */}
464
- <div className="flex flex-col overflow-hidden border-r border-[var(--kyro-border)]" style={{ width: `${splitPos}%` }}>
481
+ <div className={`${mobilePanel === "editor" ? "flex" : "hidden"} md:flex flex-col overflow-hidden border-r border-[var(--kyro-border)] w-full md:w-auto`} style={{ flex: 'none', width: typeof window !== "undefined" && window.innerWidth >= 768 ? `${splitPos}%` : "100%" }}>
465
482
  {/* Editor pills */}
466
483
  <div className="flex gap-0.5 px-3 py-1.5 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface)]">
467
484
  {editorPills.map((p) => (
468
485
  <button
469
486
  key={p.key}
470
487
  onClick={() => setActiveEditorTab(p.key)}
471
- className={`px-2.5 py-1 text-[10px] font-semibold rounded-md transition-all ${
472
- activeEditorTab === p.key
488
+ className={`px-2.5 py-1 text-[10px] font-semibold rounded-md transition-all ${activeEditorTab === p.key
473
489
  ? "bg-[var(--kyro-primary)] text-white"
474
490
  : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"
475
- }`}
491
+ }`}
476
492
  >
477
493
  {p.label}
478
494
  </button>
@@ -509,7 +525,7 @@ export function GraphQLPlayground({
509
525
 
510
526
  {/* Drag handle */}
511
527
  <div
512
- className="absolute top-0 bottom-0 z-10 w-1.5 cursor-col-resize group"
528
+ className="hidden md:block absolute top-0 bottom-0 z-10 w-1.5 cursor-col-resize group"
513
529
  style={{ left: `calc(${splitPos}% - 3px)` }}
514
530
  onMouseDown={startDrag}
515
531
  >
@@ -517,18 +533,17 @@ export function GraphQLPlayground({
517
533
  </div>
518
534
 
519
535
  {/* Right panel */}
520
- <div className="flex-1 flex flex-col overflow-hidden min-w-0" style={{ width: `${100 - splitPos}%` }}>
536
+ <div className={`${mobilePanel === "response" ? "flex" : "hidden"} md:flex flex-1 flex-col overflow-hidden min-w-0 w-full md:w-auto`} style={{ flex: undefined, width: typeof window !== "undefined" && window.innerWidth >= 768 ? `${100 - splitPos}%` : "100%" }}>
521
537
  {/* Right pills */}
522
538
  <div className="flex gap-0.5 px-3 py-1.5 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface)]">
523
539
  {rightPills.map((p) => (
524
540
  <button
525
541
  key={p.key}
526
542
  onClick={() => { setRightTab(p.key); if (p.key === "docs") setShowDocs(true); }}
527
- className={`px-2.5 py-1 text-[10px] font-semibold rounded-md transition-all ${
528
- rightTab === p.key
543
+ className={`px-2.5 py-1 text-[10px] font-semibold rounded-md transition-all ${rightTab === p.key
529
544
  ? "bg-[var(--kyro-primary)] text-white"
530
545
  : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"
531
- }`}
546
+ }`}
532
547
  >
533
548
  {p.label}
534
549
  </button>
@@ -601,7 +616,7 @@ export function GraphQLPlayground({
601
616
  <button
602
617
  key={t}
603
618
  onClick={() => setSelectedType(found)}
604
- className="flex items-center justify-between p-3 bg-[var(--kyro-surface-accent)] rounded-xl border border-[var(--kyro-border)] hover:border-[var(--kyro-primary)] transition-all text-left group"
619
+ className="flex items-center justify-between p-3 bg-[var(--kyro-surface-accent)] rounded-md border border-[var(--kyro-border)] hover:border-[var(--kyro-primary)] transition-all text-left group"
605
620
  >
606
621
  <div>
607
622
  <span className="text-[10px] font-semibold text-[var(--kyro-text-muted)] block">{t}</span>
@@ -684,9 +699,8 @@ export function GraphQLPlayground({
684
699
  </span>
685
700
  )}
686
701
  {lastStatus > 0 && (
687
- <span className={`text-[9px] font-semibold px-1.5 py-0.5 rounded ${
688
- lastStatus < 400 ? "bg-[var(--kyro-success-bg)] text-[var(--kyro-success)]" : "bg-[var(--kyro-danger-bg)] text-[var(--kyro-danger)]"
689
- }`}>
702
+ <span className={`text-[9px] font-semibold px-1.5 py-0.5 rounded ${lastStatus < 400 ? "bg-[var(--kyro-success-bg)] text-[var(--kyro-success)]" : "bg-[var(--kyro-danger-bg)] text-[var(--kyro-danger)]"
703
+ }`}>
690
704
  {lastStatus}
691
705
  </span>
692
706
  )}
@@ -91,7 +91,7 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
91
91
  headers: {},
92
92
  body: "",
93
93
  });
94
- const [response, setResponse] = useState<Record<string, unknown> | null>(null);
94
+ const [response, setResponse] = useState<{ status: number; duration: number; size: number; data: any } | null>(null);
95
95
  const [loading, setLoading] = useState(false);
96
96
  const [error, setError] = useState<string | null>(null);
97
97
  const [activeEditorTab, setActiveEditorTab] = useState<"params" | "headers" | "body">("params");
@@ -102,6 +102,7 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
102
102
  const [saveRequestName, setSaveRequestName] = useState("");
103
103
  const [copied, setCopied] = useState(false);
104
104
  const [showSidebar, setShowSidebar] = useState(true);
105
+ const [mobilePanel, setMobilePanel] = useState<"editor" | "response">("editor");
105
106
  const [splitPos, setSplitPos] = useState(50);
106
107
  const [isDragging, setIsDragging] = useState(false);
107
108
  const containerRef = useRef<HTMLDivElement>(null);
@@ -343,7 +344,7 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
343
344
  <div className="flex items-center gap-2 px-3 py-2 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface)] shrink-0">
344
345
  <button
345
346
  onClick={() => setShowSidebar(!showSidebar)}
346
- className="p-1 rounded-lg text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)] md:hidden"
347
+ className="p-1 rounded-lg text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"
347
348
  title="Toggle sidebar"
348
349
  >
349
350
  <ChevronRight className={`w-4 h-4 transition-transform ${showSidebar ? "rotate-180" : ""}`} />
@@ -365,7 +366,7 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
365
366
  value={currentRequest.url}
366
367
  onChange={(e) => setCurrentRequest({ ...currentRequest, url: e.target.value })}
367
368
  placeholder="https://api.example.com/endpoint"
368
- className="flex-1 px-3 py-1.5 text-xs bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-md text-[var(--kyro-text-primary)] placeholder:text-[var(--kyro-text-muted)] focus:outline-none focus:border-[var(--kyro-primary)] font-mono"
369
+ className="flex-1 min-w-0 px-3 py-1.5 text-xs bg-[var(--kyro-surface-accent)] border border-[var(--kyro-border)] rounded-md text-[var(--kyro-text-primary)] placeholder:text-[var(--kyro-text-muted)] focus:outline-none focus:border-[var(--kyro-primary)] font-mono"
369
370
  />
370
371
  <div className="flex items-center gap-1">
371
372
  <button
@@ -385,10 +386,18 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
385
386
  </div>
386
387
  </div>
387
388
 
388
- <div className="flex-1 flex overflow-hidden">
389
+ <div className="flex-1 flex overflow-hidden relative">
390
+ {/* Mobile sidebar backdrop */}
391
+ {showSidebar && (
392
+ <div
393
+ className="fixed inset-0 bg-black/40 z-10 md:hidden"
394
+ onClick={() => setShowSidebar(false)}
395
+ />
396
+ )}
397
+
389
398
  {/* Left sidebar */}
390
399
  {showSidebar && (
391
- <div className="w-60 flex-shrink-0 flex flex-col border-r border-[var(--kyro-border)] bg-[var(--kyro-surface)]">
400
+ <div className="absolute md:relative z-20 h-full w-60 flex-shrink-0 flex flex-col border-r border-[var(--kyro-border)] bg-[var(--kyro-surface)]">
392
401
  <div className="flex border-b border-[var(--kyro-border)]">
393
402
  {sidebarPills.map((p) => (
394
403
  <button
@@ -557,9 +566,36 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
557
566
  )}
558
567
 
559
568
  {/* Main split area */}
560
- <div className="flex-1 flex overflow-hidden">
569
+ <div className="flex-1 flex flex-col md:flex-row overflow-hidden">
570
+ {/* Mobile panel switcher */}
571
+ <div className="flex md:hidden border-b border-[var(--kyro-border)] bg-[var(--kyro-surface)] px-3 py-1.5 gap-1">
572
+ <button
573
+ onClick={() => setMobilePanel("editor")}
574
+ className={`flex-1 px-3 py-1 text-[10px] font-semibold rounded-md transition-all ${
575
+ mobilePanel === "editor"
576
+ ? "bg-[var(--kyro-primary)] text-white"
577
+ : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"
578
+ }`}
579
+ >
580
+ Request
581
+ </button>
582
+ <button
583
+ onClick={() => setMobilePanel("response")}
584
+ className={`flex-1 px-3 py-1 text-[10px] font-semibold rounded-md transition-all ${
585
+ mobilePanel === "response"
586
+ ? "bg-[var(--kyro-primary)] text-white"
587
+ : "text-[var(--kyro-text-muted)] hover:text-[var(--kyro-text-primary)] hover:bg-[var(--kyro-surface-accent)]"
588
+ }`}
589
+ >
590
+ Response
591
+ </button>
592
+ </div>
593
+
561
594
  {/* Left: Editor */}
562
- <div className="flex flex-col overflow-hidden border-r border-[var(--kyro-border)]" style={{ width: showSidebar ? `${splitPos}%` : "100%" }}>
595
+ <div
596
+ className={`${mobilePanel === "editor" ? "flex" : "hidden"} md:flex flex-col overflow-hidden border-r border-[var(--kyro-border)] w-full md:w-auto`}
597
+ style={{ width: typeof window !== "undefined" && window.innerWidth >= 768 ? `${splitPos}%` : undefined }}
598
+ >
563
599
  {/* Editor pills */}
564
600
  <div className="flex gap-0.5 px-3 py-1.5 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface)]">
565
601
  {editorPills.map((p) => (
@@ -614,10 +650,10 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
614
650
  </div>
615
651
  </div>
616
652
 
617
- {/* Drag handle */}
653
+ {/* Drag handle - hidden on mobile */}
618
654
  {showSidebar && (
619
655
  <div
620
- className="absolute top-0 bottom-0 z-10 w-1.5 cursor-col-resize group"
656
+ className="hidden md:block absolute top-0 bottom-0 z-10 w-1.5 cursor-col-resize group"
621
657
  style={{ left: `calc(${splitPos}% - 3px)` }}
622
658
  onMouseDown={startDrag}
623
659
  >
@@ -626,7 +662,10 @@ export function RestPlayground({ collections = [] }: RestPlaygroundProps) {
626
662
  )}
627
663
 
628
664
  {/* Right: Response */}
629
- <div className="flex-1 flex flex-col overflow-hidden min-w-0" style={{ width: showSidebar ? `${100 - splitPos}%` : "50%" }}>
665
+ <div
666
+ className={`${mobilePanel === "response" ? "flex" : "hidden"} md:flex flex-1 flex-col overflow-hidden min-w-0 w-full md:w-auto`}
667
+ style={{ width: typeof window !== "undefined" && window.innerWidth >= 768 ? (showSidebar ? `${100 - splitPos}%` : "50%") : undefined }}
668
+ >
630
669
  <div className="flex items-center gap-2 px-3 py-1.5 border-b border-[var(--kyro-border)] bg-[var(--kyro-surface)]">
631
670
  <span className="text-[10px] font-semibold text-[var(--kyro-text-secondary)]">Response</span>
632
671
  {response && (
@@ -572,6 +572,8 @@ export default function RichTextField({
572
572
  extensions: [
573
573
  StarterKit.configure({
574
574
  codeBlock: true,
575
+ link: false,
576
+ underline: false,
575
577
  }),
576
578
  Link.configure({
577
579
  openOnClick: false,
@@ -591,7 +593,7 @@ export default function RichTextField({
591
593
  TextStyle,
592
594
  Color,
593
595
  ],
594
- content: value || {},
596
+ content: value || { type: "doc", content: [] },
595
597
  editable: !disabled,
596
598
  onUpdate: ({ editor }: { editor: any }) => {
597
599
  onChange(editor.getJSON());
@@ -45,7 +45,7 @@ export function SplitButton({
45
45
  if (saveStatus === "error") return `${btnBase} bg-[var(--kyro-error)] border-[var(--kyro-error)] text-[var(--kyro-sidebar-text-active)]`;
46
46
  if (isPublishedIdle) return `${btnBase} bg-[var(--kyro-gray-200)] border-[var(--kyro-gray-200)] text-[var(--kyro-text-muted)] cursor-not-allowed`;
47
47
  // has changes → accent
48
- return `${btnBase} bg-[var(--kyro-primary)] border-[var(--kyro-primary)] hover:bg-[var(--kyro-primary-hover)]`;
48
+ return `${btnBase} bg-[var(--kyro-primary)] border-[var(--kyro-primary)] text-[var(--kyro-sidebar-text-active)] hover:bg-[var(--kyro-primary-hover)]`;
49
49
  };
50
50
 
51
51
  const chevronBase =
@@ -94,6 +94,8 @@
94
94
  --kyro-input-border: #1e293b;
95
95
 
96
96
  --kyro-black: #ffffff;
97
+ --kyro-black-light: #f3f4f6;
98
+ --kyro-black-hover: #e5e7eb;
97
99
  --kyro-gray-50: #1a2332;
98
100
  --kyro-gray-100: #1e293b;
99
101
  --kyro-gray-200: #334155;