diff --git a/.gitignore b/.gitignore index d6cb1d9..6c833f6 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,5 @@ apps/www/public/r/* apps/www/registry.json apps/www/registry-pro.json apps/www/registry/__index__.tsx + +.claude diff --git a/apps/www/app/docs/[[...slug]]/page.tsx b/apps/www/app/docs/[[...slug]]/page.tsx index a16bf13..b5bd314 100644 --- a/apps/www/app/docs/[[...slug]]/page.tsx +++ b/apps/www/app/docs/[[...slug]]/page.tsx @@ -46,7 +46,7 @@ export default async function Page(props: { {page.data.title} {page.data.description} -
+
{ - const slug = [...route.slug] - if (slug.length > 0) { - slug[slug.length - 1] = `${slug[slug.length - 1]}.mdx` - } - return { slug } - }) + return routes.map((route) => { + const slug = [...route.slug] + if (slug.length > 0) { + slug[slug.length - 1] = `${slug[slug.length - 1]}.mdx` + } + return { slug } + }) } export async function GET( @@ -24,9 +23,9 @@ export async function GET( { params }: { params: Promise<{ slug: string[] }> } ) { const slug = (await params).slug - const pageSlug = slug.map((segment: string, index: number) => - index === slug.length - 1 && segment.endsWith('.mdx') - ? segment.slice(0, -4) + const pageSlug = slug.map((segment: string, index: number) => + index === slug.length - 1 && segment.endsWith(".mdx") + ? segment.slice(0, -4) : segment ) const page = source.getPage(pageSlug) @@ -53,4 +52,4 @@ Error: EISDIR: illegal operation on a directory, copyfile '/Users/X/Documents/Pe dest: '/Users/X/Documents/Personal/Github/limeplay/apps/www/out/llms.mdx/hooks' } error: script "build" exited with code 1 - */ \ No newline at end of file + */ diff --git a/apps/www/components/codeblock.tsx b/apps/www/components/codeblock.tsx index 8ac0a64..7eb04be 100644 --- a/apps/www/components/codeblock.tsx +++ b/apps/www/components/codeblock.tsx @@ -90,8 +90,8 @@ export function CodeBlock({ ref={ref} {...props} className={cn( - isTab ? [bg, "rounded-lg shadow-sm"] : "my-4 rounded-xl bg-card p-1", - "shiki not-prose relative overflow-hidden border text-sm outline-none", + isTab ? [bg, "rounded-lg"] : "bg-card p-1", + "shiki not-prose relative overflow-hidden text-sm outline-none", props.className )} > diff --git a/apps/www/components/component-preview-control.tsx b/apps/www/components/component-preview-control.tsx index cd7daaa..4187e2c 100644 --- a/apps/www/components/component-preview-control.tsx +++ b/apps/www/components/component-preview-control.tsx @@ -1,7 +1,7 @@ "use client" import * as TabsPrimitive from "@radix-ui/react-tabs" -import { motion } from "motion/react" +import { AnimatePresence, motion } from "motion/react" import * as React from "react" import { Tabs, TabsList } from "@/components/ui/tabs" @@ -11,14 +11,17 @@ interface ComponentPreviewControlProps { children: React.ReactNode className?: string hideCode?: boolean + trailingSlot?: React.ReactNode } export function ComponentPreviewControl({ children, className, hideCode = false, + trailingSlot, }: ComponentPreviewControlProps) { const [activeTab, setActiveTab] = React.useState("preview") + const childArray = React.Children.toArray(children) return ( )} + {trailingSlot ? ( +
{trailingSlot}
+ ) : null} +
+
+ + + {childArray} + +
- {children} ) } diff --git a/apps/www/components/component-preview.tsx b/apps/www/components/component-preview.tsx index 6cbac11..37985ba 100644 --- a/apps/www/components/component-preview.tsx +++ b/apps/www/components/component-preview.tsx @@ -43,17 +43,12 @@ export async function ComponentPreview({ const filePath = path.join(Component?.files?.[0]?.path) const fileContent = await fs.promises.readFile(filePath, "utf-8") const fileName = path.basename(filePath) - const PreviewComponent = withPlayer ? PlayerLayoutDemo : React.Fragment const rendered = await highlight(fileContent, { components: { pre: (props) =>
,
     },
     lang: "tsx",
-    themes: {
-      dark: "min-dark",
-      light: "github-light",
-    },
   })
 
   return (
@@ -61,39 +56,53 @@ export async function ComponentPreview({
       className={cn("group relative my-4 mb-12 flex flex-col space-y-2")}
       {...props}
     >
-      
-        
-        
-          
+          }
+          overlayChildren={overlayChildren}
+          type={type}
+        >
+          
+        
+      ) : (
+        
+          
-        
-      
+            
+ +
+ + + + {rendered} + + + + )}
) } diff --git a/apps/www/components/stream-panel/custom-overlay.tsx b/apps/www/components/stream-panel/custom-overlay.tsx new file mode 100644 index 0000000..0a0d874 --- /dev/null +++ b/apps/www/components/stream-panel/custom-overlay.tsx @@ -0,0 +1,233 @@ +"use client" + +import { AnimatePresence, motion } from "motion/react" +import React, { useState } from "react" + +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { useDocsDialStore } from "@/lib/docs-dial-store" +import { cn } from "@/lib/utils" + +import { OverlayShell } from "./overlay-shell" + +interface CustomOverlayProps { + onBack: () => void + onLoad: (src: string, config?: string) => void + show: boolean +} + +export function CustomOverlay({ onBack, onLoad, show }: CustomOverlayProps) { + const store = useDocsDialStore() + const [urlError, setUrlError] = useState(false) + const [configError, setConfigError] = useState(false) + const [showSave, setShowSave] = useState(false) + const [saveName, setSaveName] = useState("") + + const isValidUrl = React.useMemo(() => { + const src = store.customSrc.trim() + if (!src) return false + try { + const url = new URL(src) + return url.protocol === "http:" || url.protocol === "https:" + } catch { + return false + } + }, [store.customSrc]) + + const isValidConfig = React.useMemo(() => { + const config = store.customConfig.trim() + if (!config) return true + try { + JSON.parse(config) + return true + } catch { + return false + } + }, [store.customConfig]) + + const canLoad = isValidUrl && isValidConfig + const canSave = canLoad && saveName.trim().length > 0 + + const handleUrlChange = (value: string) => { + store.setCustomSrc(value) + if (value.trim()) { + try { + const url = new URL(value.trim()) + setUrlError(url.protocol !== "http:" && url.protocol !== "https:") + } catch { + setUrlError(true) + } + } else { + setUrlError(false) + } + } + + const handleConfigChange = (value: string) => { + store.setCustomConfig(value) + if (value.trim()) { + try { + JSON.parse(value.trim()) + setConfigError(false) + } catch { + setConfigError(true) + } + } else { + setConfigError(false) + } + } + + const handleConfigPaste = (e: React.ClipboardEvent) => { + const pasted = e.clipboardData.getData("text") + try { + const parsed = JSON.parse(pasted) + e.preventDefault() + store.setCustomConfig(JSON.stringify(parsed, null, 2)) + setConfigError(false) + } catch { + // not valid JSON, let default paste happen + } + } + + const handleLoad = () => { + if (!canLoad) return + onLoad(store.customSrc.trim(), store.customConfig.trim() || undefined) + } + + const handleSave = () => { + if (!canSave) return + store.saveStream({ + config: store.customConfig.trim() || undefined, + name: saveName.trim(), + src: store.customSrc.trim(), + }) + setSaveName("") + setShowSave(false) + } + + return ( + +
+
+ + handleUrlChange(e.target.value)} + placeholder="https://example.com/stream.m3u8" + value={store.customSrc} + /> + {urlError && ( +

+ Enter a valid HTTP/HTTPS URL +

+ )} +
+
+
+ + + Reference + +
+