diff --git a/docs/app/(home)/cloud/CloudCodeBlock.module.css b/docs/app/(home)/cloud/CloudCodeBlock.module.css index ca371cded..8b832f4cd 100644 --- a/docs/app/(home)/cloud/CloudCodeBlock.module.css +++ b/docs/app/(home)/cloud/CloudCodeBlock.module.css @@ -29,6 +29,22 @@ background: rgb(34 197 94 / 14%); } +.code :global(.token.constant) { + color: #005a9c !important; +} + +.changedLine :global(.token.string) { + color: #9f1239 !important; +} + +[data-theme="dark"] .code :global(.token.constant) { + color: #b8bdff !important; +} + +[data-theme="dark"] .changedLine :global(.token.string) { + color: #f2a88f !important; +} + @media (max-width: 767px) { .code { scrollbar-width: none; diff --git a/docs/app/(home)/cloud/CloudCodeBlock.tsx b/docs/app/(home)/cloud/CloudCodeBlock.tsx index 41976221f..cbc50518a 100644 --- a/docs/app/(home)/cloud/CloudCodeBlock.tsx +++ b/docs/app/(home)/cloud/CloudCodeBlock.tsx @@ -2,16 +2,11 @@ import { useTheme } from "next-themes"; import { Highlight, themes } from "prism-react-renderer"; -import { useEffect, useState } from "react"; import styles from "./CloudCodeBlock.module.css"; export function CloudCodeBlock({ code }: { code: string }) { const { resolvedTheme } = useTheme(); - const [mounted, setMounted] = useState(false); - - useEffect(() => setMounted(true), []); - - const isDark = mounted && resolvedTheme === "dark"; + const isDark = resolvedTheme === "dark"; return (
diff --git a/docs/app/(home)/cloud/CloudHero.tsx b/docs/app/(home)/cloud/CloudHero.tsx new file mode 100644 index 000000000..0383033c7 --- /dev/null +++ b/docs/app/(home)/cloud/CloudHero.tsx @@ -0,0 +1,139 @@ +import { ArrowUpRight } from "lucide-react"; +import { BevelButton } from "../components/Button/BevelButton"; +import heroStyles from "../sections/HeroSection/HeroSection.module.css"; +import cloudStyles from "./page.module.css"; + +function CloudCtas() { + return ( +
+ } + /> + } + /> +
+ ); +} + +function CloudTitle() { + return ( + + + OpenUI Cloud + + API for running OpenUI in Production + + ); +} + +function CloudSubtitle() { + return ( + + One API for models, fallbacks, validation, rendering, and observability. + + ); +} + +function CloudPreview({ decorative = false }: { decorative?: boolean }) { + return ( + + + {decorative + + ); +} + +export function CloudHero() { + return ( +
+
+
+
+

+ +

+

+ +

+
+ +
+
+ +
+
+
+
+ +
+
+
+
+

+ +

+
+

+ +

+
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+
+
+ +
+
+
+
+ ); +} diff --git a/docs/app/(home)/cloud/FeaturesSection.module.css b/docs/app/(home)/cloud/FeaturesSection.module.css index c7c510369..f36b9ed3e 100644 --- a/docs/app/(home)/cloud/FeaturesSection.module.css +++ b/docs/app/(home)/cloud/FeaturesSection.module.css @@ -15,30 +15,21 @@ height: 400px; } -.featureImage { +.featureMedia { flex: 0 0 720px; width: 720px; height: 400px; border-radius: 24px; + overflow: hidden; } .featureImage { display: block; + width: 100%; + height: 100%; object-fit: cover; } -.featureImageDark { - display: none; -} - -[data-theme="dark"] .featureImageLight { - display: none; -} - -[data-theme="dark"] .featureImageDark { - display: block; -} - .content { display: flex; flex: 0 0 400px; @@ -107,7 +98,7 @@ height: auto; } - .featureImage { + .featureMedia { flex: 0 0 auto; width: 100%; height: auto; @@ -131,7 +122,7 @@ padding-inline: var(--home-section-padding-inline); } - .featureImage { + .featureMedia { order: -1; border-radius: 12px; } diff --git a/docs/app/(home)/cloud/FeaturesSection.tsx b/docs/app/(home)/cloud/FeaturesSection.tsx index 3be69c4c7..377e41fa9 100644 --- a/docs/app/(home)/cloud/FeaturesSection.tsx +++ b/docs/app/(home)/cloud/FeaturesSection.tsx @@ -1,5 +1,4 @@ import { Button } from "@/components/button"; -import Image from "next/image"; import type { ReactNode } from "react"; import { FeatureGridSection, @@ -64,21 +63,23 @@ export function FeaturesSection() { <>
- OpenUI Cloud routing requests across model providers - + + + OpenUI Cloud routing requests across model providers + - OpenUI Cloud validating and correcting model output - + + + OpenUI Cloud validating and correcting model output +
- OpenUI Cloud reports and presentation artifacts - + + + OpenUI Cloud reports and presentation artifacts + { + const root = document.documentElement; + + const syncSources = () => { + const dark = root.dataset.theme === "dark"; + const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; + const media = + dark === prefersDark ? "(prefers-color-scheme: dark)" : dark ? "all" : "not all"; + + document + .querySelectorAll("source[data-theme-source]") + .forEach((source) => { + if (source.media !== media) source.media = media; + }); + }; + + syncSources(); + + const observer = new MutationObserver(syncSources); + observer.observe(root, { + attributes: true, + attributeFilter: ["data-theme"], + }); + + return () => observer.disconnect(); + }, []); + + return null; +} diff --git a/docs/app/(home)/cloud/page.module.css b/docs/app/(home)/cloud/page.module.css index 3bdb0fb50..2049e929d 100644 --- a/docs/app/(home)/cloud/page.module.css +++ b/docs/app/(home)/cloud/page.module.css @@ -63,18 +63,6 @@ height: auto; } -.heroImageDark { - display: none; -} - -[data-theme="dark"] .heroImageLight { - display: none; -} - -[data-theme="dark"] .heroImageDark { - display: block; -} - @media (max-width: 1023px) { .title { font-size: inherit; diff --git a/docs/app/(home)/cloud/page.tsx b/docs/app/(home)/cloud/page.tsx index 74a0943a3..526888128 100644 --- a/docs/app/(home)/cloud/page.tsx +++ b/docs/app/(home)/cloud/page.tsx @@ -1,17 +1,14 @@ -import { ArrowUpRight } from "lucide-react"; import type { Metadata } from "next"; -import Image from "next/image"; -import { BevelButton } from "../components/Button/BevelButton"; import styles from "../page.module.css"; import { Footer } from "../sections/Footer/Footer"; -import { HeroSection } from "../sections/HeroSection/HeroSection"; import { LogoStrip } from "../sections/LogoStrip/LogoStrip"; import { CloudCtaSection } from "./CloudCtaSection"; +import { CloudHero } from "./CloudHero"; import { CloudIntegrationSection } from "./CloudIntegrationSection"; import { ComparisonSection } from "./ComparisonSection"; import { EnterpriseSection } from "./EnterpriseSection"; import { FeaturesSection } from "./FeaturesSection"; -import cloudStyles from "./page.module.css"; +import { ThemeImageController } from "./ThemeImageController"; export const metadata: Metadata = { title: "OpenUI Cloud - Managed Generative UI Infrastructure", @@ -44,99 +41,20 @@ export const metadata: Metadata = { export default function OpenUICloudPage() { return ( -
-
- - - OpenUI Cloud - - API for running OpenUI in Production - - } - subtitle={ - - One API for models, fallbacks, validation, rendering, and observability. - - } - smallSubtitle - tightDesktopSpacing={false} - splitLockup - flushInnerInlinePadding - commandSlot={ -
- } - /> - } - /> -
- } - showPlaygroundButton={false} - showGitHubBanner={false} - showTagline={false} - desktopPreviewSlot={ - <> - OpenUI Cloud production interface preview - - - } - mobilePreviewSlot={ - <> - OpenUI Cloud production interface preview - - - } - /> - - - - - - -
+ <> +
+
+ + + + + + + +
+
-
+ + ); } diff --git a/docs/app/(home)/components/Accordion/Accordion.tsx b/docs/app/(home)/components/Accordion/Accordion.tsx index 86cbb0fa5..e11326748 100644 --- a/docs/app/(home)/components/Accordion/Accordion.tsx +++ b/docs/app/(home)/components/Accordion/Accordion.tsx @@ -1,12 +1,46 @@ "use client"; -import { AnimatePresence, motion, type TargetAndTransition, type Transition } from "motion/react"; -import type { ReactNode } from "react"; +import type { CSSProperties, ReactNode } from "react"; const NO_SHADOW = "0px 0px 0px rgba(0,0,0,0)"; -const DEFAULT_PANEL_INITIAL = { height: 0, opacity: 0 }; -const DEFAULT_PANEL_ANIMATE = { height: "auto", opacity: 1 }; -const DEFAULT_PANEL_EXIT = { height: 0, opacity: 0 }; + +interface Transition { + duration?: number; + delay?: number; + ease?: readonly number[] | string; +} + +interface PanelTarget { + height?: CSSProperties["height"]; + opacity?: CSSProperties["opacity"]; + scale?: number; + y?: number; +} + +function transitionStyle(transition?: Transition): string | undefined { + if (!transition) return undefined; + const duration = transition.duration ?? 0.3; + const delay = transition.delay ?? 0; + const easing = + Array.isArray(transition.ease) && transition.ease.length === 4 + ? `cubic-bezier(${transition.ease.join(",")})` + : (transition.ease ?? "ease"); + return `all ${duration}s ${easing} ${delay}s`; +} + +function panelStyle(target: PanelTarget, transition?: Transition): CSSProperties { + const transforms = [ + target.y !== undefined ? `translateY(${target.y}px)` : "", + target.scale !== undefined ? `scale(${target.scale})` : "", + ].filter(Boolean); + + return { + height: target.height, + opacity: target.opacity, + transform: transforms.length > 0 ? transforms.join(" ") : undefined, + transition: transitionStyle(transition), + }; +} interface AccordionItemProps { open: boolean; @@ -36,19 +70,19 @@ export function AccordionItem({ children, }: AccordionItemProps) { return ( - {children} - +
); } @@ -56,9 +90,9 @@ interface AccordionPanelProps { open: boolean; className?: string; transition?: Transition; - initial?: TargetAndTransition; - animate?: TargetAndTransition; - exit?: TargetAndTransition; + initial?: PanelTarget; + animate?: PanelTarget; + exit?: PanelTarget; children: ReactNode; } @@ -66,24 +100,14 @@ export function AccordionPanel({ open, className, transition, - initial = DEFAULT_PANEL_INITIAL, - animate = DEFAULT_PANEL_ANIMATE, - exit = DEFAULT_PANEL_EXIT, + animate = { height: "auto", opacity: 1 }, children, }: AccordionPanelProps) { + if (!open) return null; + return ( - - {open && ( - - {children} - - )} - +
+ {children} +
); } diff --git a/docs/app/(home)/components/GitHubButton/GitHubButton.tsx b/docs/app/(home)/components/GitHubButton/GitHubButton.tsx index 666e6a92a..703690d1f 100644 --- a/docs/app/(home)/components/GitHubButton/GitHubButton.tsx +++ b/docs/app/(home)/components/GitHubButton/GitHubButton.tsx @@ -1,7 +1,7 @@ "use client"; -import type { JSX, ReactNode } from "react"; import { GitHubIcon, useGitHubStarCount } from "@/components/brand-logo"; +import type { JSX, ReactNode } from "react"; import { PillLink } from "../Button/Button"; import styles from "./GitHubButton.module.css"; @@ -25,9 +25,12 @@ export function parseRepoFromUrl(href: string): string { * (count-up via requestAnimationFrame) and folds in the repo-parse + fallback * that Hero duplicated in two places. */ -export function useGitHubStars(hrefOrRepo: string, options?: { fallback?: number }): number { +export function useGitHubStars( + hrefOrRepo: string, + options?: { fallback?: number; live?: boolean }, +): number { const repo = hrefOrRepo.includes("github.com/") ? parseRepoFromUrl(hrefOrRepo) : hrefOrRepo; - const count = useGitHubStarCount(repo); + const count = useGitHubStarCount(repo, options?.live ?? true); return count ?? options?.fallback ?? GITHUB_STAR_FALLBACK; } @@ -73,6 +76,8 @@ export interface GitHubButtonProps { compact?: boolean; /** desktopGlow only: keep the black pill appearance in dark mode (skip the white flip). */ keepBlack?: boolean; + /** Fetch the live star count. Disable for critical navigation chrome. */ + liveCount?: boolean; } function cx(...classNames: Array): string { @@ -88,14 +93,40 @@ export function GitHubButton({ classes, compact, keepBlack, + liveCount = true, }: GitHubButtonProps): JSX.Element { if (variant === "desktopPill") { - return ; + return ( + + ); } if (variant === "desktopGlow") { - return ; + return ( + + ); } - return ; + return ( + + ); } // --- desktopPill (= HeroSection's DesktopGithubButton) ---------------------- @@ -129,14 +160,16 @@ function DesktopGlowVariant({ compact, keepBlack, arrow, + liveCount, }: { href: string; className?: string; compact?: boolean; keepBlack?: boolean; arrow?: ReactNode; + liveCount: boolean; }): JSX.Element { - const count = useGitHubStars(href); + const count = useGitHubStars(href, { live: liveCount }); const label = String(count); return ( diff --git a/docs/app/(home)/globals.css b/docs/app/(home)/globals.css index 41e11f1a6..90bb8ea02 100644 --- a/docs/app/(home)/globals.css +++ b/docs/app/(home)/globals.css @@ -1,4 +1,9 @@ .homeTheme { + /* The component-library secondary/tertiary defaults are intended for disabled + UI and fall below WCAG contrast when used as marketing copy. */ + --openui-text-neutral-secondary: #676767; + --openui-text-neutral-tertiary: #676767; + --home-color-text-primary: var(--text-neutral-primary); --home-color-text-inverse: var(--text-accent-primary); --home-color-text-muted: var(--text-neutral-secondary); @@ -109,5 +114,7 @@ /* Dark mode: flip the few hardcoded marketing surfaces/hairlines that don't ride on the --openui-* / --text-* tokens (those adapt automatically). */ [data-theme="dark"] .homeTheme { + --openui-text-neutral-secondary: #aaa; + --openui-text-neutral-tertiary: #aaa; --home-hairline: rgba(255, 255, 255, 0.07); } diff --git a/docs/app/(home)/layout.tsx b/docs/app/(home)/layout.tsx index b88e7e3f0..bc9442c11 100644 --- a/docs/app/(home)/layout.tsx +++ b/docs/app/(home)/layout.tsx @@ -1,12 +1,11 @@ -import { WebsiteThemeProvider } from "@/components/website-theme-provider"; import "./globals.css"; import { Navbar } from "./sections/Navbar/Navbar"; export default function Layout({ children }: { children: React.ReactNode }) { return ( - + <>
{children}
-
+ ); } diff --git a/docs/app/(home)/sections/Footer/Footer.tsx b/docs/app/(home)/sections/Footer/Footer.tsx index ecb762192..5654e8390 100644 --- a/docs/app/(home)/sections/Footer/Footer.tsx +++ b/docs/app/(home)/sections/Footer/Footer.tsx @@ -1,6 +1,4 @@ -"use client"; import svgPaths from "@/imports/svg-urruvoh2be"; -import { useId } from "react"; import styles from "./Footer.module.css"; // --------------------------------------------------------------------------- @@ -55,9 +53,8 @@ const SOCIAL_LINKS: SocialLink[] = [ // Sub-components // --------------------------------------------------------------------------- -function SocialIcon({ link }: { link: SocialLink }) { - const uniqueId = useId(); - const clipPathId = link.clipId ? `${link.clipId}-${uniqueId}` : undefined; +function SocialIcon({ link, idPrefix }: { link: SocialLink; idPrefix: string }) { + const clipPathId = link.clipId ? `${idPrefix}-${link.clipId}` : undefined; const svgContent = clipPathId ? ( @@ -95,11 +92,11 @@ function SocialIcon({ link }: { link: SocialLink }) { ); } -function SocialIcons() { +function SocialIcons({ idPrefix }: { idPrefix: string }) { return (
{SOCIAL_LINKS.map((link) => ( - + ))}
); @@ -123,12 +120,7 @@ function ThesysLogo() { function HandcraftedMascot() { return ( // eslint-disable-next-line @next/next/no-img-element - + ); } @@ -170,14 +162,14 @@ export function Footer() {

355 Bryant St, San Francisco, CA 94107

- +

© {new Date().getFullYear()} Thesys Inc. All Rights Reserved

- +

© {new Date().getFullYear()} Thesys Inc. All Rights Reserved diff --git a/docs/app/(home)/sections/LogoStrip/LogoStrip.tsx b/docs/app/(home)/sections/LogoStrip/LogoStrip.tsx index a3e319c3b..ea84aab62 100644 --- a/docs/app/(home)/sections/LogoStrip/LogoStrip.tsx +++ b/docs/app/(home)/sections/LogoStrip/LogoStrip.tsx @@ -22,10 +22,10 @@ const HOME_LOGOS: Logo[] = [ const CLOUD_LOGOS: Logo[] = [ { src: "/logos/entelligence.svg", alt: "Entelligence" }, - { src: "/logos/pointlabs.svg", alt: "Pointlabs" }, + { src: "/logos/pointlabs.webp", alt: "Pointlabs" }, { src: "/logos/wisdom.svg", alt: "Wisdom" }, { src: "/logos/mili.svg", alt: "Mili" }, - { src: "/logos/ficell-labs.svg", alt: "Ficell Labs" }, + { src: "/logos/ficell-labs.webp", alt: "Ficell Labs" }, ]; /* The track holds SETS copies of the logo list and animates by -50%, so the diff --git a/docs/app/(home)/sections/Navbar/Navbar.tsx b/docs/app/(home)/sections/Navbar/Navbar.tsx index 4f93a0e1a..20c992804 100644 --- a/docs/app/(home)/sections/Navbar/Navbar.tsx +++ b/docs/app/(home)/sections/Navbar/Navbar.tsx @@ -1,5 +1,3 @@ -"use client"; - import { SiteMarketingHeader } from "@/components/site-marketing-header"; export function Navbar() { diff --git a/docs/app/blog/fumadocs.css b/docs/app/blog/fumadocs.css new file mode 100644 index 000000000..81d999ee3 --- /dev/null +++ b/docs/app/blog/fumadocs.css @@ -0,0 +1,2 @@ +@reference "../global.css"; +@import "fumadocs-ui/css/preset.css"; diff --git a/docs/app/blog/layout.tsx b/docs/app/blog/layout.tsx index 184c4659a..a461173ca 100644 --- a/docs/app/blog/layout.tsx +++ b/docs/app/blog/layout.tsx @@ -1,7 +1,8 @@ -import "../(home)/globals.css"; import { WebsiteThemeProvider } from "@/components/website-theme-provider"; import type { Metadata } from "next"; +import "../(home)/globals.css"; import { BlogNavbar } from "./components/BlogNavbar"; +import "./fumadocs.css"; export const metadata: Metadata = { alternates: { diff --git a/docs/app/chat/_components/agent-surfaces/cloud-agent-surface.tsx b/docs/app/chat/_components/agent-surfaces/cloud-agent-surface.tsx index 16125deb7..58ec05d9b 100644 --- a/docs/app/chat/_components/agent-surfaces/cloud-agent-surface.tsx +++ b/docs/app/chat/_components/agent-surfaces/cloud-agent-surface.tsx @@ -4,7 +4,7 @@ import { createCloudChatLLM } from "@/lib/openui-cloud/chat-llm"; import { DEFAULT_MODEL } from "@/lib/openui-cloud/models"; import { CLOUD_USER_ID_HEADER, getOrCreateCloudUserId } from "@/lib/openui-cloud/user-id"; import { defineArtifactCategories } from "@openuidev/react-headless"; -import { AgentInterface } from "@openuidev/react-ui"; +import { AgentInterface } from "@openuidev/react-ui/AgentInterface"; import { chatLibrary, presentationArtifactRenderer, diff --git a/docs/app/chat/_components/agent-surfaces/cloud-model-switcher.tsx b/docs/app/chat/_components/agent-surfaces/cloud-model-switcher.tsx index 8014f7e27..c0c1d9586 100644 --- a/docs/app/chat/_components/agent-surfaces/cloud-model-switcher.tsx +++ b/docs/app/chat/_components/agent-surfaces/cloud-model-switcher.tsx @@ -8,7 +8,7 @@ import { SelectItem, SelectLabel, SelectTrigger, -} from "@openuidev/react-ui"; +} from "@openuidev/react-ui/Select"; import { useMemo } from "react"; import styles from "../../chat-page.module.css"; diff --git a/docs/app/chat/_components/agent-surfaces/oss-agent-surface.tsx b/docs/app/chat/_components/agent-surfaces/oss-agent-surface.tsx index 0ad98166d..afabe2cf3 100644 --- a/docs/app/chat/_components/agent-surfaces/oss-agent-surface.tsx +++ b/docs/app/chat/_components/agent-surfaces/oss-agent-surface.tsx @@ -1,12 +1,8 @@ "use client"; import { isDemoCreditsErrorPayload } from "@/lib/demo-credits"; -import { - AgentInterface, - openAIAdapter, - openAIMessageFormat, - type ChatLLM, -} from "@openuidev/react-ui"; +import { openAIAdapter, openAIMessageFormat, type ChatLLM } from "@openuidev/react-headless"; +import { AgentInterface } from "@openuidev/react-ui/AgentInterface"; import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib"; import { useMemo } from "react"; diff --git a/docs/app/chat/_components/chat-page-client.tsx b/docs/app/chat/_components/chat-page-client.tsx index 64d711e5a..9531b0ceb 100644 --- a/docs/app/chat/_components/chat-page-client.tsx +++ b/docs/app/chat/_components/chat-page-client.tsx @@ -5,10 +5,17 @@ import { OPENUI_CLOUD_UNAVAILABLE_MESSAGE } from "@/lib/openui-cloud/errors"; import dynamic from "next/dynamic"; import { Component, useCallback, useState, type ReactNode } from "react"; import styles from "../chat-page.module.css"; -import { OssAgentSurface } from "./agent-surfaces/oss-agent-surface"; import { ChatPageHeader } from "./chat-page-header"; import type { ChatMode } from "./chat-types"; +const OssAgentSurface = dynamic( + () => import("./agent-surfaces/oss-agent-surface").then((module) => module.OssAgentSurface), + { + ssr: false, + loading: () => , + }, +); + const CloudAgentSurface = dynamic( () => import("./agent-surfaces/cloud-agent-surface").then((module) => module.CloudAgentSurface), { diff --git a/docs/app/chat/layout.tsx b/docs/app/chat/layout.tsx index 87e87c6a8..481d32697 100644 --- a/docs/app/chat/layout.tsx +++ b/docs/app/chat/layout.tsx @@ -1,4 +1,6 @@ import { WebsiteThemeProvider } from "@/components/website-theme-provider"; +import "@openuidev/react-ui/styles/index.css"; +import "@openuidev/thesys/styles.css"; import type { ReactNode } from "react"; export default function ChatLayout({ children }: { children: ReactNode }) { diff --git a/docs/app/compare/_components/agent-surfaces/cloud-agent-surface.tsx b/docs/app/compare/_components/agent-surfaces/cloud-agent-surface.tsx index fd65d8217..bb8f5f309 100644 --- a/docs/app/compare/_components/agent-surfaces/cloud-agent-surface.tsx +++ b/docs/app/compare/_components/agent-surfaces/cloud-agent-surface.tsx @@ -2,7 +2,7 @@ import { createCloudChatLLM } from "@/lib/openui-cloud/chat-llm"; import { CLOUD_USER_ID_HEADER, getOrCreateCloudUserId } from "@/lib/openui-cloud/user-id"; -import { AgentInterface } from "@openuidev/react-ui"; +import { AgentInterface } from "@openuidev/react-ui/AgentInterface"; import { artifactRenderers, chatLibrary, useOpenuiCloudStorage } from "@openuidev/thesys"; import { useMemo, useState } from "react"; import type { ComparisonControllerRegistry } from "../comparison-mode-controller"; diff --git a/docs/app/compare/_components/agent-surfaces/markdown-agent-surface.tsx b/docs/app/compare/_components/agent-surfaces/markdown-agent-surface.tsx index 4ecb0e523..6b6b4e1e2 100644 --- a/docs/app/compare/_components/agent-surfaces/markdown-agent-surface.tsx +++ b/docs/app/compare/_components/agent-surfaces/markdown-agent-surface.tsx @@ -1,6 +1,6 @@ "use client"; -import { AgentInterface } from "@openuidev/react-ui"; +import { AgentInterface } from "@openuidev/react-ui/AgentInterface"; import type { ComparisonControllerRegistry } from "../comparison-mode-controller"; import { ComparisonModeControllerBridge } from "../comparison-mode-controller"; import { ComparisonSurfaceWelcome } from "./comparison-surface-welcome"; diff --git a/docs/app/compare/_components/agent-surfaces/oss-agent-surface.tsx b/docs/app/compare/_components/agent-surfaces/oss-agent-surface.tsx index 20ef99a7b..1ef633cbc 100644 --- a/docs/app/compare/_components/agent-surfaces/oss-agent-surface.tsx +++ b/docs/app/compare/_components/agent-surfaces/oss-agent-surface.tsx @@ -1,6 +1,6 @@ "use client"; -import { AgentInterface } from "@openuidev/react-ui"; +import { AgentInterface } from "@openuidev/react-ui/AgentInterface"; import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib"; import type { ComparisonControllerRegistry } from "../comparison-mode-controller"; import { ComparisonModeControllerBridge } from "../comparison-mode-controller"; diff --git a/docs/app/compare/_components/agent-surfaces/use-comparison-chat-llm.ts b/docs/app/compare/_components/agent-surfaces/use-comparison-chat-llm.ts index 0272e39bb..c9b098175 100644 --- a/docs/app/compare/_components/agent-surfaces/use-comparison-chat-llm.ts +++ b/docs/app/compare/_components/agent-surfaces/use-comparison-chat-llm.ts @@ -1,7 +1,7 @@ "use client"; import { isDemoCreditsErrorPayload } from "@/lib/demo-credits"; -import { openAIAdapter, openAIMessageFormat, type ChatLLM } from "@openuidev/react-ui"; +import { openAIAdapter, openAIMessageFormat, type ChatLLM } from "@openuidev/react-headless"; import { useMemo } from "react"; type ComparisonResponseMode = "markdown" | "openui"; diff --git a/docs/app/compare/_components/compare-page-client.tsx b/docs/app/compare/_components/compare-page-client.tsx index 5d6059246..e9540418c 100644 --- a/docs/app/compare/_components/compare-page-client.tsx +++ b/docs/app/compare/_components/compare-page-client.tsx @@ -14,7 +14,6 @@ import { } from "react"; import styles from "../chat-page.module.css"; import { MarkdownAgentSurface } from "./agent-surfaces/markdown-agent-surface"; -import { OssAgentSurface } from "./agent-surfaces/oss-agent-surface"; import { ChatPageHeader } from "./chat-page-header"; import { COMPARISON_MODE_LABELS, @@ -36,6 +35,14 @@ import { const ALL_MODES = ["markdown", "oss", "cloud"] as const; +const OssAgentSurface = dynamic( + () => import("./agent-surfaces/oss-agent-surface").then((module) => module.OssAgentSurface), + { + ssr: false, + loading: () => , + }, +); + const CloudAgentSurface = dynamic( () => import("./agent-surfaces/cloud-agent-surface").then((module) => module.CloudAgentSurface), { diff --git a/docs/app/compare/layout.tsx b/docs/app/compare/layout.tsx index 62d083a86..9e99f49ec 100644 --- a/docs/app/compare/layout.tsx +++ b/docs/app/compare/layout.tsx @@ -1,4 +1,6 @@ import { WebsiteThemeProvider } from "@/components/website-theme-provider"; +import "@openuidev/react-ui/styles/index.css"; +import "@openuidev/thesys/styles.css"; import type { ReactNode } from "react"; export default function CompareLayout({ children }: { children: ReactNode }) { diff --git a/docs/app/components/blocks/accordion/page.tsx b/docs/app/components/blocks/accordion/page.tsx index 262644945..b5fa8ccc9 100644 --- a/docs/app/components/blocks/accordion/page.tsx +++ b/docs/app/components/blocks/accordion/page.tsx @@ -6,12 +6,14 @@ import { AccordionContent, AccordionItem, AccordionTrigger, +} from "@openuidev/react-ui/Accordion"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@openuidev/react-ui"; +} from "@openuidev/react-ui/Select"; import { useState } from "react"; import styles from "./page.module.css"; diff --git a/docs/app/components/blocks/button-group/page.tsx b/docs/app/components/blocks/button-group/page.tsx index bdac44a10..59016616d 100644 --- a/docs/app/components/blocks/button-group/page.tsx +++ b/docs/app/components/blocks/button-group/page.tsx @@ -6,16 +6,16 @@ import { PreviewSection, SegmentedToggle, } from "@components/components/preview"; +import { Button } from "@openuidev/react-ui/Button"; +import { Buttons } from "@openuidev/react-ui/Buttons"; +import { IconButton } from "@openuidev/react-ui/IconButton"; import { - Button, - Buttons, - IconButton, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, -} from "@openuidev/react-ui"; +} from "@openuidev/react-ui/Select"; import { useState } from "react"; import styles from "./page.module.css"; diff --git a/docs/app/components/blocks/callout/page.tsx b/docs/app/components/blocks/callout/page.tsx index c4f8a1caf..bda20cc81 100644 --- a/docs/app/components/blocks/callout/page.tsx +++ b/docs/app/components/blocks/callout/page.tsx @@ -7,8 +7,8 @@ import { SelectItem, SelectTrigger, SelectValue, - TextCallout, -} from "@openuidev/react-ui"; +} from "@openuidev/react-ui/Select"; +import { TextCallout } from "@openuidev/react-ui/TextCallout"; import { useState } from "react"; type CalloutVariant = "neutral" | "info" | "warning" | "success" | "danger"; diff --git a/docs/app/components/blocks/charts/area/page.tsx b/docs/app/components/blocks/charts/area/page.tsx index 521cf57e4..047940cfb 100644 --- a/docs/app/components/blocks/charts/area/page.tsx +++ b/docs/app/components/blocks/charts/area/page.tsx @@ -2,7 +2,16 @@ import ClientOnly from "@components/blocks/_components/ClientOnly"; import { BlockVariantPreview, PreviewPage, PreviewSection } from "@components/components/preview"; -import { AreaChart, AreaChartCondensed } from "@openuidev/react-ui"; +import dynamic from "next/dynamic"; + +const AreaChart = dynamic( + () => import("@openuidev/react-ui/Charts").then((module) => module.AreaChart), + { ssr: false }, +); +const AreaChartCondensed = dynamic( + () => import("@openuidev/react-ui/Charts").then((module) => module.AreaChartCondensed), + { ssr: false }, +); const areaData = [ { month: "Jan", desktop: 150, mobile: 90 }, diff --git a/docs/app/components/blocks/charts/bar/page.tsx b/docs/app/components/blocks/charts/bar/page.tsx index 811a83052..79f678405 100644 --- a/docs/app/components/blocks/charts/bar/page.tsx +++ b/docs/app/components/blocks/charts/bar/page.tsx @@ -2,7 +2,16 @@ import ClientOnly from "@components/blocks/_components/ClientOnly"; import { BlockVariantPreview, PreviewPage, PreviewSection } from "@components/components/preview"; -import { BarChart, BarChartCondensed } from "@openuidev/react-ui"; +import dynamic from "next/dynamic"; + +const BarChart = dynamic( + () => import("@openuidev/react-ui/Charts").then((module) => module.BarChart), + { ssr: false }, +); +const BarChartCondensed = dynamic( + () => import("@openuidev/react-ui/Charts").then((module) => module.BarChartCondensed), + { ssr: false }, +); const barData = [ { month: "Jan", desktop: 150, mobile: 90 }, diff --git a/docs/app/components/blocks/charts/donut/page.tsx b/docs/app/components/blocks/charts/donut/page.tsx index 59af6b50c..5666ea08b 100644 --- a/docs/app/components/blocks/charts/donut/page.tsx +++ b/docs/app/components/blocks/charts/donut/page.tsx @@ -2,7 +2,12 @@ import BlocksDocPage from "@components/blocks/_components/BlocksDocPage"; import ClientOnly from "@components/blocks/_components/ClientOnly"; -import { PieChart } from "@openuidev/react-ui"; +import dynamic from "next/dynamic"; + +const PieChart = dynamic( + () => import("@openuidev/react-ui/Charts").then((module) => module.PieChart), + { ssr: false }, +); const donutData = [ { category: "January", value: 1250 }, diff --git a/docs/app/components/blocks/charts/line/page.tsx b/docs/app/components/blocks/charts/line/page.tsx index 3c56d8c08..4e4fe5a6a 100644 --- a/docs/app/components/blocks/charts/line/page.tsx +++ b/docs/app/components/blocks/charts/line/page.tsx @@ -2,7 +2,16 @@ import ClientOnly from "@components/blocks/_components/ClientOnly"; import { BlockVariantPreview, PreviewPage, PreviewSection } from "@components/components/preview"; -import { LineChart, LineChartCondensed } from "@openuidev/react-ui"; +import dynamic from "next/dynamic"; + +const LineChart = dynamic( + () => import("@openuidev/react-ui/Charts").then((module) => module.LineChart), + { ssr: false }, +); +const LineChartCondensed = dynamic( + () => import("@openuidev/react-ui/Charts").then((module) => module.LineChartCondensed), + { ssr: false }, +); const lineData = [ { month: "Jan", desktop: 150, mobile: 90 }, diff --git a/docs/app/components/blocks/charts/pie/page.tsx b/docs/app/components/blocks/charts/pie/page.tsx index 6b511208b..e2372159d 100644 --- a/docs/app/components/blocks/charts/pie/page.tsx +++ b/docs/app/components/blocks/charts/pie/page.tsx @@ -2,7 +2,12 @@ import BlocksDocPage from "@components/blocks/_components/BlocksDocPage"; import ClientOnly from "@components/blocks/_components/ClientOnly"; -import { PieChart } from "@openuidev/react-ui"; +import dynamic from "next/dynamic"; + +const PieChart = dynamic( + () => import("@openuidev/react-ui/Charts").then((module) => module.PieChart), + { ssr: false }, +); const pieData = [ { category: "January", value: 1250 }, diff --git a/docs/app/components/blocks/charts/radial/page.tsx b/docs/app/components/blocks/charts/radial/page.tsx index 6bcf6989b..e32be7355 100644 --- a/docs/app/components/blocks/charts/radial/page.tsx +++ b/docs/app/components/blocks/charts/radial/page.tsx @@ -2,7 +2,12 @@ import BlocksDocPage from "@components/blocks/_components/BlocksDocPage"; import ClientOnly from "@components/blocks/_components/ClientOnly"; -import { RadialChart } from "@openuidev/react-ui"; +import dynamic from "next/dynamic"; + +const RadialChart = dynamic( + () => import("@openuidev/react-ui/Charts").then((module) => module.RadialChart), + { ssr: false }, +); const radialData = [ { month: "January", value: 1250 }, diff --git a/docs/app/components/blocks/charts/scatter/page.tsx b/docs/app/components/blocks/charts/scatter/page.tsx index d8abd6c5b..d30b081ba 100644 --- a/docs/app/components/blocks/charts/scatter/page.tsx +++ b/docs/app/components/blocks/charts/scatter/page.tsx @@ -2,7 +2,12 @@ import BlocksDocPage from "@components/blocks/_components/BlocksDocPage"; import ClientOnly from "@components/blocks/_components/ClientOnly"; -import { ScatterChart } from "@openuidev/react-ui"; +import dynamic from "next/dynamic"; + +const ScatterChart = dynamic( + () => import("@openuidev/react-ui/Charts").then((module) => module.ScatterChart), + { ssr: false }, +); const scatterData = [ { diff --git a/docs/app/components/blocks/checkbox-group/page.tsx b/docs/app/components/blocks/checkbox-group/page.tsx index ee9b71871..2ef4fad41 100644 --- a/docs/app/components/blocks/checkbox-group/page.tsx +++ b/docs/app/components/blocks/checkbox-group/page.tsx @@ -1,7 +1,8 @@ "use client"; import BlocksDocPage from "@components/blocks/_components/BlocksDocPage"; -import { CheckBoxGroup, CheckBoxItem } from "@openuidev/react-ui"; +import { CheckBoxGroup } from "@openuidev/react-ui/CheckBoxGroup"; +import { CheckBoxItem } from "@openuidev/react-ui/CheckBoxItem"; function CheckboxGroupPreview() { return ( diff --git a/docs/app/components/blocks/date-picker/page.tsx b/docs/app/components/blocks/date-picker/page.tsx index 61f749a56..79ce6f15f 100644 --- a/docs/app/components/blocks/date-picker/page.tsx +++ b/docs/app/components/blocks/date-picker/page.tsx @@ -1,7 +1,7 @@ "use client"; import BlocksDocPage from "@components/blocks/_components/BlocksDocPage"; -import { DatePicker } from "@openuidev/react-ui"; +import { DatePicker } from "@openuidev/react-ui/DatePicker"; function DatePickerPreview() { return ; diff --git a/docs/app/components/blocks/image-items/page.tsx b/docs/app/components/blocks/image-items/page.tsx index 88f86f559..e1fb4b13e 100644 --- a/docs/app/components/blocks/image-items/page.tsx +++ b/docs/app/components/blocks/image-items/page.tsx @@ -1,7 +1,7 @@ "use client"; import { BlockVariantPreview, PreviewPage, PreviewSection } from "@components/components/preview"; -import { ImageBlock } from "@openuidev/react-ui"; +import { ImageBlock } from "@openuidev/react-ui/ImageBlock"; import styles from "./page.module.css"; const DEFAULT_IMAGE = diff --git a/docs/app/components/blocks/input-field/page.tsx b/docs/app/components/blocks/input-field/page.tsx index 81c789160..26d927ee1 100644 --- a/docs/app/components/blocks/input-field/page.tsx +++ b/docs/app/components/blocks/input-field/page.tsx @@ -1,7 +1,7 @@ "use client"; import BlocksDocPage from "@components/blocks/_components/BlocksDocPage"; -import { Input } from "@openuidev/react-ui"; +import { Input } from "@openuidev/react-ui/Input"; function InputFieldPreview() { return ; diff --git a/docs/app/components/blocks/lists/page.tsx b/docs/app/components/blocks/lists/page.tsx index 8e07c26ce..71d3a51f3 100644 --- a/docs/app/components/blocks/lists/page.tsx +++ b/docs/app/components/blocks/lists/page.tsx @@ -6,7 +6,8 @@ import { PreviewSection, SegmentedToggle, } from "@components/components/preview"; -import { ListBlock, ListItem } from "@openuidev/react-ui"; +import { ListBlock } from "@openuidev/react-ui/ListBlock"; +import { ListItem } from "@openuidev/react-ui/ListItem"; import { ChevronRight, FileText, Globe, LayoutPanelTop } from "lucide-react"; import { useMemo, useState } from "react"; import styles from "./page.module.css"; diff --git a/docs/app/components/blocks/radio-button-group/page.tsx b/docs/app/components/blocks/radio-button-group/page.tsx index 3b0010613..1a6047838 100644 --- a/docs/app/components/blocks/radio-button-group/page.tsx +++ b/docs/app/components/blocks/radio-button-group/page.tsx @@ -1,7 +1,8 @@ "use client"; import BlocksDocPage from "@components/blocks/_components/BlocksDocPage"; -import { RadioGroup, RadioItem } from "@openuidev/react-ui"; +import { RadioGroup } from "@openuidev/react-ui/RadioGroup"; +import { RadioItem } from "@openuidev/react-ui/RadioItem"; function RadioButtonGroupPreview() { return ( diff --git a/docs/app/components/blocks/select/page.tsx b/docs/app/components/blocks/select/page.tsx index cde712be4..5cdd47bec 100644 --- a/docs/app/components/blocks/select/page.tsx +++ b/docs/app/components/blocks/select/page.tsx @@ -10,7 +10,7 @@ import { SelectSeparator, SelectTrigger, SelectValue, -} from "@openuidev/react-ui"; +} from "@openuidev/react-ui/Select"; function SelectPreview() { return ( diff --git a/docs/app/components/blocks/tables/page.tsx b/docs/app/components/blocks/tables/page.tsx index 5f97aa359..d078e5075 100644 --- a/docs/app/components/blocks/tables/page.tsx +++ b/docs/app/components/blocks/tables/page.tsx @@ -8,8 +8,8 @@ import { TableHead, TableHeader, TableRow, - Tag, -} from "@openuidev/react-ui"; +} from "@openuidev/react-ui/Table"; +import { Tag } from "@openuidev/react-ui/Tag"; function TablePreview() { return ( diff --git a/docs/app/components/blocks/tabs/page.tsx b/docs/app/components/blocks/tabs/page.tsx index d4f3fcf0f..575622286 100644 --- a/docs/app/components/blocks/tabs/page.tsx +++ b/docs/app/components/blocks/tabs/page.tsx @@ -6,7 +6,7 @@ import { PreviewSection, SegmentedToggle, } from "@components/components/preview"; -import { Tabs, TabsList, TabsTrigger } from "@openuidev/react-ui"; +import { Tabs, TabsList, TabsTrigger } from "@openuidev/react-ui/Tabs"; import { useState } from "react"; import styles from "./page.module.css"; diff --git a/docs/app/components/blocks/tag-item/page.tsx b/docs/app/components/blocks/tag-item/page.tsx index 0f20ed377..005c288cd 100644 --- a/docs/app/components/blocks/tag-item/page.tsx +++ b/docs/app/components/blocks/tag-item/page.tsx @@ -1,7 +1,7 @@ "use client"; import BlocksDocPage from "@components/blocks/_components/BlocksDocPage"; -import { Tag } from "@openuidev/react-ui"; +import { Tag } from "@openuidev/react-ui/Tag"; function TagItemPreview() { const tags = [ diff --git a/docs/app/components/blocks/toggle-group/page.tsx b/docs/app/components/blocks/toggle-group/page.tsx index 2c5de58b6..77606633c 100644 --- a/docs/app/components/blocks/toggle-group/page.tsx +++ b/docs/app/components/blocks/toggle-group/page.tsx @@ -1,7 +1,8 @@ "use client"; import BlocksDocPage from "@components/blocks/_components/BlocksDocPage"; -import { SwitchGroup, SwitchItem } from "@openuidev/react-ui"; +import { SwitchGroup } from "@openuidev/react-ui/SwitchGroup"; +import { SwitchItem } from "@openuidev/react-ui/SwitchItem"; function ToggleGroupPreview() { return ( diff --git a/docs/app/components/components/ProAccessBanner/ProAccessBanner.tsx b/docs/app/components/components/ProAccessBanner/ProAccessBanner.tsx index 6be64f5cf..919fbadef 100644 --- a/docs/app/components/components/ProAccessBanner/ProAccessBanner.tsx +++ b/docs/app/components/components/ProAccessBanner/ProAccessBanner.tsx @@ -1,6 +1,6 @@ "use client"; -import { Button } from "@openuidev/react-ui"; +import { Button } from "@openuidev/react-ui/Button"; import styles from "./ProAccessBanner.module.css"; interface ProAccessBannerProps { diff --git a/docs/app/components/components/ThemeBuilder/ChartsSection.tsx b/docs/app/components/components/ThemeBuilder/ChartsSection.tsx index de7d2d738..da88598bd 100644 --- a/docs/app/components/components/ThemeBuilder/ChartsSection.tsx +++ b/docs/app/components/components/ThemeBuilder/ChartsSection.tsx @@ -7,7 +7,7 @@ import { PieChart, RadialChart, ScatterChart, -} from "@openuidev/react-ui"; +} from "@openuidev/react-ui/Charts"; import styles from "./realBlocksCanvas.module.css"; const MONTHLY_DATA = [ diff --git a/docs/app/components/components/ThemeBuilder/realBlocksCanvas.tsx b/docs/app/components/components/ThemeBuilder/realBlocksCanvas.tsx index 1c1d60c51..af7ab12a1 100644 --- a/docs/app/components/components/ThemeBuilder/realBlocksCanvas.tsx +++ b/docs/app/components/components/ThemeBuilder/realBlocksCanvas.tsx @@ -5,35 +5,37 @@ import { AccordionContent, AccordionItem, AccordionTrigger, - Button, - Buttons, - CheckBoxGroup, - CheckBoxItem, - DatePicker, - IconButton, - ImageBlock, - Input, - RadioGroup, - RadioItem, +} from "@openuidev/react-ui/Accordion"; +import { Button } from "@openuidev/react-ui/Button"; +import { Buttons } from "@openuidev/react-ui/Buttons"; +import { CheckBoxGroup } from "@openuidev/react-ui/CheckBoxGroup"; +import { CheckBoxItem } from "@openuidev/react-ui/CheckBoxItem"; +import { DatePicker } from "@openuidev/react-ui/DatePicker"; +import { IconButton } from "@openuidev/react-ui/IconButton"; +import { ImageBlock } from "@openuidev/react-ui/ImageBlock"; +import { Input } from "@openuidev/react-ui/Input"; +import { RadioGroup } from "@openuidev/react-ui/RadioGroup"; +import { RadioItem } from "@openuidev/react-ui/RadioItem"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, - SwitchGroup, - SwitchItem, +} from "@openuidev/react-ui/Select"; +import { SwitchGroup } from "@openuidev/react-ui/SwitchGroup"; +import { SwitchItem } from "@openuidev/react-ui/SwitchItem"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, - Tabs, - TabsList, - TabsTrigger, - Tag, - TextCallout, -} from "@openuidev/react-ui"; +} from "@openuidev/react-ui/Table"; +import { Tabs, TabsList, TabsTrigger } from "@openuidev/react-ui/Tabs"; +import { Tag } from "@openuidev/react-ui/Tag"; +import { TextCallout } from "@openuidev/react-ui/TextCallout"; import dynamic from "next/dynamic"; import { useState } from "react"; import styles from "./realBlocksCanvas.module.css"; diff --git a/docs/app/components/components/preview/PromptCopyButton.tsx b/docs/app/components/components/preview/PromptCopyButton.tsx index 8317e2816..d2b140d00 100644 --- a/docs/app/components/components/preview/PromptCopyButton.tsx +++ b/docs/app/components/components/preview/PromptCopyButton.tsx @@ -1,6 +1,6 @@ "use client"; -import { IconButton } from "@openuidev/react-ui"; +import { IconButton } from "@openuidev/react-ui/IconButton"; import { Check, Copy } from "lucide-react"; import { useEffect, useState } from "react"; diff --git a/docs/app/components/theme-builder/page.tsx b/docs/app/components/theme-builder/page.tsx index 13fb9b5aa..a141a590b 100644 --- a/docs/app/components/theme-builder/page.tsx +++ b/docs/app/components/theme-builder/page.tsx @@ -1,7 +1,13 @@ "use client"; import { useAppTheme } from "@components/components/AppThemeProvider/AppThemeProvider"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@openuidev/react-ui"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@openuidev/react-ui/Select"; import { ThemeProvider, swatch, diff --git a/docs/app/deferred-analytics.tsx b/docs/app/deferred-analytics.tsx new file mode 100644 index 000000000..735f6d689 --- /dev/null +++ b/docs/app/deferred-analytics.tsx @@ -0,0 +1,64 @@ +"use client"; + +import { useEffect } from "react"; + +const GA_ID = "G-MZ0TZ82NM2"; +const FALLBACK_DELAY_MS = 30_000; + +type AnalyticsWindow = Window & { + dataLayer?: unknown[][]; +}; + +export function DeferredAnalytics() { + useEffect(() => { + let loaded = false; + let idleCallbackId: number | undefined; + + const load = () => { + if (loaded) return; + loaded = true; + + const analyticsWindow = window as AnalyticsWindow; + analyticsWindow.dataLayer ??= []; + analyticsWindow.dataLayer.push(["js", new Date()]); + analyticsWindow.dataLayer.push(["config", GA_ID]); + + const script = document.createElement("script"); + script.async = true; + script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_ID}`; + document.head.appendChild(script); + }; + + const scheduleAfterNextPaint = () => { + window.requestAnimationFrame(() => { + if ("requestIdleCallback" in window) { + idleCallbackId = window.requestIdleCallback(load, { timeout: 2_000 }); + } else { + setTimeout(load, 0); + } + }); + }; + + const interactionEvents = ["pointerdown", "keydown", "touchstart"] as const; + interactionEvents.forEach((eventName) => { + window.addEventListener(eventName, scheduleAfterNextPaint, { + once: true, + passive: true, + }); + }); + + const fallbackTimer = window.setTimeout(load, FALLBACK_DELAY_MS); + + return () => { + window.clearTimeout(fallbackTimer); + if (idleCallbackId !== undefined && "cancelIdleCallback" in window) { + window.cancelIdleCallback(idleCallbackId); + } + interactionEvents.forEach((eventName) => { + window.removeEventListener(eventName, scheduleAfterNextPaint); + }); + }; + }, []); + + return null; +} diff --git a/docs/app/demo/github/components/ConversationPanel/ConversationPanel.tsx b/docs/app/demo/github/components/ConversationPanel/ConversationPanel.tsx index 63bb44b66..5090ecb2e 100644 --- a/docs/app/demo/github/components/ConversationPanel/ConversationPanel.tsx +++ b/docs/app/demo/github/components/ConversationPanel/ConversationPanel.tsx @@ -1,6 +1,6 @@ "use client"; -import { IconButton } from "@openuidev/react-ui"; +import { IconButton } from "@openuidev/react-ui/IconButton"; import { MarkDownRenderer } from "@openuidev/react-ui/MarkDownRenderer"; import { ChevronRight, MessageSquare, Send, Square } from "lucide-react"; import { useEffect, useRef, useState } from "react"; diff --git a/docs/app/demo/github/components/GitHubConnect/GitHubConnect.tsx b/docs/app/demo/github/components/GitHubConnect/GitHubConnect.tsx index 4b4c20f14..caf94ca3c 100644 --- a/docs/app/demo/github/components/GitHubConnect/GitHubConnect.tsx +++ b/docs/app/demo/github/components/GitHubConnect/GitHubConnect.tsx @@ -1,7 +1,7 @@ "use client"; import { GitHubIcon } from "@/components/brand-logo"; -import { Button } from "@openuidev/react-ui"; +import { Button } from "@openuidev/react-ui/Button"; import { Activity, Check, diff --git a/docs/app/demo/github/components/Modal/Modal.tsx b/docs/app/demo/github/components/Modal/Modal.tsx index 47b5d784d..e1ceca0d8 100644 --- a/docs/app/demo/github/components/Modal/Modal.tsx +++ b/docs/app/demo/github/components/Modal/Modal.tsx @@ -1,4 +1,4 @@ -import { IconButton } from "@openuidev/react-ui"; +import { IconButton } from "@openuidev/react-ui/IconButton"; import { X } from "lucide-react"; import type { ReactNode } from "react"; import { useCallback, useEffect } from "react"; diff --git a/docs/app/demo/github/components/PreviewPanel/PreviewPanel.tsx b/docs/app/demo/github/components/PreviewPanel/PreviewPanel.tsx index 78bfbf79c..7031afee2 100644 --- a/docs/app/demo/github/components/PreviewPanel/PreviewPanel.tsx +++ b/docs/app/demo/github/components/PreviewPanel/PreviewPanel.tsx @@ -1,6 +1,8 @@ import type { ActionEvent, ParseResult } from "@openuidev/react-lang"; import { Renderer } from "@openuidev/react-lang"; -import { IconButton, openuiLibrary, ThemeProvider } from "@openuidev/react-ui"; +import { openuiLibrary } from "@openuidev/react-ui/genui-lib"; +import { IconButton } from "@openuidev/react-ui/IconButton"; +import { ThemeProvider } from "@openuidev/react-ui/ThemeProvider"; import { Loader2, Maximize2, Monitor } from "lucide-react"; import { useCallback, useState } from "react"; import { Modal } from "../Modal/Modal"; diff --git a/docs/app/demo/github/layout.tsx b/docs/app/demo/github/layout.tsx index 76adb44c1..6b4a79cf9 100644 --- a/docs/app/demo/github/layout.tsx +++ b/docs/app/demo/github/layout.tsx @@ -1,5 +1,6 @@ import { WebsiteThemeProvider } from "@/components/website-theme-provider"; import { createPageMetadata } from "@/lib/page-metadata"; +import "@openuidev/react-ui/styles/index.css"; import type { ReactNode } from "react"; import "./layout.css"; diff --git a/docs/app/demo/github/page.tsx b/docs/app/demo/github/page.tsx index de85d4c22..a3180600f 100644 --- a/docs/app/demo/github/page.tsx +++ b/docs/app/demo/github/page.tsx @@ -3,7 +3,7 @@ import { DemoCreditsDialog } from "@/components/DemoCreditsDialog"; import { isDemoCreditsErrorPayload } from "@/lib/demo-credits"; import { mergeStatements } from "@openuidev/react-lang"; -import { Button } from "@openuidev/react-ui"; +import { Button } from "@openuidev/react-ui/Button"; import { encode } from "gpt-tokenizer"; import { Activity, @@ -18,12 +18,12 @@ import { type LucideIcon, } from "lucide-react"; import { useTheme } from "next-themes"; +import dynamic from "next/dynamic"; import { Highlight, themes } from "prism-react-renderer"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { ConversationPanel } from "./components/ConversationPanel/ConversationPanel"; import { GitHubConnect } from "./components/GitHubConnect/GitHubConnect"; import { Header } from "./components/Header/Header"; -import { PreviewPanel } from "./components/PreviewPanel/PreviewPanel"; import { SavedSidebar } from "./components/SavedSidebar/SavedSidebar"; import { GITHUB_DEMO_MODEL_LABEL, @@ -44,6 +44,11 @@ import { type SavedDashboard, } from "./saved/store"; +const PreviewPanel = dynamic( + () => import("./components/PreviewPanel/PreviewPanel").then((module) => module.PreviewPanel), + { ssr: false }, +); + // ── Helpers ───────────────────────────────────────────────────────────────── function extractCodeOnly(response: string): string | null { diff --git a/docs/app/demos/components/CatalogPanel/CatalogPanel.tsx b/docs/app/demos/components/CatalogPanel/CatalogPanel.tsx index 0c8d392e2..f1a2e5da7 100644 --- a/docs/app/demos/components/CatalogPanel/CatalogPanel.tsx +++ b/docs/app/demos/components/CatalogPanel/CatalogPanel.tsx @@ -1,7 +1,7 @@ "use client"; import type { ComponentGroup } from "@openuidev/react-lang"; -import { openuiLibrary } from "@openuidev/react-ui"; +import { openuiLibrary } from "@openuidev/react-ui/genui-lib"; import { ChevronLeft, ChevronRight, LayoutList } from "lucide-react"; import { useState } from "react"; import "./CatalogPanel.css"; diff --git a/docs/app/demos/components/PreviewPanel/PreviewPanel.tsx b/docs/app/demos/components/PreviewPanel/PreviewPanel.tsx index 022f95fca..9c92ac9d6 100644 --- a/docs/app/demos/components/PreviewPanel/PreviewPanel.tsx +++ b/docs/app/demos/components/PreviewPanel/PreviewPanel.tsx @@ -1,6 +1,7 @@ import type { ParseResult } from "@openuidev/react-lang"; import { Renderer } from "@openuidev/react-lang"; -import { openuiLibrary, ThemeProvider } from "@openuidev/react-ui"; +import { openuiLibrary } from "@openuidev/react-ui/genui-lib"; +import { ThemeProvider } from "@openuidev/react-ui/ThemeProvider"; import { Loader2, Maximize2, Monitor } from "lucide-react"; import { useCallback, useEffect, useMemo, useState } from "react"; import type { Theme } from "../../constants"; diff --git a/docs/app/demos/layout.tsx b/docs/app/demos/layout.tsx index d061b81dc..1b17411a3 100644 --- a/docs/app/demos/layout.tsx +++ b/docs/app/demos/layout.tsx @@ -1,5 +1,6 @@ import { WebsiteThemeProvider } from "@/components/website-theme-provider"; import { createPageMetadata } from "@/lib/page-metadata"; +import "@openuidev/react-ui/styles/index.css"; import type { ReactNode } from "react"; import "./layout.css"; diff --git a/docs/app/demos/page.tsx b/docs/app/demos/page.tsx index d1c3cb970..91a17ea46 100644 --- a/docs/app/demos/page.tsx +++ b/docs/app/demos/page.tsx @@ -4,12 +4,17 @@ import { DemoCreditsDialog } from "@/components/DemoCreditsDialog"; import { isDemoCreditsErrorPayload } from "@/lib/demo-credits"; import { Send, Square } from "lucide-react"; import { useTheme } from "next-themes"; +import dynamic from "next/dynamic"; import { useCallback, useRef, useState } from "react"; import { CodePanel } from "./components/CodePanel/CodePanel"; import { Header } from "./components/Header/Header"; -import { PreviewPanel } from "./components/PreviewPanel/PreviewPanel"; import { MODELS, STARTER_PROMPTS, type Model, type Status, type Theme } from "./constants"; +const PreviewPanel = dynamic( + () => import("./components/PreviewPanel/PreviewPanel").then((module) => module.PreviewPanel), + { ssr: false }, +); + export default function DemosPage() { const { theme, setTheme } = useTheme(); const [prompt, setPrompt] = useState(""); diff --git a/docs/app/docs/fumadocs.css b/docs/app/docs/fumadocs.css new file mode 100644 index 000000000..81d999ee3 --- /dev/null +++ b/docs/app/docs/fumadocs.css @@ -0,0 +1,2 @@ +@reference "../global.css"; +@import "fumadocs-ui/css/preset.css"; diff --git a/docs/app/docs/layout.tsx b/docs/app/docs/layout.tsx index b17771b80..45fe82380 100644 --- a/docs/app/docs/layout.tsx +++ b/docs/app/docs/layout.tsx @@ -1,11 +1,15 @@ import { DocsRouteLayout } from "@/components/docs-route-layout"; import { WebsiteThemeProvider } from "@/components/website-theme-provider"; import { source } from "@/lib/source"; +import { RootProvider } from "fumadocs-ui/provider/next"; +import "./fumadocs.css"; export default function Layout({ children }: LayoutProps<"/docs">) { return ( - - {children} - + + + {children} + + ); } diff --git a/docs/app/global.css b/docs/app/global.css index 109d40605..d6c464953 100644 --- a/docs/app/global.css +++ b/docs/app/global.css @@ -1,12 +1,40 @@ -@import url("https://fonts.googleapis.com/css2?family=Playwrite+US+Trad&family=Geist:wght@400;500;600;700&display=swap"); @import "tailwindcss"; @import "fumadocs-ui/css/neutral.css"; -@import "fumadocs-ui/css/preset.css"; @import "../shared/design-system/styles/variables/index.css"; -@import "@openuidev/react-ui/styles/index.css"; -@import "@openuidev/thesys/styles.css"; body { + /* Static aliases cover routes that only consume design tokens. Interactive + component-library routes can still override them with ThemeProvider. */ + --openui-background: var(--background); + --openui-foreground: var(--foreground); + --openui-highlight-subtle: var(--highlight-subtle); + --openui-highlight: var(--highlight); + --openui-highlight-strong: var(--highlight-strong); + --openui-inverted-background: var(--inverted-background); + --openui-text-neutral-primary: var(--text-neutral-primary); + --openui-text-neutral-secondary: var(--text-neutral-secondary); + --openui-text-neutral-tertiary: var(--text-neutral-tertiary); + --openui-text-accent-primary: var(--text-accent-primary); + --openui-text-info-primary: var(--text-info-primary); + --openui-text-info-inverted: var(--text-info-inverted); + --openui-text-white: var(--text-white); + --openui-text-black: var(--text-black); + --openui-border-default: var(--border-default); + --openui-border-interactive: var(--border-interactive); + --openui-border-accent: var(--border-accent); + --openui-border-accent-emphasis: var(--border-accent-emphasis); + --openui-radius-s: var(--radius-s); + --openui-radius-4xl: var(--radius-4xl); + --openui-radius-full: var(--radius-full); + --openui-shadow-s: var(--shadow-s); + --openui-shadow-m: var(--shadow-m); + --openui-shadow-l: var(--shadow-l); + --openui-font-body: var(--font-body); + --openui-font-code: var(--font-code); + --openui-font-heading: var(--font-heading); + --openui-font-label: var(--font-label); + --openui-font-numbers: var(--font-numbers); + --color-fd-background: var(--openui-background); --color-fd-foreground: var(--openui-text-neutral-primary); --color-fd-muted: var(--openui-highlight-subtle); diff --git a/docs/app/layout.tsx b/docs/app/layout.tsx index 7f8afaff8..3db245dad 100644 --- a/docs/app/layout.tsx +++ b/docs/app/layout.tsx @@ -1,20 +1,15 @@ -import { RootProvider } from "fumadocs-ui/provider/next"; import type { Metadata } from "next"; -import { Geist_Mono, Inter } from "next/font/google"; -import Script from "next/script"; +import { Inter } from "next/font/google"; import { BASE_URL } from "../lib/source"; +import { DeferredAnalytics } from "./deferred-analytics"; import "./global.css"; import { PHProvider } from "./providers"; +import { SiteThemeProvider } from "./site-theme-provider"; const inter = Inter({ subsets: ["latin"], }); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - const SITE_TITLE = "OpenUI - The Open Standard for Generative UI"; const SITE_DESCRIPTION = "Full-stack, renderer-agnostic Generative UI with a streaming-first language, official React support, community integrations, and up to 67% fewer tokens than JSON."; @@ -84,23 +79,12 @@ export const metadata: Metadata = { export default function Layout({ children }: LayoutProps<"/">) { return ( - + - - - {children} - - - + + {children} + + ); diff --git a/docs/app/providers.tsx b/docs/app/providers.tsx index 253ebec37..44c2a37d8 100644 --- a/docs/app/providers.tsx +++ b/docs/app/providers.tsx @@ -1,20 +1,41 @@ "use client"; +import { loadPostHog } from "@/lib/posthog-client"; import { addThesysLinkAttribution } from "@/lib/thesys-link-attribution"; -import posthog from "posthog-js"; -import { PostHogProvider } from "posthog-js/react"; import { useEffect } from "react"; +const FALLBACK_DELAY_MS = 30_000; + export function PHProvider({ children }: { children: React.ReactNode }) { useEffect(() => { - posthog.init("phc_3OLW53x09ZTVZSV6BEpj5uycj3ooqR6KOemOjx04e3D", { - api_host: "https://dgoeivjus9jfp.cloudfront.net", - capture_pageview: "history_change", - disable_session_recording: false, - session_recording: { - sampleRate: 0.1, - }, - }); + let loaded = false; + let idleCallbackId: number | undefined; + let posthogDistinctId: string | undefined; + let posthogSessionId: string | undefined; + + const load = () => { + if (loaded) return; + loaded = true; + + void loadPostHog() + .then((posthog) => { + posthogDistinctId = posthog.get_distinct_id(); + posthogSessionId = posthog.get_session_id(); + }) + .catch(() => { + // Analytics failures must not affect navigation. + }); + }; + + const scheduleAfterNextPaint = () => { + window.requestAnimationFrame(() => { + if ("requestIdleCallback" in window) { + idleCallbackId = window.requestIdleCallback(load, { timeout: 2_000 }); + } else { + setTimeout(load, 0); + } + }); + }; const decorateLink = (anchor: HTMLAnchorElement) => { const href = anchor.getAttribute("href"); @@ -23,39 +44,14 @@ export function PHProvider({ children }: { children: React.ReactNode }) { const attributedHref = addThesysLinkAttribution( href, window.location.href, - posthog.get_distinct_id(), - posthog.get_session_id(), + posthogDistinctId, + posthogSessionId, ); if (attributedHref !== href) anchor.setAttribute("href", attributedHref); }; - const decorateLinksWithin = (root: ParentNode) => { - if (root instanceof HTMLAnchorElement) decorateLink(root); - root.querySelectorAll("a[href]").forEach(decorateLink); - }; - - decorateLinksWithin(document); - - const observer = new MutationObserver((mutations) => { - for (const mutation of mutations) { - if (mutation.type === "attributes") { - if (mutation.target instanceof HTMLAnchorElement) decorateLink(mutation.target); - continue; - } - - for (const node of mutation.addedNodes) { - if (node instanceof Element) decorateLinksWithin(node); - } - } - }); - - observer.observe(document.body, { - attributes: true, - attributeFilter: ["href"], - childList: true, - subtree: true, - }); + document.querySelectorAll("a[href]").forEach(decorateLink); const refreshLinkAtInteraction = (event: Event) => { if (!(event.target instanceof Element)) return; @@ -66,11 +62,27 @@ export function PHProvider({ children }: { children: React.ReactNode }) { document.addEventListener("pointerdown", refreshLinkAtInteraction, true); document.addEventListener("click", refreshLinkAtInteraction, true); + const interactionEvents = ["pointerdown", "keydown", "touchstart"] as const; + interactionEvents.forEach((eventName) => { + window.addEventListener(eventName, scheduleAfterNextPaint, { + once: true, + passive: true, + }); + }); + + const fallbackTimer = window.setTimeout(load, FALLBACK_DELAY_MS); + return () => { - observer.disconnect(); + window.clearTimeout(fallbackTimer); + if (idleCallbackId !== undefined && "cancelIdleCallback" in window) { + window.cancelIdleCallback(idleCallbackId); + } document.removeEventListener("pointerdown", refreshLinkAtInteraction, true); document.removeEventListener("click", refreshLinkAtInteraction, true); + interactionEvents.forEach((eventName) => { + window.removeEventListener(eventName, scheduleAfterNextPaint); + }); }; }, []); - return {children}; + return children; } diff --git a/docs/app/site-theme-provider.tsx b/docs/app/site-theme-provider.tsx new file mode 100644 index 000000000..57afe7b26 --- /dev/null +++ b/docs/app/site-theme-provider.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { ThemeProvider } from "next-themes"; +import type { ReactNode } from "react"; + +export function SiteThemeProvider({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} diff --git a/docs/components/brand-logo.module.css b/docs/components/brand-logo.module.css index 17a2f0f37..b24185178 100644 --- a/docs/components/brand-logo.module.css +++ b/docs/components/brand-logo.module.css @@ -1,3 +1,126 @@ +.thesysLink { + position: relative; + display: block; + flex-shrink: 0; + width: 1.5rem; + height: 1.5rem; + cursor: pointer; +} + +.thesysMark { + position: absolute; + z-index: 1; + display: block; + width: 100%; + height: 100%; + transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1); +} + +.thesysRect, +.thesysPath, +.thesysGlow { + transition: + fill 160ms ease, + opacity 160ms ease, + transform 160ms ease; +} + +.thesysRect { + fill: rgb(0 0 0 / 4%); +} + +.thesysPath { + fill: #000; +} + +.thesysGlow { + position: absolute; + inset: -0.25rem; + z-index: 0; + border-radius: 0.5rem; + background: rgb(0 0 0 / 5%); + opacity: 0; + transform: scale(0.5); +} + +.thesysLink:hover .thesysMark, +.thesysLink:focus-visible .thesysMark { + transform: scale(1.15) rotate(8deg); +} + +.thesysLink:hover .thesysRect, +.thesysLink:focus-visible .thesysRect { + fill: #000; +} + +.thesysLink:hover .thesysPath, +.thesysLink:focus-visible .thesysPath { + fill: #fff; +} + +.thesysLink:hover .thesysGlow, +.thesysLink:focus-visible .thesysGlow { + opacity: 1; + transform: scale(1); +} + +.thesysLinkDark .thesysRect { + fill: rgb(255 255 255 / 12%); +} + +.thesysLinkDark .thesysPath { + fill: #fff; +} + +.thesysLinkDark .thesysGlow { + background: rgb(255 255 255 / 10%); +} + +.thesysLinkDark:hover .thesysRect, +.thesysLinkDark:focus-visible .thesysRect { + fill: #fff; +} + +.thesysLinkDark:hover .thesysPath, +.thesysLinkDark:focus-visible .thesysPath { + fill: #000; +} + +.openuiLink { + display: flex; + align-items: center; + gap: 0.125rem; + text-decoration: none; +} + +.openuiMascot { + position: relative; + flex-shrink: 0; + width: 2rem; + height: 2rem; +} + +.openuiMascotImage { + position: absolute; + inset: 0; + display: block; + width: 100%; + height: 100%; + object-fit: contain; +} + +.openuiText { + color: #000; + font-family: "Inter", sans-serif; + font-size: 15px; + font-weight: 600; + line-height: 1.5rem; +} + +.openuiTextDark { + color: #fff; +} + .githubButton { position: relative; display: inline-flex; diff --git a/docs/components/brand-logo.tsx b/docs/components/brand-logo.tsx index 4176b0d23..79c652cc0 100644 --- a/docs/components/brand-logo.tsx +++ b/docs/components/brand-logo.tsx @@ -1,104 +1,46 @@ "use client"; import svgPaths from "@/imports/svg-urruvoh2be"; -import { AnimatePresence, motion } from "motion/react"; import Link from "next/link"; import { useEffect, useState } from "react"; import styles from "./brand-logo.module.css"; -const COUNT_UP_DURATION = 1500; - -const LOGO_SPRING = { type: "spring", stiffness: 400, damping: 15 } as const; -const LOGO_COLOR_TRANSITION = { duration: 0.25 } as const; -const GLOW_TRANSITION = { duration: 0.2 } as const; - export type LogoVariant = "light" | "dark"; function cx(...classNames: Array) { return classNames.filter(Boolean).join(" "); } -export function ThesysLogo({ - isHovered, - onHoverChange, - variant = "light", -}: { - isHovered: boolean; - onHoverChange: (hovered: boolean) => void; - variant?: LogoVariant; -}) { +export function ThesysLogo({ variant = "light" }: { variant?: LogoVariant }) { const isDark = variant === "dark"; - const rectIdleColor = isDark ? "rgba(255,255,255,0.12)" : "rgba(0,0,0,0.04)"; - const rectHoveredColor = isDark ? "#ffffff" : "#000000"; - const pathIdleColor = isDark ? "#ffffff" : "#000000"; - const pathHoveredColor = isDark ? "#000000" : "#ffffff"; - const glowClass = isDark - ? "absolute -inset-1 rounded-lg bg-white/10" - : "absolute -inset-1 rounded-lg bg-black/5"; - return ( onHoverChange(true)} - onMouseLeave={() => onHoverChange(false)} + aria-label="Visit Thesys" + className={`${styles.thesysLink} ${isDark ? styles.thesysLinkDark : ""}`.trim()} > - - - - - - - {isHovered && ( - - )} - + ); } export function OpenUILogo({ variant = "light" }: { variant?: LogoVariant }) { const isDark = variant === "dark"; - const textClass = isDark - ? "font-['Geist',sans-serif] font-semibold text-[15px] text-white leading-6" - : "font-['Geist',sans-serif] font-semibold text-[15px] text-black leading-6"; + const textClass = `${styles.openuiText} ${isDark ? styles.openuiTextDark : ""}`.trim(); return ( - + {/* Shiro mascot */} -

+
{/* eslint-disable-next-line @next/next/no-img-element */} - +
OpenUI @@ -144,33 +86,23 @@ function fetchGitHubStarCount(repo: string): Promise { return inflight; } -export function useGitHubStarCount(repo: string) { +export function useGitHubStarCount(repo: string, enabled = true) { const [count, setCount] = useState(null); useEffect(() => { + if (!enabled) return; + let cancelled = false; void fetchGitHubStarCount(repo).then((target) => { if (cancelled || target === null) return; - const startCount = Math.max(target - 50, 0); - const startTime = performance.now(); - - setCount(startCount); - - const tick = () => { - if (cancelled) return; - const progress = Math.min((performance.now() - startTime) / COUNT_UP_DURATION, 1); - setCount(Math.round(startCount + (target - startCount) * progress)); - if (progress < 1) requestAnimationFrame(tick); - }; - - requestAnimationFrame(tick); + setCount(target); }); return () => { cancelled = true; }; - }, [repo]); + }, [enabled, repo]); return count; } diff --git a/docs/components/site-header.tsx b/docs/components/site-header.tsx index ecd1fb2ac..5ca60fd38 100644 --- a/docs/components/site-header.tsx +++ b/docs/components/site-header.tsx @@ -1,6 +1,4 @@ -"use client"; - -import { useState, type CSSProperties, type ReactNode } from "react"; +import type { CSSProperties, ReactNode } from "react"; import { OpenUILogo, ThesysLogo, type LogoVariant } from "./brand-logo"; import styles from "./site-header.module.css"; @@ -9,11 +7,9 @@ function joinClasses(...classNames: Array) { } export function SiteHeaderBrand({ variant = "light" }: { variant?: LogoVariant }) { - const [isHovered, setIsHovered] = useState(false); - return (
- +
diff --git a/docs/components/site-marketing-header.module.css b/docs/components/site-marketing-header.module.css index 4a50436e0..7d95e71bd 100644 --- a/docs/components/site-marketing-header.module.css +++ b/docs/components/site-marketing-header.module.css @@ -52,6 +52,7 @@ cursor: pointer; background: rgb(0 0 0 / 60%); backdrop-filter: blur(12px); + animation: mobileBackdropIn 0.2s ease both; } .mobileTrayWrap { @@ -61,6 +62,27 @@ right: 0; z-index: 50; pointer-events: none; + animation: mobileTrayIn 0.2s ease both; +} + +@keyframes mobileBackdropIn { + from { + opacity: 0; + } +} + +@keyframes mobileTrayIn { + from { + opacity: 0; + transform: translateY(-8px); + } +} + +@media (prefers-reduced-motion: reduce) { + .mobileBackdrop, + .mobileTrayWrap { + animation: none; + } } .mobileTray { diff --git a/docs/components/site-marketing-header.tsx b/docs/components/site-marketing-header.tsx index ecd176aa2..15e6bf8f6 100644 --- a/docs/components/site-marketing-header.tsx +++ b/docs/components/site-marketing-header.tsx @@ -10,7 +10,6 @@ import { } from "@/components/site-primary-nav"; import { ThemeToggle } from "@/components/theme-toggle"; import { ArrowRight } from "lucide-react"; -import { AnimatePresence, motion } from "motion/react"; import { useTheme } from "next-themes"; import Link from "next/link"; import { useCallback, useEffect, useState, type ReactNode } from "react"; @@ -116,21 +115,8 @@ function MobileMenu({ onClose }: { onClose: () => void }) { return ( <> - - +
+
@@ -156,6 +142,7 @@ function MobileMenu({ onClose }: { onClose: () => void }) { void }) {
- +
); } @@ -183,14 +170,11 @@ export function SiteMarketingHeader({ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); const [isScrolled, setIsScrolled] = useState(borderMode === "always"); const { resolvedTheme } = useTheme(); - const [mounted, setMounted] = useState(false); - useEffect(() => setMounted(true), []); const resolvedBrandVariant = - brandVariant ?? (mounted && resolvedTheme === "dark" ? "dark" : "light"); + brandVariant ?? (resolvedTheme === "dark" ? "dark" : "light"); useEffect(() => { if (borderMode === "always") { - setIsScrolled(true); return; } @@ -235,6 +219,7 @@ export function SiteMarketingHeader({
@@ -258,9 +243,7 @@ export function SiteMarketingHeader({
} /> - - {isMobileMenuOpen && } - + {isMobileMenuOpen && } ); } diff --git a/docs/components/theme-toggle.tsx b/docs/components/theme-toggle.tsx index e264cc7fa..0b69b1758 100644 --- a/docs/components/theme-toggle.tsx +++ b/docs/components/theme-toggle.tsx @@ -3,7 +3,6 @@ import { cn } from "@/lib/cn"; import { Moon, Sun } from "@phosphor-icons/react"; import { useTheme } from "next-themes"; -import { useEffect, useState } from "react"; import styles from "./theme-toggle.module.css"; type ThemeToggleProps = { @@ -15,9 +14,7 @@ type ThemeToggleProps = { export function ThemeToggle({ className, onToggle, title, ariaLabel }: ThemeToggleProps) { const { setTheme, resolvedTheme } = useTheme(); - const [mounted, setMounted] = useState(false); - useEffect(() => setMounted(true), []); - const isDark = mounted && resolvedTheme === "dark"; + const isDark = resolvedTheme === "dark"; const nextTheme = isDark ? "light" : "dark"; const defaultLabel = `Switch to ${nextTheme} mode`; diff --git a/docs/content/docs/openui-lang/components/lang-example.tsx b/docs/content/docs/openui-lang/components/lang-example.tsx index 7ab270a76..5c672b497 100644 --- a/docs/content/docs/openui-lang/components/lang-example.tsx +++ b/docs/content/docs/openui-lang/components/lang-example.tsx @@ -9,8 +9,12 @@ import { TabsTrigger, } from "@/components/overview-components"; import { genuiOutput } from "@/components/overview-components/genui"; -import { Renderer } from "@openuidev/react-lang"; -import { openuiLibrary } from "@openuidev/react-ui"; +import dynamic from "next/dynamic"; + +const RenderedOpenUIExample = dynamic( + () => import("./rendered-openui-example").then((module) => module.RenderedOpenUIExample), + { ssr: false }, +); const renderableOutput = `root = Stack([welcomeCard]) welcomeCard = Card([welcomeHeader, welcomeBody]) @@ -115,7 +119,7 @@ const completion = await client.chat.completions.create({ Output Preview
- +
diff --git a/docs/content/docs/openui-lang/components/rendered-openui-example.tsx b/docs/content/docs/openui-lang/components/rendered-openui-example.tsx new file mode 100644 index 000000000..4a6729fa9 --- /dev/null +++ b/docs/content/docs/openui-lang/components/rendered-openui-example.tsx @@ -0,0 +1,8 @@ +"use client"; + +import { Renderer } from "@openuidev/react-lang"; +import { openuiLibrary } from "@openuidev/react-ui/genui-lib"; + +export function RenderedOpenUIExample({ response }: { response: string }) { + return ; +} diff --git a/docs/lib/create-cli-copy-analytics.ts b/docs/lib/create-cli-copy-analytics.ts index 46be70f9a..4f5c8981b 100644 --- a/docs/lib/create-cli-copy-analytics.ts +++ b/docs/lib/create-cli-copy-analytics.ts @@ -1,4 +1,4 @@ -import posthog from "posthog-js"; +import { capturePostHogEvent } from "./posthog-client"; export const CREATE_CLI_COMMAND_COPIED_EVENT = "create_cli_command_copied"; @@ -49,9 +49,5 @@ export function captureCreateCliCommandCopied( const properties = getCreateCliCommandCopiedProperties(command, context); if (!properties || typeof window === "undefined") return; - try { - posthog.capture(CREATE_CLI_COMMAND_COPIED_EVENT, properties); - } catch { - // Analytics must never interfere with a successful clipboard action. - } + capturePostHogEvent(CREATE_CLI_COMMAND_COPIED_EVENT, properties); } diff --git a/docs/lib/posthog-client.ts b/docs/lib/posthog-client.ts new file mode 100644 index 000000000..eb4c3f673 --- /dev/null +++ b/docs/lib/posthog-client.ts @@ -0,0 +1,28 @@ +type PostHog = (typeof import("posthog-js/dist/module.slim"))["default"]; + +let posthogPromise: Promise | undefined; + +export function loadPostHog(): Promise { + posthogPromise ??= import("posthog-js/dist/module.slim").then(({ default: posthog }) => { + posthog.init("phc_3OLW53x09ZTVZSV6BEpj5uycj3ooqR6KOemOjx04e3D", { + api_host: "https://dgoeivjus9jfp.cloudfront.net", + capture_pageview: "history_change", + advanced_disable_flags: true, + disable_external_dependency_loading: true, + disable_session_recording: true, + disable_surveys: true, + }); + + return posthog; + }); + + return posthogPromise; +} + +export function capturePostHogEvent(eventName: string, properties: object): void { + void loadPostHog() + .then((posthog) => posthog.capture(eventName, properties as Record)) + .catch(() => { + // Analytics must never interfere with the user action being measured. + }); +} diff --git a/docs/next.config.mjs b/docs/next.config.mjs index c4482dda8..20a6f3121 100644 --- a/docs/next.config.mjs +++ b/docs/next.config.mjs @@ -8,6 +8,7 @@ const withMDX = createMDX(); /** @type {import('next').NextConfig} */ const config = { + productionBrowserSourceMaps: true, serverExternalPackages: ["@takumi-rs/image-response"], turbopack: { root: dirname(dirname(__dirname)), diff --git a/docs/package.json b/docs/package.json index a9b7e23f0..cb06ecc61 100644 --- a/docs/package.json +++ b/docs/package.json @@ -5,7 +5,7 @@ "scripts": { "generate:prompts": "pnpm --filter @openuidev/cli build && pnpm exec openui generate lib/chat-library.ts --out generated/chat-system-prompt.txt && pnpm exec openui generate lib/playground-library.ts --out generated/playground-system-prompt.txt && pnpm exec openui generate lib/playground-library.ts --json-schema --out generated/playground-component-spec.json", "fetch:tweets": "node scripts/fetch-static-tweets.mjs", - "build": "next build --no-mangling", + "build": "next build", "dev": "pnpm generate:prompts && next dev", "start": "next start", "types:check": "fumadocs-mdx && next typegen && tsc --noEmit", diff --git a/docs/public/logos/ficell-labs.webp b/docs/public/logos/ficell-labs.webp new file mode 100644 index 000000000..687e3ddfe Binary files /dev/null and b/docs/public/logos/ficell-labs.webp differ diff --git a/docs/public/logos/pointlabs.webp b/docs/public/logos/pointlabs.webp new file mode 100644 index 000000000..cc6c85c47 Binary files /dev/null and b/docs/public/logos/pointlabs.webp differ diff --git a/docs/public/openui-cloud/hero-dark-1280.webp b/docs/public/openui-cloud/hero-dark-1280.webp new file mode 100644 index 000000000..ba9707902 Binary files /dev/null and b/docs/public/openui-cloud/hero-dark-1280.webp differ diff --git a/docs/public/openui-cloud/hero-dark-720.webp b/docs/public/openui-cloud/hero-dark-720.webp new file mode 100644 index 000000000..6ef22d052 Binary files /dev/null and b/docs/public/openui-cloud/hero-dark-720.webp differ diff --git a/docs/public/openui-cloud/hero-dark.svg b/docs/public/openui-cloud/hero-dark.svg index 36bd99e10..538e130b2 100644 --- a/docs/public/openui-cloud/hero-dark.svg +++ b/docs/public/openui-cloud/hero-dark.svg @@ -1,327 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/public/openui-cloud/hero-light-1280.webp b/docs/public/openui-cloud/hero-light-1280.webp new file mode 100644 index 000000000..b37cd6d20 Binary files /dev/null and b/docs/public/openui-cloud/hero-light-1280.webp differ diff --git a/docs/public/openui-cloud/hero-light-720.webp b/docs/public/openui-cloud/hero-light-720.webp new file mode 100644 index 000000000..d5f459b0b Binary files /dev/null and b/docs/public/openui-cloud/hero-light-720.webp differ diff --git a/docs/public/openui-cloud/hero-light.svg b/docs/public/openui-cloud/hero-light.svg index 2190d7936..706a2bfa3 100644 --- a/docs/public/openui-cloud/hero-light.svg +++ b/docs/public/openui-cloud/hero-light.svg @@ -1,340 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/public/openui-cloud/llm-gateway-dark.svg b/docs/public/openui-cloud/llm-gateway-dark.svg index 278c2348b..5e3e44669 100644 --- a/docs/public/openui-cloud/llm-gateway-dark.svg +++ b/docs/public/openui-cloud/llm-gateway-dark.svg @@ -1,88 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/public/openui-cloud/llm-gateway-dark.webp b/docs/public/openui-cloud/llm-gateway-dark.webp new file mode 100644 index 000000000..6a9394306 Binary files /dev/null and b/docs/public/openui-cloud/llm-gateway-dark.webp differ diff --git a/docs/public/openui-cloud/llm-gateway.svg b/docs/public/openui-cloud/llm-gateway.svg index 58fdc14fb..6fb876fb5 100644 --- a/docs/public/openui-cloud/llm-gateway.svg +++ b/docs/public/openui-cloud/llm-gateway.svg @@ -1,92 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/public/openui-cloud/llm-gateway.webp b/docs/public/openui-cloud/llm-gateway.webp new file mode 100644 index 000000000..bb839cca6 Binary files /dev/null and b/docs/public/openui-cloud/llm-gateway.webp differ diff --git a/docs/public/openui-cloud/reports-dark-1440.webp b/docs/public/openui-cloud/reports-dark-1440.webp new file mode 100644 index 000000000..e7448b0c0 Binary files /dev/null and b/docs/public/openui-cloud/reports-dark-1440.webp differ diff --git a/docs/public/openui-cloud/reports-dark-720.webp b/docs/public/openui-cloud/reports-dark-720.webp new file mode 100644 index 000000000..286a4bde2 Binary files /dev/null and b/docs/public/openui-cloud/reports-dark-720.webp differ diff --git a/docs/public/openui-cloud/reports-light-1440.webp b/docs/public/openui-cloud/reports-light-1440.webp new file mode 100644 index 000000000..540b2758f Binary files /dev/null and b/docs/public/openui-cloud/reports-light-1440.webp differ diff --git a/docs/public/openui-cloud/reports-light-720.webp b/docs/public/openui-cloud/reports-light-720.webp new file mode 100644 index 000000000..d612e352e Binary files /dev/null and b/docs/public/openui-cloud/reports-light-720.webp differ diff --git a/docs/public/openui-cloud/validation-dark.svg b/docs/public/openui-cloud/validation-dark.svg index 899fd98c9..e2fd9a804 100644 --- a/docs/public/openui-cloud/validation-dark.svg +++ b/docs/public/openui-cloud/validation-dark.svg @@ -1,80 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/public/openui-cloud/validation.svg b/docs/public/openui-cloud/validation.svg index ee57c1892..ba80fc79b 100644 --- a/docs/public/openui-cloud/validation.svg +++ b/docs/public/openui-cloud/validation.svg @@ -1,80 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/docs/shared/design-system/components/AppThemeProvider/AppThemeProvider.tsx b/docs/shared/design-system/components/AppThemeProvider/AppThemeProvider.tsx index 03152377d..1f593efcc 100644 --- a/docs/shared/design-system/components/AppThemeProvider/AppThemeProvider.tsx +++ b/docs/shared/design-system/components/AppThemeProvider/AppThemeProvider.tsx @@ -1,6 +1,6 @@ "use client"; -import { ThemeProvider } from "@openuidev/react-ui"; +import { ThemeProvider } from "@openuidev/react-ui/ThemeProvider"; import type { ReactNode } from "react"; const FONT_FAMILY = 'var(--font-inter), "Inter", "Segoe UI", Arial, sans-serif'; diff --git a/docs/shared/design-system/components/TopBarDocsButton/TopBarDocsButton.tsx b/docs/shared/design-system/components/TopBarDocsButton/TopBarDocsButton.tsx index 0d82ab0ac..34e8ca411 100644 --- a/docs/shared/design-system/components/TopBarDocsButton/TopBarDocsButton.tsx +++ b/docs/shared/design-system/components/TopBarDocsButton/TopBarDocsButton.tsx @@ -1,6 +1,6 @@ "use client"; -import { Button } from "@openuidev/react-ui"; +import { Button } from "@openuidev/react-ui/Button"; import { useRouter } from "next/navigation"; export default function TopBarDocsButton() { diff --git a/docs/shared/design-system/components/preview/PromptCopyButton.tsx b/docs/shared/design-system/components/preview/PromptCopyButton.tsx index 8317e2816..d2b140d00 100644 --- a/docs/shared/design-system/components/preview/PromptCopyButton.tsx +++ b/docs/shared/design-system/components/preview/PromptCopyButton.tsx @@ -1,6 +1,6 @@ "use client"; -import { IconButton } from "@openuidev/react-ui"; +import { IconButton } from "@openuidev/react-ui/IconButton"; import { Check, Copy } from "lucide-react"; import { useEffect, useState } from "react";