Chris Tate 5 месяцев назад
Родитель
Сommit
13b722672a

+ 6 - 4
apps/web/components/code-block.tsx

@@ -101,10 +101,12 @@ export function CodeBlock({ code, lang }: CodeBlockProps) {
 
   return (
     <div className="relative group">
-      <CopyButton
-        text={code}
-        className="absolute top-0 right-0 opacity-0 group-hover:opacity-100 text-muted-foreground"
-      />
+      <div className="sticky top-0 float-right z-10">
+        <CopyButton
+          text={code}
+          className="opacity-0 group-hover:opacity-100 text-muted-foreground bg-card"
+        />
+      </div>
       <div
         className="text-[11px] leading-relaxed [&_pre]:bg-transparent! [&_pre]:p-0! [&_pre]:m-0! [&_pre]:border-none! [&_pre]:rounded-none! [&_pre]:text-[11px]! [&_code]:bg-transparent! [&_code]:p-0! [&_code]:rounded-none! [&_code]:text-[11px]!"
         dangerouslySetInnerHTML={{ __html: html }}

+ 6 - 4
apps/web/components/code.tsx

@@ -73,10 +73,12 @@ export async function Code({ children, lang = "typescript" }: CodeProps) {
 
   return (
     <div className="group relative my-6 rounded-lg border border-border bg-card p-4 overflow-x-auto text-sm [&_pre]:bg-transparent! [&_pre]:p-0! [&_pre]:m-0! [&_code]:bg-transparent! font-mono">
-      <CopyButton
-        text={children.trim()}
-        className="absolute top-2 right-2 opacity-0 group-hover:opacity-100 text-muted-foreground"
-      />
+      <div className="sticky top-0 float-right z-10 -mt-1 -mr-1">
+        <CopyButton
+          text={children.trim()}
+          className="opacity-0 group-hover:opacity-100 text-muted-foreground bg-card"
+        />
+      </div>
       <div dangerouslySetInnerHTML={{ __html: html }} />
     </div>
   );

+ 18 - 22
apps/web/components/demo.tsx

@@ -277,28 +277,24 @@ export function Demo() {
             ))}
           </div>
           <div className="border border-border rounded p-3 bg-card font-mono text-xs h-96 overflow-auto text-left">
-            {activeTab === "stream" && (
-              <div className="space-y-1">
-                {streamLines.map((line, i) => (
-                  <div
-                    key={i}
-                    className="text-muted-foreground truncate animate-in fade-in slide-in-from-bottom-1 duration-200"
-                  >
-                    {line}
-                  </div>
-                ))}
-                {showLoadingDots && (
-                  <div className="flex gap-1 mt-2">
-                    <span className="w-1 h-1 bg-muted-foreground rounded-full animate-pulse" />
-                    <span className="w-1 h-1 bg-muted-foreground rounded-full animate-pulse [animation-delay:75ms]" />
-                    <span className="w-1 h-1 bg-muted-foreground rounded-full animate-pulse [animation-delay:150ms]" />
-                  </div>
-                )}
-                {streamLines.length === 0 && !showLoadingDots && (
-                  <div className="text-muted-foreground/50">waiting...</div>
-                )}
-              </div>
-            )}
+            <div className={activeTab === "stream" ? "" : "hidden"}>
+              {streamLines.length > 0 ? (
+                <>
+                  <CodeBlock code={streamLines.join("\n")} lang="json" />
+                  {showLoadingDots && (
+                    <div className="flex gap-1 mt-2">
+                      <span className="w-1 h-1 bg-muted-foreground rounded-full animate-pulse" />
+                      <span className="w-1 h-1 bg-muted-foreground rounded-full animate-pulse [animation-delay:75ms]" />
+                      <span className="w-1 h-1 bg-muted-foreground rounded-full animate-pulse [animation-delay:150ms]" />
+                    </div>
+                  )}
+                </>
+              ) : (
+                <div className="text-muted-foreground/50">
+                  {showLoadingDots ? "streaming..." : "waiting..."}
+                </div>
+              )}
+            </div>
             <div className={activeTab === "json" ? "" : "hidden"}>
               <CodeBlock code={jsonCode} lang="json" />
             </div>

+ 22 - 3
apps/web/components/demo/checkbox.tsx

@@ -1,19 +1,38 @@
 "use client";
 
+import { useState } from "react";
 import type { ComponentRenderProps } from "./types";
 import { baseClass, getCustomClass } from "./utils";
 
 export function Checkbox({ element }: ComponentRenderProps) {
   const { props } = element;
   const customClass = getCustomClass(props);
+  const [checked, setChecked] = useState(!!props.checked);
 
   return (
     <label
-      className={`flex items-center gap-2 text-xs ${baseClass} ${customClass}`}
+      className={`flex items-center gap-2 text-xs cursor-pointer ${baseClass} ${customClass}`}
+      onClick={() => setChecked((prev) => !prev)}
     >
       <div
-        className={`w-3.5 h-3.5 border border-border rounded-sm ${props.checked ? "bg-foreground" : "bg-card"}`}
-      />
+        className={`w-3.5 h-3.5 border border-border rounded-sm flex items-center justify-center transition-colors ${checked ? "bg-foreground" : "bg-card"}`}
+      >
+        {checked && (
+          <svg
+            className="w-2.5 h-2.5 text-background"
+            fill="none"
+            stroke="currentColor"
+            viewBox="0 0 24 24"
+          >
+            <path
+              strokeLinecap="round"
+              strokeLinejoin="round"
+              strokeWidth={3}
+              d="M5 13l4 4L19 7"
+            />
+          </svg>
+        )}
+      </div>
       {props.label as string}
     </label>
   );

+ 13 - 3
apps/web/components/demo/radio.tsx

@@ -1,5 +1,6 @@
 "use client";
 
+import { useState } from "react";
 import type { ComponentRenderProps } from "./types";
 import { baseClass, getCustomClass } from "./utils";
 
@@ -7,6 +8,7 @@ export function Radio({ element }: ComponentRenderProps) {
   const { props } = element;
   const customClass = getCustomClass(props);
   const options = (props.options as string[]) || [];
+  const [selected, setSelected] = useState(0);
 
   return (
     <div className={`space-y-1 ${baseClass} ${customClass}`}>
@@ -16,10 +18,18 @@ export function Radio({ element }: ComponentRenderProps) {
         </div>
       ) : null}
       {options.map((opt, i) => (
-        <label key={i} className="flex items-center gap-2 text-xs">
+        <label
+          key={i}
+          className="flex items-center gap-2 text-xs cursor-pointer"
+          onClick={() => setSelected(i)}
+        >
           <div
-            className={`w-3.5 h-3.5 border border-border rounded-full ${i === 0 ? "bg-foreground" : "bg-card"}`}
-          />
+            className={`w-3.5 h-3.5 border border-border rounded-full flex items-center justify-center transition-colors ${selected === i ? "border-foreground" : ""}`}
+          >
+            {selected === i && (
+              <div className="w-2 h-2 rounded-full bg-foreground" />
+            )}
+          </div>
           {opt}
         </label>
       ))}

+ 6 - 3
apps/web/components/demo/switch.tsx

@@ -1,22 +1,25 @@
 "use client";
 
+import { useState } from "react";
 import type { ComponentRenderProps } from "./types";
 import { baseClass, getCustomClass } from "./utils";
 
 export function Switch({ element }: ComponentRenderProps) {
   const { props } = element;
   const customClass = getCustomClass(props);
+  const [checked, setChecked] = useState(!!props.checked);
 
   return (
     <label
-      className={`flex items-center justify-between gap-2 text-xs ${baseClass} ${customClass}`}
+      className={`flex items-center justify-between gap-2 text-xs cursor-pointer ${baseClass} ${customClass}`}
+      onClick={() => setChecked((prev) => !prev)}
     >
       <span>{props.label as string}</span>
       <div
-        className={`w-8 h-4 rounded-full relative ${props.checked ? "bg-foreground" : "bg-border"}`}
+        className={`w-8 h-4 rounded-full relative transition-colors ${checked ? "bg-foreground" : "bg-border"}`}
       >
         <div
-          className={`absolute w-3 h-3 rounded-full bg-background top-0.5 transition-all ${props.checked ? "right-0.5" : "left-0.5"}`}
+          className={`absolute w-3 h-3 rounded-full bg-background top-0.5 transition-all ${checked ? "right-0.5" : "left-0.5"}`}
         />
       </div>
     </label>