page.tsx 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. "use client";
  2. import { useState, useEffect, useCallback, type ReactNode } from "react";
  3. import ConfettiExplosion from "react-confetti-explosion";
  4. import { JSONUIProvider, Renderer } from "@json-render/react";
  5. import type { Spec } from "@json-render/core";
  6. import {
  7. registry,
  8. actionHandlers,
  9. computedFunctions,
  10. onConfetti,
  11. } from "@/lib/render/registry";
  12. import { examples } from "@/lib/examples";
  13. function SpecRenderer({ spec }: { spec: Spec }): ReactNode {
  14. return (
  15. <JSONUIProvider
  16. registry={registry}
  17. initialState={spec.state ?? {}}
  18. handlers={actionHandlers}
  19. functions={computedFunctions}
  20. >
  21. <Renderer spec={spec} registry={registry} />
  22. </JSONUIProvider>
  23. );
  24. }
  25. export default function Page() {
  26. const [selectedIndex, setSelectedIndex] = useState(0);
  27. const selected = examples[selectedIndex]!;
  28. const [confettiKey, setConfettiKey] = useState(0);
  29. const [confettiActive, setConfettiActive] = useState(false);
  30. const fireConfetti = useCallback(() => {
  31. setConfettiKey((k) => k + 1);
  32. setConfettiActive(true);
  33. }, []);
  34. useEffect(() => onConfetti(fireConfetti), [fireConfetti]);
  35. return (
  36. <div className="h-screen flex flex-col bg-muted/30">
  37. {/* Example selector */}
  38. <nav className="flex gap-1 p-3 overflow-x-auto border-b bg-background shrink-0">
  39. {examples.map((ex, i) => (
  40. <button
  41. key={ex.name}
  42. onClick={() => setSelectedIndex(i)}
  43. className={`px-3 py-1.5 text-sm rounded-md whitespace-nowrap transition-colors ${
  44. i === selectedIndex
  45. ? "bg-primary text-primary-foreground"
  46. : "hover:bg-muted"
  47. }`}
  48. >
  49. {ex.name}
  50. </button>
  51. ))}
  52. </nav>
  53. {/* Render area */}
  54. <div className="flex-1 flex items-start justify-center overflow-auto p-6">
  55. <div className="relative bg-background border rounded-lg shadow-sm w-full max-w-[960px]">
  56. {confettiActive && (
  57. <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
  58. <ConfettiExplosion
  59. key={confettiKey}
  60. portal={false}
  61. force={0.8}
  62. duration={3500}
  63. particleCount={400}
  64. particleSize={8}
  65. colors={["#00F0FF", "#7B61FF", "#FF3DFF", "#00FF94", "#FFE14D"]}
  66. width={1600}
  67. height="200vh"
  68. zIndex={1}
  69. onComplete={() => setConfettiActive(false)}
  70. />
  71. </div>
  72. )}
  73. <div className="p-6 relative z-10">
  74. <p className="text-xs text-muted-foreground mb-4">
  75. {selected.description}
  76. </p>
  77. <SpecRenderer key={selectedIndex} spec={selected.spec} />
  78. </div>
  79. </div>
  80. </div>
  81. </div>
  82. );
  83. }