diff --git a/.gitignore b/.gitignore index a5e18ac..ab1d6b4 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,15 @@ xcuserdata # VSCode .history/ + +# Next.js +.next +out +*.tsbuildinfo + +# OpenNext / Cloudflare +.open-next +.wrangler + +# Environment +.env*.local diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index e049bda..d364e18 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -6,7 +6,7 @@ import ora from 'ora'; import { DEFAULT_CONFIG, getConfig, setConfig, type WatermelonConfig } from './utils/config.js'; import { collectComponents, downloadRegistryFile } from './utils/registry.js'; import { getMissingDependencies, installDependencies } from './utils/dependencies.js'; -import { ensureComponentsDirectory, ensureUtilsFile, installFiles } from './utils/files.js'; +import { ensureComponentsDirectory, ensureCssFile, ensureUtilsFile, installFiles } from './utils/files.js'; const program = new Command(); @@ -85,6 +85,7 @@ program await setConfig(cwd, config); await ensureComponentsDirectory(cwd, config); await ensureUtilsFile(cwd, config); + await ensureCssFile(cwd, config); const requiredDeps = [ 'clsx', diff --git a/apps/cli/src/utils/files.ts b/apps/cli/src/utils/files.ts index cf05454..f95222e 100644 --- a/apps/cli/src/utils/files.ts +++ b/apps/cli/src/utils/files.ts @@ -89,6 +89,245 @@ export async function ensureComponentsDirectory(cwd: string, config: WatermelonC await fs.ensureDir(path.join(cwd, componentsPath, 'ui')); } +export async function ensureCssFile(cwd: string, config: WatermelonConfig): Promise { + const cssContent = `@import "tailwindcss"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-sans); + --font-mono: var(--font-geist-mono); + --shadow-3d: inset 0 5px 6px var(--color-border); + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --radius-sm: calc(var(--radius) * 0.6); + --radius-md: calc(var(--radius) * 0.8); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) * 1.4); + --radius-2xl: calc(var(--radius) * 1.8); + --radius-3xl: calc(var(--radius) * 2.2); + --radius-4xl: calc(var(--radius) * 2.6); +} + +:root { + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.809 0.105 251.813); + --chart-2: oklch(0.623 0.214 259.815); + --chart-3: oklch(0.546 0.245 262.881); + --chart-4: oklch(0.488 0.243 264.376); + --chart-5: oklch(0.424 0.199 265.638); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.205 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(0.809 0.105 251.813); + --chart-2: oklch(0.623 0.214 259.815); + --chart-3: oklch(0.546 0.245 262.881); + --chart-4: oklch(0.488 0.243 264.376); + --chart-5: oklch(0.424 0.199 265.638); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.556 0 0); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } + html { + @apply font-sans; + } + +@keyframes show-top-mask { + to { + --top-mask-height: var(--mask-height); + } + } + +@keyframes hide-bottom-mask { + to { + --bottom-mask-height: 0px; + } + } + +@keyframes show-left-mask { + to { + --left-mask-width: var(--mask-width); + } + } + +@keyframes hide-right-mask { + to { + --right-mask-width: 0px; + } + } +} + +@property --top-mask-height { + syntax: ""; + inherits: true; + initial-value: 0px; +} + +@property --bottom-mask-height { + syntax: ""; + inherits: true; + initial-value: 64px; +} + +@property --left-mask-width { + syntax: ""; + inherits: true; + initial-value: 0px; +} + +@property --right-mask-width { + syntax: ""; + inherits: true; + initial-value: 64px; +} + +@utility scroll-fade-effect-y { + --mask-height: 64px; + --mask-offset-top: 0px; + --mask-offset-bottom: 0px; + --scroll-buffer: 2rem; + mask-image: linear-gradient(to top, transparent, black 90%), linear-gradient(to bottom, transparent 0%, black 100%), linear-gradient(black, black); + mask-size: 100% var(--top-mask-height), 100% var(--bottom-mask-height), 100% 100%; + mask-repeat: no-repeat, no-repeat, no-repeat; + mask-position: 0 var(--mask-offset-top), 0 calc(100% - var(--mask-offset-bottom)), 0 0; + mask-composite: exclude; + animation-name: show-top-mask, hide-bottom-mask; + animation-timeline: scroll(self), scroll(self); + animation-range: 0 var(--scroll-buffer), calc(100% - var(--scroll-buffer)) 100%; + animation-fill-mode: both; +} + +@utility scroll-fade-effect-x { + --mask-width: 64px; + --mask-offset-left: 0px; + --mask-offset-right: 0px; + --scroll-buffer: 2rem; + mask-image: linear-gradient(to left, transparent, black 90%), linear-gradient(to right, transparent 0%, black 100%), linear-gradient(black, black); + mask-size: var(--left-mask-width) 100%, var(--right-mask-width) 100%, 100% 100%; + mask-repeat: no-repeat, no-repeat, no-repeat; + mask-position: var(--mask-offset-left) 0, calc(100% - var(--mask-offset-right)) 0, 0 0; + mask-composite: exclude; + animation-name: show-left-mask, hide-right-mask; + animation-timeline: scroll(self inline), scroll(self inline); + animation-range: 0 var(--scroll-buffer), calc(100% - var(--scroll-buffer)) 100%; + animation-fill-mode: both; +} + +/* Hide scrollbar globally */ +.scrollbar-hide { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.scrollbar-hide::-webkit-scrollbar { + display: none; +} + +/* Apply to all scrollable elements */ +* { + scrollbar-width: none; + -ms-overflow-style: none; +} + +*::-webkit-scrollbar { + display: none; +} +`; + + const fullPath = path.join(cwd, config.tailwind.css); + + if (!(await fs.pathExists(fullPath))) { + await fs.ensureDir(path.dirname(fullPath)); + await fs.writeFile(fullPath, cssContent, 'utf-8'); + } +} + function resolveTargetRoot(cwd: string, installPath?: string): string { if (!installPath) { return cwd; diff --git a/apps/platform/app/(docs)/[...slug]/page.tsx b/apps/platform/app/(docs)/[...slug]/page.tsx index fd936b4..a3ce1ce 100644 --- a/apps/platform/app/(docs)/[...slug]/page.tsx +++ b/apps/platform/app/(docs)/[...slug]/page.tsx @@ -1,3 +1,4 @@ +import type { ComponentType } from "react"; import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; @@ -6,7 +7,12 @@ import { DocsBody, DocsDescription, DocsTitle } from "fumadocs-ui/page"; import MotionDiv from "@/components/core/motion-div"; import { PageHeader } from "@/components/core/typography"; import { Button } from "@/components/ui/button"; -import { DocsPager, OnThisPage, PreviewCard } from "@/components/showcase/docs-primitives"; +import { + DocsPager, + OnThisPage, + PreviewCard, +} from "@/components/showcase/docs-primitives"; +import { ComponentLivePreview } from "@/components/showcase/component-live-preview"; import { ComponentPreview } from "@/components/mdx/component-preview"; import { ComponentInstallation } from "@/components/showcase/component-installation"; import { @@ -14,10 +20,7 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; -import { - getComponentLinks, - getComponentPager, -} from "@/lib/docs-navigation"; +import { getComponentLinks, getComponentPager } from "@/lib/docs-navigation"; import { docsSource } from "@/lib/docs-source"; import { getRegistryCatalog } from "@/lib/registry-catalog"; import { customMDXComponents } from "@/mdx-components"; @@ -26,6 +29,24 @@ type DocsPageParams = { slug: string[]; }; +type ResolvedDocsPageData = { + body: ComponentType<{ components?: typeof customMDXComponents }>; + toc?: TOCItemType[]; + kind?: "guide" | "component"; + badge?: string; + title: string; + description?: string; + category?: string; + sourceHref?: string; + registryHref?: string; + qrValue?: string; + appStoreHref?: string; + playStoreHref?: string; + install?: string[]; + importPath?: string; + dependencies?: string[]; +}; + export function generateStaticParams() { return docsSource.generateParams(); } @@ -58,9 +79,10 @@ export default async function DocsPage({ notFound(); } - const Content = page.data.body; - const baseToc = (page.data.toc ?? []) as TOCItemType[]; - const isComponentPage = page.data.kind === "component"; + const pageData = page.data as ResolvedDocsPageData; + const Content = pageData.body; + const baseToc = pageData.toc ?? []; + const isComponentPage = pageData.kind === "component"; const toc = isComponentPage ? [ @@ -85,12 +107,12 @@ export default async function DocsPage({ - + @@ -103,7 +125,9 @@ export default async function DocsPage({ const component = catalog .flatMap((category) => category.items) .find((item) => item.slug === componentSlug); - const componentDoc = componentDocs.find((item) => item.slug === componentSlug); + const componentDoc = componentDocs.find( + (item) => item.slug === componentSlug, + ); if (!component || !componentSlug || !componentDoc) { notFound(); @@ -122,26 +146,26 @@ export default async function DocsPage({
-

- {page.data.category} +

+ {pageData.category}

- - {page.data.title} + + {pageData.title} - {page.data.description} + {pageData.description}
- {page.data.sourceHref ? ( + {pageData.sourceHref ? ( ) : null} - {page.data.registryHref ? ( + {pageData.registryHref ? ( @@ -150,9 +174,9 @@ export default async function DocsPage({
- {page.data.qrValue && - page.data.appStoreHref && - page.data.playStoreHref ? ( + {pageData.qrValue && + pageData.appStoreHref && + pageData.playStoreHref ? (
@@ -162,9 +186,9 @@ export default async function DocsPage({ @@ -172,9 +196,11 @@ export default async function DocsPage({ ) : null} + video={undefined} + poster={undefined} + > + +
- + diff --git a/apps/platform/app/(docs)/animated-components/page.tsx b/apps/platform/app/(docs)/animated-components/page.tsx new file mode 100644 index 0000000..b5f663b --- /dev/null +++ b/apps/platform/app/(docs)/animated-components/page.tsx @@ -0,0 +1,28 @@ +import type { TOCItemType } from "fumadocs-core/toc"; +import { ComponentsIndexView } from "@/components/showcase/component-docs"; +import { getRegistryCatalog } from "@/lib/registry-catalog"; +import { OnThisPage } from "@/components/showcase/docs-primitives"; + +export default async function AnimatedComponentsPage() { + const categories = await getRegistryCatalog(); + const toc: TOCItemType[] = [ + { url: "#component-overview", title: "Overview", depth: 2 }, + { url: "#animated-components", title: "Animated Components", depth: 2 }, + ]; + + const animatedCategories = categories.filter( + (c) => c.title === "Animated Components", + ); + + return ( + <> + + + + ); +} diff --git a/apps/platform/app/(docs)/components/page.tsx b/apps/platform/app/(docs)/components/page.tsx index 39da432..3d20c00 100644 --- a/apps/platform/app/(docs)/components/page.tsx +++ b/apps/platform/app/(docs)/components/page.tsx @@ -7,17 +7,22 @@ export default async function ComponentsPage() { const categories = await getRegistryCatalog(); const toc: TOCItemType[] = [ { url: "#component-overview", title: "Overview", depth: 2 }, - ...categories.map((category) => ({ - url: `#${category.slug}`, - title: category.title, - depth: 2, - })), + { url: "#components", title: "Components", depth: 2 }, ]; + const standardCategories = categories.filter( + (c) => c.title !== "Animated Components", + ); + return ( <> - + ); } diff --git a/apps/platform/app/(docs)/layout.tsx b/apps/platform/app/(docs)/layout.tsx index 3c5f9c5..dd138b3 100644 --- a/apps/platform/app/(docs)/layout.tsx +++ b/apps/platform/app/(docs)/layout.tsx @@ -5,12 +5,14 @@ import { Navbar } from "@/components/core/navbar"; import { TOCProvider } from "@/components/core/toc-context"; import { DocsTOC } from "@/components/core/docs-toc"; import { + getAnimatedComponentEntries, + getComponentEntries, getCommandLinks, - getComponentGroups, getGuideLinks, } from "@/lib/docs-navigation"; import { HugeiconsIcon } from "@hugeicons/react"; import { + Atom02Icon, BookOpen02Icon, ComputerTerminalIcon, } from "@hugeicons/core-free-icons"; @@ -32,14 +34,15 @@ export default function ComponentsLayout({ })), }, ]; - const componentGroups = getComponentGroups().map((group) => ({ - title: group.title, - url: "#", + const componentGroups = getComponentEntries().map((item) => ({ + title: item.title, + url: item.url, icon: , - items: group.items.map((item) => ({ - title: item.title, - url: item.url, - })), + })); + const animatedComponentGroups = getAnimatedComponentEntries().map((item) => ({ + title: item.title, + url: item.url, + icon: , })); const commandLinks = getCommandLinks(); @@ -49,17 +52,20 @@ export default function ComponentsLayout({ - -
+ +
-
-
- +
+
+
+ +
+
{children}
-
{children}
-
-
+ +