diff --git a/apps/docs/.env.example b/apps/docs/.env.example index ae7d7891..68fb00e8 100644 --- a/apps/docs/.env.example +++ b/apps/docs/.env.example @@ -1,5 +1,12 @@ # AI Gateway API Key (optional - for AI chat) AI_GATEWAY_API_KEY="" +# Optional central Geistdocs chat proxy. When set, this is used instead of AI Gateway. +GEISTDOCS_CHAT_PROXY_URL="" +GEISTDOCS_CHAT_PROXY_TOKEN="" + # Production URL (automatically set on Vercel) -NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL="localhost:3000" \ No newline at end of file +NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL="localhost:3000" + +# Required for precomputed feature flag URLs +FLAGS_SECRET="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" diff --git a/apps/docs/app/[lang]/agents.md/route.ts b/apps/docs/app/[lang]/agents.md/route.ts new file mode 100644 index 00000000..e49cab2d --- /dev/null +++ b/apps/docs/app/[lang]/agents.md/route.ts @@ -0,0 +1,10 @@ +import { createAgentsRoute } from '@vercel/geistdocs/routes/agents'; +import { config } from '@/lib/geistdocs/config'; + +const agentsRoute = createAgentsRoute({ + config, +}); + +export const GET = agentsRoute.GET; +export const generateStaticParams = agentsRoute.generateStaticParams; +export const revalidate = false; diff --git a/apps/docs/app/[lang]/docs/[[...slug]]/page.tsx b/apps/docs/app/[lang]/docs/[[...slug]]/page.tsx index ce439ee6..0c78a6e8 100644 --- a/apps/docs/app/[lang]/docs/[[...slug]]/page.tsx +++ b/apps/docs/app/[lang]/docs/[[...slug]]/page.tsx @@ -1,106 +1,35 @@ -import { createRelativeLink } from "fumadocs-ui/mdx"; -import type { Metadata } from "next"; -import { notFound } from "next/navigation"; -import { AskAI } from "@/components/geistdocs/ask-ai"; -import { CopyPage } from "@/components/geistdocs/copy-page"; -import { - DocsBody, - DocsDescription, - DocsPage, - DocsTitle, -} from "@/components/geistdocs/docs-page"; -import { EditSource } from "@/components/geistdocs/edit-source"; -import { Feedback } from "@/components/geistdocs/feedback"; +import { ThemeAwareImage } from "@vercel/geistdocs/components/theme-aware-image"; +import { MobileDocsBar } from "@vercel/geistdocs/mobile-docs-bar"; +import { createDocsPage } from "@vercel/geistdocs/pages/docs"; import { getMDXComponents } from "@/components/geistdocs/mdx-components"; -import { MobileDocsBar } from "@/components/geistdocs/mobile-docs-bar"; -import { OpenInChat } from "@/components/geistdocs/open-in-chat"; -import { ScrollTop } from "@/components/geistdocs/scroll-top"; -import { Separator } from "@/components/ui/separator"; -import { getLLMText, getPageImage, source } from "@/lib/geistdocs/source"; import { IframeBrowser } from "@/components/custom/iframe-browser"; import { LearnMore } from "@/components/custom/learn-more"; import { ProviderList } from "@/components/custom/provider-list"; -import { ThemeAwareImage } from "@/components/custom/theme-aware-image"; +import { config } from "@/lib/geistdocs/config"; +import { geistdocsSource } from "@/lib/geistdocs/source"; import { ExternalLinkIcon } from "lucide-react"; -const Page = async ({ params }: PageProps<"/[lang]/docs/[[...slug]]">) => { - const { slug, lang } = await params; - const page = source.getPage(slug, lang); - - if (!page) { - notFound(); - } - - const markdown = await getLLMText(page); - const MDX = page.data.body; - - return ( - - - - - - - - - - ), - }} - tableOfContentPopover={{ enabled: false }} - toc={page.data.toc} - > - - {page.data.title} - {page.data.description} - - - - - ); -}; - -export const generateStaticParams = () => source.generateParams(); - -export const generateMetadata = async ({ - params, -}: PageProps<"/[lang]/docs/[[...slug]]">) => { - const { slug, lang } = await params; - const page = source.getPage(slug, lang); - - if (!page) { - notFound(); - } - - const metadata: Metadata = { - title: page.data.title, - description: page.data.description, - openGraph: { - images: getPageImage(page).url, - }, - alternates: { - types: { - "text/markdown": slug ? `/docs/${slug}.md` : "/docs.md", - }, - }, - }; - - return metadata; -}; - -export default Page; +const docsPage = createDocsPage({ + config, + mdx: ({ link }) => + getMDXComponents({ + a: link, + IframeBrowser, + LearnMore, + ProviderList, + ThemeAwareImage, + ExternalSmall: ExternalLinkIcon, + }), + openGraph: { + images: true, + }, + source: geistdocsSource, + tableOfContentPopover: { + enabled: false, + }, + renderTop: ({ data }) => , +}); + +export default docsPage.Page; +export const generateStaticParams = docsPage.generateStaticParams; +export const generateMetadata = docsPage.generateMetadata; diff --git a/apps/docs/app/[lang]/home/[code]/components/illustrations.tsx b/apps/docs/app/[lang]/home/[code]/components/illustrations.tsx index 692eb112..2e1c2bd1 100644 --- a/apps/docs/app/[lang]/home/[code]/components/illustrations.tsx +++ b/apps/docs/app/[lang]/home/[code]/components/illustrations.tsx @@ -36,7 +36,7 @@ export const Flexible = ({ ...props }) => { width="64" height="64" rx="12" - fill="var(--background)" + fill="var(--ds-background-100)" /> { width="65" height="65" rx="12.5" - stroke="var(--border)" + stroke="var(--ds-gray-alpha-400)" /> { @@ -351,15 +351,15 @@ export const Adaptable = ({ ...props }) => { - - - + + + { @@ -390,39 +390,78 @@ export const Adaptable = ({ ...props }) => { width="39" height="8" rx="4" - fill="var(--background)" + fill="var(--ds-background-100)" + /> + + + + - - - - + - - + { cy="100" r="23.2008" fill="black" - stroke="var(--border)" + stroke="var(--ds-gray-alpha-400)" strokeWidth="1.5984" /> { - - - + + + @@ -655,11 +694,11 @@ export const Effortless = ({ ...props }) => { { { { { /> { /> { /> { y2="309.5" gradientUnits="userSpaceOnUse" > - - + + diff --git a/apps/docs/app/[lang]/home/[code]/components/testimonials/index.tsx b/apps/docs/app/[lang]/home/[code]/components/testimonials/index.tsx index 7a709256..50c63bfe 100644 --- a/apps/docs/app/[lang]/home/[code]/components/testimonials/index.tsx +++ b/apps/docs/app/[lang]/home/[code]/components/testimonials/index.tsx @@ -14,7 +14,7 @@ export const Card = ({ name, alias, avatar, url, children }: CardProps) => {
{ alt={name} width={40} height={40} - className="h-12 w-12 rounded-full bg-muted" + className="h-12 w-12 rounded-full bg-gray-100" />
-
{name}
-
{alias}
+
{name}
+
{alias}
-
{children}
+
{children}
); }; diff --git a/apps/docs/app/[lang]/home/[code]/copy-snippet.tsx b/apps/docs/app/[lang]/home/[code]/copy-snippet.tsx index 2a9103d6..bf46f775 100644 --- a/apps/docs/app/[lang]/home/[code]/copy-snippet.tsx +++ b/apps/docs/app/[lang]/home/[code]/copy-snippet.tsx @@ -16,13 +16,13 @@ export const CopySnippet = ({ text }: { text: string }) => { ); diff --git a/apps/docs/app/[lang]/home/[code]/highlighted-code.tsx b/apps/docs/app/[lang]/home/[code]/highlighted-code.tsx index df97cbd8..4dbb836f 100644 --- a/apps/docs/app/[lang]/home/[code]/highlighted-code.tsx +++ b/apps/docs/app/[lang]/home/[code]/highlighted-code.tsx @@ -1,6 +1,8 @@ -import type { CSSProperties } from 'react'; +import { CodeBlock } from '@vercel/geistdocs/components/code-block'; +import { geistShikiTheme } from '@vercel/geistdocs/shiki-theme'; +import { highlight } from 'fumadocs-core/highlight'; +import type { ComponentProps } from 'react'; import type { BundledLanguage } from 'shiki'; -import { codeToTokens } from 'shiki'; type HighlightedCodeProps = { code: string; @@ -9,101 +11,37 @@ type HighlightedCodeProps = { caption: string; }; -const parseCssString = (css: string): Record => { - const style: Record = {}; - for (const decl of css.split(';')) { - const idx = decl.indexOf(':'); - if (idx > 0) { - const prop = decl.slice(0, idx).trim(); - const val = decl.slice(idx + 1).trim(); - if (prop && val) { - style[prop] = val; - } - } - } - return style; -}; - export const HighlightedCode = async ({ code, lang, filename, caption, }: HighlightedCodeProps) => { - const result = await codeToTokens(code, { + // Highlight with the same theme the docs use and render through the + // geistdocs CodeBlock so the home page blocks match the documentation. + const rendered = await highlight(code, { lang, - themes: { - light: 'github-light', - dark: 'github-dark', + engine: 'js', + theme: geistShikiTheme, + components: { + pre: ({ children, className, style }: ComponentProps<'pre'>) => ( + + {children} + + ), }, }); - const preStyle: Record = {}; - - if (result.bg) { - preStyle['--sdm-bg'] = result.bg; - } - if (result.fg) { - preStyle['--sdm-fg'] = result.fg; - } - if (result.rootStyle) { - Object.assign(preStyle, parseCssString(result.rootStyle)); - } - return ( -
-
-
- {filename} -
-
-          
-            {result.tokens.map((row, index) => (
-              // biome-ignore lint/suspicious/noArrayIndexKey: stable token order
-              
-                {row.map((token, tokenIndex) => (
-                  
-                    {token.content}
-                  
-                ))}
-              
-            ))}
-          
-        
+
+ {/* Grow the block to fill the grid cell so both columns share a height; + *:mb-0 drops CodeBlock's own bottom margin. These snippets render + without a language icon, so hide the (empty) header icon slot to keep + the filename flush with the header padding. */} +
+ {rendered}
- - {caption} - + {caption}
); }; diff --git a/apps/docs/app/[lang]/home/[code]/install-command.tsx b/apps/docs/app/[lang]/home/[code]/install-command.tsx index 17fb6a03..4b721aab 100644 --- a/apps/docs/app/[lang]/home/[code]/install-command.tsx +++ b/apps/docs/app/[lang]/home/[code]/install-command.tsx @@ -1,7 +1,5 @@ 'use client'; -import { useRouter } from 'next/navigation'; -import { useState } from 'react'; import { CommandPromptContent, CommandPromptCopy, @@ -12,7 +10,8 @@ import { CommandPromptTrigger, CommandPromptTriggerDivider, CommandPromptViewport, -} from '@/components/ui/command-prompt'; +} from '@vercel/geistdocs/components/command-prompt'; +import { useState } from 'react'; const COMMAND_FOR_HUMANS = 'npm install flags'; const COMMAND_FOR_AGENTS = 'npx skills add vercel/flags@flags-sdk'; @@ -26,9 +25,12 @@ interface InstallCommandProps { } export const InstallCommand = ({ value, flagKey }: InstallCommandProps) => { - const router = useRouter(); - // Optimistic override so the tab updates instantly; the server render of the - // matching prebuilt `[code]` takes over on refresh. + // The optimistic override drives the UI instantly and the cookie persists the + // choice for the next load (the server reads it and renders the matching + // prebuilt `[code]`). We intentionally do NOT call router.refresh() here: + // this flag's only consumer is this switcher, so a refresh changes nothing + // visible, but it remounts the subtree mid-toggle and kills the command-line + // width animation. const [override, setOverride] = useState(null); const current = override ?? value; @@ -40,7 +42,6 @@ export const InstallCommand = ({ value, flagKey }: InstallCommandProps) => { if (nextValue === current) return; document.cookie = `${flagKey}=${nextValue}; max-age=${oneYearInSeconds}; path=/`; setOverride(nextValue); - router.refresh(); }} value={current} > diff --git a/apps/docs/app/[lang]/home/[code]/layout.tsx b/apps/docs/app/[lang]/home/[code]/layout.tsx index b60afad2..10ce2457 100644 --- a/apps/docs/app/[lang]/home/[code]/layout.tsx +++ b/apps/docs/app/[lang]/home/[code]/layout.tsx @@ -11,7 +11,7 @@ export default async function Layout({ return ( <> {bannerFlag ? ( -
+
Flags SDK is the simplest way to use feature flags in Next.js and SvelteKit.
diff --git a/apps/docs/app/[lang]/home/[code]/page.tsx b/apps/docs/app/[lang]/home/[code]/page.tsx index 3c0597fa..8cc85f18 100644 --- a/apps/docs/app/[lang]/home/[code]/page.tsx +++ b/apps/docs/app/[lang]/home/[code]/page.tsx @@ -1,9 +1,9 @@ +import { Button } from '@vercel/geistdocs/components/button'; import { generatePermutations } from 'flags/next'; import { FlagValues } from 'flags/react'; import { ArrowRight } from 'lucide-react'; import type { Metadata } from 'next'; import Link from 'next/link'; -import { Button } from '@/components/ui/button'; import { enableBannerFlag, enableDitheredHeroFlag, @@ -98,7 +98,7 @@ export default async function HomePage({

{heroTextFlag}

-

+

Flags SDK is a free, open-source library for using feature flags in Next.js and SvelteKit.

@@ -110,12 +110,12 @@ export default async function HomePage({
{ditheredHeroFlag ? : null} -
+
Try the Flags SDK
- + Set persistent flags for this page
@@ -153,7 +153,7 @@ export default async function HomePage({

Using flags as code

-

+

The SDK sits between your application and the source of your flags, helping you follow best practices and keep your website fast. @@ -168,7 +168,7 @@ export default async function HomePage({

{feature.title}

-

+

{feature.description}

@@ -183,7 +183,7 @@ export default async function HomePage({

Effortless setup

-

+

With a simple declarative API to define and use your feature flags.

diff --git a/apps/docs/app/[lang]/home/[code]/toggles.tsx b/apps/docs/app/[lang]/home/[code]/toggles.tsx index 270360c2..cc81b9b8 100644 --- a/apps/docs/app/[lang]/home/[code]/toggles.tsx +++ b/apps/docs/app/[lang]/home/[code]/toggles.tsx @@ -82,7 +82,7 @@ export const FlagToggle = ({ {label} {description ? ( - {description} + {description} ) : null}
{description ? ( - {description} + {description} ) : null} -
- {children} -
- - ); - - const withReferencedSources = ( - - {inner} - - ); - - // Always provide LocalAttachmentsContext so children get validated add function - return ( - - {withReferencedSources} - - ); -}; - -export type PromptInputBodyProps = HTMLAttributes; - -export const PromptInputBody = ({ - className, - ...props -}: PromptInputBodyProps) => ( -
-); - -export type PromptInputTextareaProps = ComponentProps< - typeof InputGroupTextarea ->; - -export const PromptInputTextarea = ({ - onChange, - onKeyDown, - className, - placeholder = "What would you like to know?", - ...props -}: PromptInputTextareaProps) => { - const controller = useOptionalPromptInputController(); - const attachments = usePromptInputAttachments(); - const [isComposing, setIsComposing] = useState(false); - - const handleKeyDown: KeyboardEventHandler = (e) => { - // Call the external onKeyDown handler first - onKeyDown?.(e); - - // If the external handler prevented default, don't run internal logic - if (e.defaultPrevented) { - return; - } - - if (e.key === "Enter") { - if (isComposing || e.nativeEvent.isComposing) { - return; - } - if (e.shiftKey) { - return; - } - e.preventDefault(); - - // Check if the submit button is disabled before submitting - const form = e.currentTarget.form; - const submitButton = form?.querySelector( - 'button[type="submit"]' - ) as HTMLButtonElement | null; - if (submitButton?.disabled) { - return; - } - - form?.requestSubmit(); - } - - // Remove last attachment when Backspace is pressed and textarea is empty - if ( - e.key === "Backspace" && - e.currentTarget.value === "" && - attachments.files.length > 0 - ) { - e.preventDefault(); - const lastAttachment = attachments.files.at(-1); - if (lastAttachment) { - attachments.remove(lastAttachment.id); - } - } - }; - - const handlePaste: ClipboardEventHandler = (event) => { - const items = event.clipboardData?.items; - - if (!items) { - return; - } - - const files: File[] = []; - - for (const item of items) { - if (item.kind === "file") { - const file = item.getAsFile(); - if (file) { - files.push(file); - } - } - } - - if (files.length > 0) { - event.preventDefault(); - attachments.add(files); - } - }; - - const controlledProps = controller - ? { - value: controller.textInput.value, - onChange: (e: ChangeEvent) => { - controller.textInput.setInput(e.currentTarget.value); - onChange?.(e); - }, - } - : { - onChange, - }; - - return ( - setIsComposing(false)} - onCompositionStart={() => setIsComposing(true)} - onKeyDown={handleKeyDown} - onPaste={handlePaste} - placeholder={placeholder} - {...props} - {...controlledProps} - /> - ); -}; - -export type PromptInputHeaderProps = Omit< - ComponentProps, - "align" ->; - -export const PromptInputHeader = ({ - className, - ...props -}: PromptInputHeaderProps) => ( - -); - -export type PromptInputFooterProps = Omit< - ComponentProps, - "align" ->; - -export const PromptInputFooter = ({ - className, - ...props -}: PromptInputFooterProps) => ( - -); - -export type PromptInputToolsProps = HTMLAttributes; - -export const PromptInputTools = ({ - className, - ...props -}: PromptInputToolsProps) => ( -
-); - -export type PromptInputButtonProps = ComponentProps; - -export const PromptInputButton = ({ - variant = "ghost", - className, - size, - ...props -}: PromptInputButtonProps) => { - const newSize = - size ?? (Children.count(props.children) > 1 ? "sm" : "icon-sm"); - - return ( - - ); -}; - -export type PromptInputActionMenuProps = ComponentProps; -export const PromptInputActionMenu = (props: PromptInputActionMenuProps) => ( - -); - -export type PromptInputActionMenuTriggerProps = PromptInputButtonProps; - -export const PromptInputActionMenuTrigger = ({ - className, - children, - ...props -}: PromptInputActionMenuTriggerProps) => ( - - - {children ?? } - - -); - -export type PromptInputActionMenuContentProps = ComponentProps< - typeof DropdownMenuContent ->; -export const PromptInputActionMenuContent = ({ - className, - ...props -}: PromptInputActionMenuContentProps) => ( - -); - -export type PromptInputActionMenuItemProps = ComponentProps< - typeof DropdownMenuItem ->; -export const PromptInputActionMenuItem = ({ - className, - ...props -}: PromptInputActionMenuItemProps) => ( - -); - -// Note: Actions that perform side-effects (like opening a file dialog) -// are provided in opt-in modules (e.g., prompt-input-attachments). - -export type PromptInputSubmitProps = ComponentProps & { - status?: ChatStatus; - onStop?: () => void; -}; - -export const PromptInputSubmit = ({ - className, - variant = "default", - size = "icon-sm", - status, - onStop, - onClick, - children, - ...props -}: PromptInputSubmitProps) => { - const isGenerating = status === "submitted" || status === "streaming"; - - let Icon = ; - - if (status === "submitted") { - Icon = ; - } else if (status === "streaming") { - Icon = ; - } else if (status === "error") { - Icon = ; - } - - const handleClick = (e: React.MouseEvent) => { - if (isGenerating && onStop) { - e.preventDefault(); - onStop(); - return; - } - onClick?.(e); - }; - - return ( - - {children ?? Icon} - - ); -}; - -export type PromptInputSelectProps = ComponentProps; - -export const PromptInputSelect = (props: PromptInputSelectProps) => ( - diff --git a/apps/docs/components/custom/provider-list.tsx b/apps/docs/components/custom/provider-list.tsx index 6f420d91..8d387ab8 100644 --- a/apps/docs/components/custom/provider-list.tsx +++ b/apps/docs/components/custom/provider-list.tsx @@ -1,6 +1,6 @@ +import { Badge } from '@vercel/geistdocs/components/badge'; import Link from 'next/link'; import { cn } from '@/lib/utils'; -import { Badge } from '../ui/badge'; import { FlagsmithLogo } from './logos/flagsmith'; import { GrowthbookLogo } from './logos/growthbook'; import { HypertuneLogo } from './logos/hypertune'; diff --git a/apps/docs/components/custom/theme-aware-image/index.tsx b/apps/docs/components/custom/theme-aware-image/index.tsx deleted file mode 100644 index 772ff2a3..00000000 --- a/apps/docs/components/custom/theme-aware-image/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import Image, { type ImageProps } from 'next/image'; -import styles from './theme-aware-image.module.css'; - -type Props = ImageProps & { src: never; srcLight: string; srcDark: string }; - -export function ThemeAwareImage({ - srcLight, - srcDark, - className, - ...rest -}: Props) { - return ( - <> - - - - ); -} diff --git a/apps/docs/components/custom/theme-aware-image/theme-aware-image.module.css b/apps/docs/components/custom/theme-aware-image/theme-aware-image.module.css deleted file mode 100644 index a8d1c262..00000000 --- a/apps/docs/components/custom/theme-aware-image/theme-aware-image.module.css +++ /dev/null @@ -1,13 +0,0 @@ -.image[data-theme="dark"] { - display: none; -} - -:global(.dark-theme) { - .image[data-theme="dark"] { - display: block; - } - - .image[data-theme="light"] { - display: none; - } -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-arrow-up-right-small.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-arrow-up-right-small.tsx deleted file mode 100644 index ca7dd589..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-arrow-up-right-small.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import type { SVGProps } from 'react'; - -interface IconProps - extends Omit< - SVGProps, - 'width' | 'height' | 'viewBox' | 'fill' - > { - color?: string; - size?: number | string; -} - -export function IconArrowUpRightSmall({ - size = 16, - color = 'currentColor', - ...props -}: IconProps) { - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-arrow-up-right.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-arrow-up-right.tsx deleted file mode 100644 index 6d667f0e..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-arrow-up-right.tsx +++ /dev/null @@ -1,27 +0,0 @@ -export function IconArrowUpRight({ - size = 16, - className, -}: { - size?: number; - className?: string; -}) { - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-check.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-check.tsx deleted file mode 100644 index 553fe06f..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-check.tsx +++ /dev/null @@ -1,27 +0,0 @@ -export function IconCheck({ - size = 16, - className, -}: { - size?: number; - className?: string; -}) { - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-chevron-down-small.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-chevron-down-small.tsx deleted file mode 100644 index 548015e6..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-chevron-down-small.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Temporary ChevronDownSmall icon fallback. - */ -export function IconChevronDownSmall({ - size = 16, - className, -}: { - size?: number; - className?: string; -}) { - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-chevron-right.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-chevron-right.tsx deleted file mode 100644 index 1f77a95e..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-chevron-right.tsx +++ /dev/null @@ -1,27 +0,0 @@ -export function IconChevronRight({ - size = 16, - className, -}: { - size?: number; - className?: string; -}) { - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-copy.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-copy.tsx deleted file mode 100644 index 47d02774..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-copy.tsx +++ /dev/null @@ -1,27 +0,0 @@ -export function IconCopy({ - size = 16, - className, -}: { - size?: number; - className?: string; -}) { - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-file-text.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-file-text.tsx deleted file mode 100644 index 99804345..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-file-text.tsx +++ /dev/null @@ -1,25 +0,0 @@ -export function IconFileText({ - size = 16, - className, -}: { - size?: number; - className?: string; -}) { - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-menu-alt.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-menu-alt.tsx deleted file mode 100644 index fbeeb908..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-menu-alt.tsx +++ /dev/null @@ -1,27 +0,0 @@ -export function IconMenuAlt({ - size = 16, - className, -}: { - size?: number; - className?: string; -}) { - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-slash-forward.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-slash-forward.tsx deleted file mode 100644 index a6224620..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/icon-slash-forward.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { SVGProps } from 'react'; - -interface IconProps - extends Omit< - SVGProps, - 'width' | 'height' | 'viewBox' | 'fill' - > { - color?: string; - size?: number | string; -} - -export function IconSlashForward({ - size = 16, - color = 'currentColor', - ...props -}: IconProps) { - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/index.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/index.tsx deleted file mode 100644 index 5bd9f084..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/icons/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Barrel fallback for @vercel/geistcn-assets/icons. - * Add re-exports here as you migrate more icons. - */ - -export { IconArrowUpRightSmall } from './icon-arrow-up-right-small'; -export { IconCheck } from './icon-check'; -export { IconChevronDownSmall } from './icon-chevron-down-small'; -export { IconCopy } from './icon-copy'; -export { IconSlashForward } from './icon-slash-forward'; diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/index.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/index.tsx deleted file mode 100644 index 6c3f826b..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Barrel fallback for @vercel/geistcn-assets/logos. - * Add re-exports here as you migrate more logos. - */ - -export { LogoAiElements } from './logo-ai-elements'; -export { LogoAiSdk } from './logo-ai-sdk'; -export { LogoChatSdk } from './logo-chat-sdk'; -export { LogoFlagsSdk } from './logo-flags-sdk'; -export { LogoIconVercel } from './logo-icon-vercel'; -export { LogoStreamdown } from './logo-streamdown'; -export { LogoVercelOss } from './logo-vercel-oss'; -export { LogoWorkflowSdk } from './logo-workflow-sdk'; diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-ai-elements.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-ai-elements.tsx deleted file mode 100644 index 2203e7e0..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-ai-elements.tsx +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Temporary AI Elements logo fallback. - */ -export function LogoAiElements({ - height = 13, - className, -}: { - height?: number; - className?: string; -}) { - const width = (101 / 13) * height; - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-ai-sdk.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-ai-sdk.tsx deleted file mode 100644 index 0720b7fd..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-ai-sdk.tsx +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Temporary AI SDK logo fallback. - */ -export function LogoAiSdk({ - height = 22, - color = 'currentColor', - className, -}: { - height?: number; - color?: string; - className?: string; -}) { - const width = (280 / 77) * height; - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-chat-sdk.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-chat-sdk.tsx deleted file mode 100644 index bc06fa66..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-chat-sdk.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Temporary Chat SDK logo fallback. - */ -export function LogoChatSdk({ - height = 22, - className, -}: { - height?: number; - className?: string; -}) { - const width = (69 / 22) * height; - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-flags-sdk.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-flags-sdk.tsx deleted file mode 100644 index 7bd1408f..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-flags-sdk.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Temporary Flags SDK logo fallback. - */ -export function LogoFlagsSdk({ - height = 22, - className, -}: { - height?: number; - className?: string; -}) { - const width = (76 / 22) * height; - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-icon-vercel.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-icon-vercel.tsx deleted file mode 100644 index c04a15c1..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-icon-vercel.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Fallback for `LogoIconVercel` from `@vercel/geistcn-assets/logos`. - * Used when the private package is not installed (e.g. external contributors). - */ -export function LogoIconVercel({ - size = 16, - className, -}: { - size?: number; - className?: string; -}) { - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-streamdown.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-streamdown.tsx deleted file mode 100644 index e390179b..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-streamdown.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Temporary Streamdown logo fallback. - */ -export function LogoStreamdown({ - height = 14, - color = 'currentColor', - className, -}: { - height?: number; - color?: string; - className?: string; -}) { - const width = (401 / 52) * height; - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-vercel-oss.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-vercel-oss.tsx deleted file mode 100644 index 8ff84d2e..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-vercel-oss.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Temporary Vercel OSS triangle logo. - */ -export function LogoVercelOss({ - size = 20, - className, -}: { - size?: number; - className?: string; -}) { - return ( - - ); -} diff --git a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-workflow-sdk.tsx b/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-workflow-sdk.tsx deleted file mode 100644 index 5c80d34e..00000000 --- a/apps/docs/components/geistcn-fallbacks/geistcn-assets/logos/logo-workflow-sdk.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Temporary Workflow SDK logo fallback. - */ -export function LogoWorkflowSdk({ - height = 22, - color = 'currentColor', - className, -}: { - height?: number; - color?: string; - className?: string; -}) { - const width = (437.736 / 58) * height; - return ( - - ); -} diff --git a/apps/docs/components/geistdocs/ask-ai.tsx b/apps/docs/components/geistdocs/ask-ai.tsx deleted file mode 100644 index b9879e20..00000000 --- a/apps/docs/components/geistdocs/ask-ai.tsx +++ /dev/null @@ -1,33 +0,0 @@ -"use client"; - -import { MessageCircleIcon } from "lucide-react"; -import { useChatContext } from "@/hooks/geistdocs/use-chat"; - -interface AskAIProps { - href: string; -} - -export const AskAI = ({ href }: AskAIProps) => { - const { setIsOpen, setPrompt } = useChatContext(); - - const protocol = process.env.NODE_ENV === "production" ? "https" : "http"; - const url = new URL( - href, - `${protocol}://${process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL}` - ).toString(); - const query = `Read this page, I want to ask questions about it. ${url}`; - - return ( - - ); -}; diff --git a/apps/docs/components/geistdocs/callout.tsx b/apps/docs/components/geistdocs/callout.tsx deleted file mode 100644 index 1d553bc8..00000000 --- a/apps/docs/components/geistdocs/callout.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { - CalloutContainer as CalloutContainerPrimitive, - CalloutDescription as CalloutDescriptionPrimitive, - Callout as CalloutPrimitive, - CalloutTitle as CalloutTitlePrimitive, -} from "fumadocs-ui/components/callout"; -import type { ComponentProps } from "react"; -import { cn } from "@/lib/utils"; - -type CalloutProps = ComponentProps; - -export const Callout = ({ className, ...props }: CalloutProps) => ( - -); - -type CalloutContainerProps = ComponentProps; - -export const CalloutContainer = (props: CalloutContainerProps) => ( - -); - -type CalloutTitleProps = ComponentProps; - -export const CalloutTitle = (props: CalloutTitleProps) => ( - -); - -type CalloutDescriptionProps = ComponentProps< - typeof CalloutDescriptionPrimitive ->; - -export const CalloutDescription = (props: CalloutDescriptionProps) => ( - -); diff --git a/apps/docs/components/geistdocs/chat.tsx b/apps/docs/components/geistdocs/chat.tsx deleted file mode 100644 index 98c2309e..00000000 --- a/apps/docs/components/geistdocs/chat.tsx +++ /dev/null @@ -1,454 +0,0 @@ -"use client"; - -import type { UIMessage } from "@ai-sdk/react"; -import { useChat } from "@ai-sdk/react"; -import { DefaultChatTransport } from "ai"; -import { useLiveQuery } from "dexie-react-hooks"; -import { ChevronRightIcon, Trash } from "lucide-react"; -import { Portal } from "radix-ui"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { toast } from "sonner"; -import type { MyUIMessage } from "@/app/api/chat/types"; -import { - Conversation, - ConversationContent, - ConversationScrollButton, -} from "@/components/ai-elements/conversation"; -import { - Message, - MessageContent, - MessageResponse, -} from "@/components/ai-elements/message"; -import { - PromptInput, - PromptInputBody, - PromptInputFooter, - type PromptInputProps, - PromptInputProvider, - PromptInputSubmit, - PromptInputTextarea, -} from "@/components/ai-elements/prompt-input"; -import { Suggestion, Suggestions } from "@/components/ai-elements/suggestion"; -import { Button } from "@/components/ui/button"; -import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer"; -import { useChatContext } from "@/hooks/geistdocs/use-chat"; -import { useIsMobile } from "@/hooks/use-mobile"; -import { db } from "@/lib/geistdocs/db"; -import { cn } from "@/lib/utils"; -import { ButtonGroup } from "../ui/button-group"; -import { Kbd, KbdGroup } from "../ui/kbd"; -import { Spinner } from "../ui/spinner"; -import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip"; -import { CopyChat } from "./copy-chat"; -import { MessageMetadata } from "./message-metadata"; - -const isFromPreviousDay = (timestamp: number): boolean => { - const messageDate = new Date(timestamp); - const today = new Date(); - - return ( - messageDate.getFullYear() !== today.getFullYear() || - messageDate.getMonth() !== today.getMonth() || - messageDate.getDate() !== today.getDate() - ); -}; - -export const useChatPersistence = () => { - const saveTimeoutRef = useRef | undefined>( - undefined - ); - - // Load messages from Dexie with live query - const storedMessages = useLiveQuery(() => - db.messages.orderBy("sequence").toArray() - ); - - // Clear messages if they're from a previous day - useEffect(() => { - if (storedMessages && storedMessages.length > 0) { - const firstMessage = storedMessages[0]; - if (firstMessage && isFromPreviousDay(firstMessage.timestamp)) { - db.messages.clear(); - } - } - }, [storedMessages]); - - // Filter out stale messages from previous days - const freshMessages = storedMessages?.filter( - (msg) => !isFromPreviousDay(msg.timestamp) - ); - - const initialMessages = - freshMessages?.map(({ timestamp, sequence, ...message }) => message) ?? []; - - const isLoading = storedMessages === undefined; - - // Save messages to Dexie with debouncing - const saveMessages = useCallback((messages: UIMessage[]) => { - if (saveTimeoutRef.current) { - clearTimeout(saveTimeoutRef.current); - } - - saveTimeoutRef.current = setTimeout(async () => { - try { - const baseTimestamp = Date.now(); - const messagesToStore = messages.map((message, index) => ({ - ...message, - timestamp: baseTimestamp + index * 1000, - sequence: index, - })); - - await db.transaction("rw", db.messages, async () => { - await db.messages.clear(); - await db.messages.bulkAdd(messagesToStore); - }); - } catch (error) { - console.error("Failed to save messages:", error); - } - }, 300); - }, []); - - // Clear all messages from Dexie - const clearMessages = useCallback(async () => { - try { - await db.messages.clear(); - } catch (error) { - console.error("Failed to clear messages:", error); - } - }, []); - - // Cleanup timeout on unmount - useEffect( - () => () => { - if (saveTimeoutRef.current) { - clearTimeout(saveTimeoutRef.current); - } - }, - [] - ); - - return { - initialMessages, - isLoading, - saveMessages, - clearMessages, - }; -}; - -interface ChatProps { - basePath: string | undefined; - suggestions: string[]; -} - -type ChatInnerProps = ChatProps & { - isOpen: boolean; -}; - -const ChatInner = ({ basePath, suggestions, isOpen }: ChatInnerProps) => { - const textareaRef = useRef(null); - const [isInitialized, setIsInitialized] = useState(false); - const [localPrompt, setLocalPrompt] = useState(""); - const [providerKey, setProviderKey] = useState(0); - const { prompt, setPrompt, setIsOpen } = useChatContext(); - const { initialMessages, isLoading, saveMessages, clearMessages } = - useChatPersistence(); - - const { messages, sendMessage, status, setMessages, stop } = useChat({ - transport: new DefaultChatTransport({ - api: basePath ? `${basePath}/api/chat` : "/api/chat", - }), - onError: (error) => { - toast.error(error.message, { - description: error.message, - }); - }, - }); - - // Sync external prompt changes to local state and force provider remount - useEffect(() => { - if (prompt && prompt !== localPrompt) { - setLocalPrompt(prompt); - setProviderKey((prev) => prev + 1); - } - }, [prompt, localPrompt]); - - // Set initial messages once loaded from IndexedDB - useEffect(() => { - if (!(isLoading || isInitialized) && initialMessages.length > 0) { - setMessages(initialMessages); - setIsInitialized(true); - } else if (!(isLoading || isInitialized)) { - // Mark as initialized even if no messages to avoid infinite re-runs - setIsInitialized(true); - } - }, [isLoading, initialMessages, isInitialized, setMessages]); - - // Save messages to IndexedDB whenever they change (but only after initialization) - useEffect(() => { - if (isInitialized && messages.length > 0) { - saveMessages(messages); - } - }, [messages, saveMessages, isInitialized]); - - // Focus textarea when chat opens - useEffect(() => { - if (isOpen) { - // Small delay to ensure the panel/drawer animation has started - const timer = setTimeout(() => { - textareaRef.current?.focus(); - }, 100); - return () => clearTimeout(timer); - } - }, [isOpen]); - - const handleSuggestionClick = async (text: string) => { - if (status === "streaming" || status === "submitted") { - return; - } - setLocalPrompt(""); - setPrompt(""); - await sendMessage({ text }); - }; - - const handleSubmit: PromptInputProps["onSubmit"] = async (message, event) => { - event.preventDefault(); - - if (status === "streaming" || status === "submitted") { - return; - } - - const { text } = message; - - if (!text) { - return; - } - - setLocalPrompt(""); - setPrompt(""); - await sendMessage({ text }); - }; - - const handleClearChat = async () => { - try { - await clearMessages(); - setMessages([]); - toast.success("Chat history cleared"); - } catch (error) { - toast.error("Failed to clear chat history", { - description: error instanceof Error ? error.message : "Unknown error", - }); - } - }; - - // Show loading state while initial messages are being loaded - if (isLoading) { - return ( -
- -
- ); - } - - return ( -
-
-

Chat

-
- - - - - - - Clear chat - - - - - - Close chat - - -
-
- - - - {messages.map((message) => ( - - - {message.parts - .filter((part) => part.type === "text") - .map((part) => ( - - - {part.text} - - - ))} - - ))} - {status === "submitted" && ( -
- -
- )} -
- -
- -
- {!messages.length && ( - <> - - {suggestions.map((text) => ( - - ))} - -

- Tip: You can open and close chat with{" "} - - ⌘ - I - -

- - )} - - - - { - setLocalPrompt(e.target.value); - setPrompt(e.target.value); - }} - ref={textareaRef} - /> - - -

- {localPrompt.length} / 1000 -

- { - e.preventDefault(); - stop(); - } - : undefined - } - status={status} - /> -
-
-
-
-
- ); -}; - -export const Chat = ({ basePath, suggestions }: ChatProps) => { - const { isOpen, setIsOpen } = useChatContext(); - const isMobile = useIsMobile(); - - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - // Check for Meta (⌘ on Mac, Windows key on Windows) + "i" (ignore case) - if ( - (event.metaKey || event.ctrlKey) && - !event.altKey && - !event.shiftKey && - event.key.toLowerCase() === "i" - ) { - event.preventDefault(); - - setIsOpen((prev) => !prev); - } - }; - - window.addEventListener("keydown", handleKeyDown); - - return () => { - window.removeEventListener("keydown", handleKeyDown); - }; - }, [setIsOpen]); - - return ( - <> - - - -
- -
-
-
- - - - - - - - -
- - ); -}; diff --git a/apps/docs/components/geistdocs/code-block-tabs.tsx b/apps/docs/components/geistdocs/code-block-tabs.tsx deleted file mode 100644 index 9385c050..00000000 --- a/apps/docs/components/geistdocs/code-block-tabs.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "fumadocs-ui/components/tabs.unstyled"; -import type { ComponentProps } from "react"; -import { cn } from "@/lib/utils"; - -export const CodeBlockTabsList = (props: ComponentProps) => ( - - {props.children} - -); - -export const CodeBlockTabsTrigger = ({ - children, - ...props -}: ComponentProps) => ( - -
- {children} - -); - -export const CodeBlockTabs = ({ - ref, - ...props -}: ComponentProps) => ( - - {props.children} - -); - -export const CodeBlockTab = (props: ComponentProps) => ( - div]:mb-0 [&_pre]:rounded-none [&_pre]:border-none", - props.className - )} - /> -); diff --git a/apps/docs/components/geistdocs/code-block.tsx b/apps/docs/components/geistdocs/code-block.tsx deleted file mode 100644 index 3cf5aee6..00000000 --- a/apps/docs/components/geistdocs/code-block.tsx +++ /dev/null @@ -1,161 +0,0 @@ -"use client"; - -import { CheckIcon, CopyIcon } from "lucide-react"; -import { - type CSSProperties, - type ReactNode, - useCallback, - useRef, - useState, -} from "react"; -import { toast } from "sonner"; -import { NextLogo } from "@/components/custom/logos/next"; -import { SvelteKitLogo } from "@/components/custom/logos/sveltekit"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { cn } from "@/lib/utils"; - -const FRAMEWORK_ICONS: Record = { - next: , - svelte: , -}; - -function parseFrameworkFromTitle(title: string): { - cleanTitle: string; - frameworkIcon: ReactNode | undefined; -} { - const match = title.match(/#(\w+)$/); - if (!match) return { cleanTitle: title, frameworkIcon: undefined }; - return { - cleanTitle: title.slice(0, -match[0].length), - frameworkIcon: FRAMEWORK_ICONS[match[1]], - }; -} - -interface CodeBlockProps { - children: ReactNode; - className?: string; - "data-line-highlighting"?: string; - "data-line-numbers"?: string; - icon?: ReactNode; - style?: CSSProperties; - tabIndex?: number; - title?: string; -} - -export const CodeBlock = ({ - children, - className, - icon, - style, - tabIndex, - title, - ...rest -}: CodeBlockProps) => { - const ref = useRef(null); - const [isCopied, setIsCopied] = useState(false); - const { "data-line-numbers": lineNumbers } = rest; - - const copyToClipboard = useCallback(async () => { - if (typeof window === "undefined" || !navigator?.clipboard?.writeText) { - toast.error("Clipboard API not available"); - return; - } - - const code = ref.current?.innerText; - - if (!code) { - toast.error("No code to copy"); - return; - } - - try { - await navigator.clipboard.writeText(code); - setIsCopied(true); - setTimeout(() => setIsCopied(false), 2000); - } catch (error) { - const message = error instanceof Error ? error.message : "Unknown error"; - - toast.error(message); - } - }, []); - - const Icon = isCopied ? CheckIcon : CopyIcon; - - const CodeBlockComponent = useCallback( - (props: { className?: string }) => ( -
code]:grid [&>code]:min-w-max",
-          className,
-          props.className
-        )}
-        ref={ref}
-        style={style}
-        tabIndex={tabIndex}
-      >
-        {children}
-      
- ), - [children, style, tabIndex, className] - ); - - if (!title) { - return ( -
- - -
- ); - } - - const { cleanTitle, frameworkIcon } = parseFrameworkFromTitle(title); - - return ( - - - {frameworkIcon ? ( -
- {frameworkIcon} -
- ) : ( -
- )} - - {cleanTitle} - - - - - - - - ); -}; diff --git a/apps/docs/components/geistdocs/copy-chat.tsx b/apps/docs/components/geistdocs/copy-chat.tsx deleted file mode 100644 index f00f6523..00000000 --- a/apps/docs/components/geistdocs/copy-chat.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import type { UIMessage } from "ai"; -import { CheckIcon, CopyIcon } from "lucide-react"; -import { useState } from "react"; -import { toast } from "sonner"; -import { Button } from "@/components/ui/button"; -import { - Tooltip, - TooltipContent, - TooltipTrigger, -} from "@/components/ui/tooltip"; - -export const CopyChat = ({ messages }: { messages: UIMessage[] }) => { - const [copied, setCopied] = useState(false); - - const handleCopyChat = async () => { - const markdown = messages - .map((message) => { - const role = message.role === "user" ? "You" : "AI"; - const content = message.parts - .filter((part) => part.type === "text") - .map((part) => part.text) - .join("\n"); - return `**${role}:**\n${content}`; - }) - .join("\n\n---\n\n"); - - try { - await navigator.clipboard.writeText(markdown); - toast.success("Chat copied to clipboard"); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - } catch (error) { - toast.error("Failed to copy chat", { - description: error instanceof Error ? error.message : "Unknown error", - }); - } - }; - - const Icon = copied ? CheckIcon : CopyIcon; - - return ( - - - - - Copy chat - - ); -}; diff --git a/apps/docs/components/geistdocs/copy-page.tsx b/apps/docs/components/geistdocs/copy-page.tsx deleted file mode 100644 index 9c31ed49..00000000 --- a/apps/docs/components/geistdocs/copy-page.tsx +++ /dev/null @@ -1,27 +0,0 @@ -"use client"; - -import { useCopyButton } from "fumadocs-ui/utils/use-copy-button"; -import { CheckIcon, CopyIcon } from "lucide-react"; - -interface CopyPageProps { - text: string; -} - -export const CopyPage = ({ text }: CopyPageProps) => { - const [checked, handleCopy] = useCopyButton(async () => { - await navigator.clipboard.writeText(text); - }); - - const Icon = checked ? CheckIcon : CopyIcon; - - return ( - - ); -}; diff --git a/apps/docs/components/geistdocs/desktop-menu.tsx b/apps/docs/components/geistdocs/desktop-menu.tsx deleted file mode 100644 index 889ed509..00000000 --- a/apps/docs/components/geistdocs/desktop-menu.tsx +++ /dev/null @@ -1,67 +0,0 @@ -"use client"; - -import DynamicLink from "fumadocs-core/dynamic-link"; -import { usePathname } from "next/navigation"; -import { IconArrowUpRightSmall } from "@/components/geistcn-fallbacks/geistcn-assets/icons/icon-arrow-up-right-small"; -import { - NavigationMenu, - NavigationMenuItem, - NavigationMenuLink, - NavigationMenuList, -} from "@/components/ui/navigation-menu"; -import { useIsMobile } from "@/hooks/use-mobile"; -import { cn } from "@/lib/utils"; - -interface DesktopMenuProps { - className?: string; - items: { label: string; href: string }[]; -} - -export const DesktopMenu = ({ items, className }: DesktopMenuProps) => { - const isMobile = useIsMobile(); - const pathname = usePathname(); - - return ( - - - {items.map((item) => { - const isExternal = item.href.startsWith("http"); - // Nav hrefs are deep links (e.g. `/docs/principles/flags-as-code`), - // but the tab represents the whole section (`/docs/principles/*`). - // Match on the first three path segments, ignoring any locale prefix. - const sectionPrefix = item.href.split("/").slice(0, 3).join("/"); - const isActive = - !isExternal && - (pathname.endsWith(sectionPrefix) || - pathname.includes(`${sectionPrefix}/`)); - - return ( - - - {isExternal ? ( - - {item.label} - - ) : ( - - {item.label} - - )} - - - ); - })} - - - ); -}; diff --git a/apps/docs/components/geistdocs/docs-layout.tsx b/apps/docs/components/geistdocs/docs-layout.tsx index e363d509..1b9976ef 100644 --- a/apps/docs/components/geistdocs/docs-layout.tsx +++ b/apps/docs/components/geistdocs/docs-layout.tsx @@ -1,48 +1,22 @@ -import { DocsLayout as FumadocsDocsLayout } from "fumadocs-ui/layouts/docs"; -import type { ComponentProps, CSSProperties, ReactNode } from "react"; -import { - Folder, - Item, - Separator, - Sidebar, -} from "@/components/geistdocs/sidebar"; -import { i18n } from "@/lib/geistdocs/i18n"; +import { GeistdocsDocsLayout as PackageDocsLayout } from "@vercel/geistdocs/layout"; +import type { ComponentProps, ReactNode } from "react"; +import { FrameworkSwitcher } from "@/components/custom/framework-switcher"; +import { config } from "@/lib/geistdocs/config"; interface DocsLayoutProps { children: ReactNode; - tree: ComponentProps["tree"]; + tree: ComponentProps["tree"]; } export const DocsLayout = ({ tree, children }: DocsLayoutProps) => ( - , - components: { - Folder, - Item, - Separator, - }, - }} - tabMode="auto" - themeSwitch={{ - enabled: false, + className: "mx-auto max-w-[1448px] bg-background-200", }} + sidebarTop={} tree={tree} > {children} - + ); diff --git a/apps/docs/components/geistdocs/docs-page.tsx b/apps/docs/components/geistdocs/docs-page.tsx deleted file mode 100644 index 1952e22c..00000000 --- a/apps/docs/components/geistdocs/docs-page.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { - DocsBody as FumadocsDocsBody, - DocsDescription as FumadocsDocsDescription, - DocsPage as FumadocsDocsPage, - DocsTitle as FumadocsDocsTitle, -} from "fumadocs-ui/layouts/docs/page"; -import type { ComponentProps } from "react"; -import { cn } from "@/lib/utils"; - -type PageProps = ComponentProps; - -export const DocsPage = ({ ...props }: PageProps) => ( - -); - -export const DocsTitle = ({ - className, - ...props -}: ComponentProps) => ( - -); - -export const DocsDescription = ( - props: ComponentProps -) => ; - -export const DocsBody = ({ - className, - ...props -}: ComponentProps) => ( - -); diff --git a/apps/docs/components/geistdocs/edit-source.tsx b/apps/docs/components/geistdocs/edit-source.tsx deleted file mode 100644 index b28564ad..00000000 --- a/apps/docs/components/geistdocs/edit-source.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { SiGithub } from "@icons-pack/react-simple-icons"; -import { github } from "@/geistdocs"; - -interface EditSourceProps { - path: string | undefined; -} - -export const EditSource = ({ path }: EditSourceProps) => { - let url: string | undefined; - - if (github.owner && github.repo && path) { - url = `https://github.com/${github.owner}/${github.repo}/edit/main/content/docs/${path}`; - } - - if (!url) { - return null; - } - - return ( - - - Edit this page on GitHub - - ); -}; diff --git a/apps/docs/components/geistdocs/feedback.tsx b/apps/docs/components/geistdocs/feedback.tsx deleted file mode 100644 index 3378a4e4..00000000 --- a/apps/docs/components/geistdocs/feedback.tsx +++ /dev/null @@ -1,139 +0,0 @@ -"use client"; - -import { SiMarkdown } from "@icons-pack/react-simple-icons"; -import { ThumbsUpIcon } from "lucide-react"; -import { usePathname } from "next/navigation"; -import { - type FormEventHandler, - useEffect, - useState, - useTransition, -} from "react"; -import { sendFeedback } from "@/app/actions/feedback"; -import { emotions } from "@/app/actions/feedback/emotions"; -import { cn } from "@/lib/utils"; -import { Button } from "../ui/button"; -import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; -import { Textarea } from "../ui/textarea"; - -type Emotion = (typeof emotions)[number]["name"]; - -export interface Feedback { - emotion: Emotion; - message: string; - url?: string; -} - -export const Feedback = () => { - const url = usePathname(); - const [submitted, setSubmitted] = useState(false); - const [emotion, setEmotion] = useState(null); - const [message, setMessage] = useState(""); - const [isPending, startTransition] = useTransition(); - - useEffect(() => { - const item = localStorage.getItem(`docs-feedback-${url}`); - - if (!item) { - return; - } - - setSubmitted(true); - }, [url]); - - const submit: FormEventHandler = (e) => { - e.preventDefault(); - - if (!emotion) { - return; - } - - startTransition(() => { - const feedback: Feedback = { - emotion, - message, - }; - - sendFeedback(url, feedback).then(() => { - setSubmitted(true); - setMessage(""); - setEmotion(null); - }); - }); - - e?.preventDefault(); - }; - - return ( - - - - - -
- {submitted ? ( -
-

Thank you for your feedback!

-
- ) : ( -
-
-