page.tsx 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. "use client";
  2. import { useState } from "react";
  3. import { Badge } from "@/components/ui/badge";
  4. import { examples, allTags, getGitHubUrl, type Example } from "@/lib/examples";
  5. import { cn } from "@/lib/utils";
  6. function ExampleCard({ example }: { example: Example }) {
  7. return (
  8. <div className="group flex flex-col rounded-xl border border-border bg-card text-card-foreground overflow-hidden transition-colors hover:border-foreground/25">
  9. <div className="flex flex-1 flex-col gap-3 p-5">
  10. <h3 className="font-semibold leading-none">{example.title}</h3>
  11. <p className="text-sm text-muted-foreground leading-relaxed">
  12. {example.description}
  13. </p>
  14. <div className="flex flex-wrap gap-1.5">
  15. {example.tags.map((tag) => (
  16. <Badge key={tag} variant="secondary" className="text-[11px]">
  17. {tag}
  18. </Badge>
  19. ))}
  20. </div>
  21. <div className="mt-auto flex items-center gap-3 pt-2">
  22. {example.demoUrl && (
  23. <a
  24. href={example.demoUrl}
  25. target="_blank"
  26. rel="noopener noreferrer"
  27. className="inline-flex items-center gap-1.5 text-sm text-foreground hover:text-primary transition-colors"
  28. >
  29. <svg
  30. xmlns="http://www.w3.org/2000/svg"
  31. width="14"
  32. height="14"
  33. viewBox="0 0 24 24"
  34. fill="none"
  35. stroke="currentColor"
  36. strokeWidth="2"
  37. strokeLinecap="round"
  38. strokeLinejoin="round"
  39. >
  40. <path d="M15 3h6v6" />
  41. <path d="M10 14 21 3" />
  42. <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
  43. </svg>
  44. Live Demo
  45. </a>
  46. )}
  47. <a
  48. href={getGitHubUrl(example)}
  49. target="_blank"
  50. rel="noopener noreferrer"
  51. className="inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors"
  52. >
  53. <svg
  54. viewBox="0 0 16 16"
  55. className="h-3.5 w-3.5"
  56. fill="currentColor"
  57. aria-hidden="true"
  58. >
  59. <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
  60. </svg>
  61. Source
  62. </a>
  63. </div>
  64. </div>
  65. </div>
  66. );
  67. }
  68. export default function ExamplesPage() {
  69. const [activeTag, setActiveTag] = useState<string | null>(null);
  70. const filtered = activeTag
  71. ? examples.filter((e) => e.tags.includes(activeTag))
  72. : examples;
  73. return (
  74. <section className="mx-auto max-w-6xl px-6 py-16">
  75. <div className="mb-10">
  76. <h1 className="text-3xl font-bold tracking-tight sm:text-4xl">
  77. Examples
  78. </h1>
  79. <p className="mt-3 text-lg text-muted-foreground">
  80. Explore json-render across frameworks, renderers, and use cases.
  81. </p>
  82. </div>
  83. <div className="mb-8 flex flex-wrap gap-2">
  84. <button
  85. onClick={() => setActiveTag(null)}
  86. className={cn(
  87. "rounded-full border px-3 py-1 text-xs font-medium transition-colors",
  88. activeTag === null
  89. ? "border-foreground bg-foreground text-background"
  90. : "border-border text-muted-foreground hover:text-foreground hover:border-foreground/50",
  91. )}
  92. >
  93. All
  94. </button>
  95. {allTags.map((tag) => (
  96. <button
  97. key={tag}
  98. onClick={() => setActiveTag(activeTag === tag ? null : tag)}
  99. className={cn(
  100. "rounded-full border px-3 py-1 text-xs font-medium transition-colors",
  101. activeTag === tag
  102. ? "border-foreground bg-foreground text-background"
  103. : "border-border text-muted-foreground hover:text-foreground hover:border-foreground/50",
  104. )}
  105. >
  106. {tag}
  107. </button>
  108. ))}
  109. </div>
  110. <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
  111. {filtered.map((example) => (
  112. <ExampleCard key={example.slug} example={example} />
  113. ))}
  114. </div>
  115. {filtered.length === 0 && (
  116. <p className="py-12 text-center text-muted-foreground">
  117. No examples match the selected filter.
  118. </p>
  119. )}
  120. </section>
  121. );
  122. }