diff --git a/docs/.gitignore b/docs/.gitignore index 8fa55a69b3..c12953bff8 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -29,4 +29,4 @@ next-env.d.ts /content/examples/*/* /components/example/generated/ -sqlite.db \ No newline at end of file +sqlite.db diff --git a/docs/app/(home)/_components/BlockCatalog.tsx b/docs/app/(home)/_components/BlockCatalog.tsx new file mode 100644 index 0000000000..72804ec79e --- /dev/null +++ b/docs/app/(home)/_components/BlockCatalog.tsx @@ -0,0 +1,108 @@ +"use client"; +import { + AudioWaveform, + ChevronRight, + Code2, + FileText, + Heading, + Image, + List, + ListOrdered, + ListTodo, + Minus, + Pilcrow, + Puzzle, + Quote, + Table, + Video, +} from "lucide-react"; +import React from "react"; + +const BlockCatalogItem: React.FC<{ name: string; icon: React.ReactNode }> = ({ + name, + icon, +}) => ( +
+
+
+ {icon} +
+ + {name} + +
+); + +export const BlockCatalog: React.FC = () => { + return ( +
+ {/* Subtle decorative elements */} +
+
+
+
+
+ +
+
+
+ 🧩 +
+

+ Build anything, block by block. +

+

+ Every BlockNote document is a collection of blocksβ€”headings, lists, + images, and more. Use the built-in blocks, customize them to fit + your needs, or create entirely new ones. +

+
+ +
+ } + /> + } + /> + } /> + } + /> + } + /> + } + /> + } /> + } /> + } + /> + } /> + } /> + } /> + } + /> + } + /> + } + /> +
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/DigitalCommons.tsx b/docs/app/(home)/_components/DigitalCommons.tsx new file mode 100644 index 0000000000..82cadb3f35 --- /dev/null +++ b/docs/app/(home)/_components/DigitalCommons.tsx @@ -0,0 +1,127 @@ +"use client"; +import Link from "next/link"; +import React, { useRef, useState } from "react"; + +export const DigitalCommons: React.FC = () => { + const videoRef = useRef(null); + const [isPlaying, setIsPlaying] = useState(false); + + const handlePlayPause = () => { + if (videoRef.current) { + if (isPlaying) { + videoRef.current.pause(); + } else { + videoRef.current.play(); + } + setIsPlaying(!isPlaying); + } + }; + + return ( +
+ {/* Warm gradient overlay to harmonize with cream hero */} +
+ {/* Top edge gradient for smoother transition */} +
+ +
+ {/* Asymmetric layout: content + video (vertically centered) */} +
+ {/* Left: Editorial content */} +
+ {/* Eyebrow with EU flag only */} +
+ πŸ‡ͺπŸ‡Ί + + Digital Commons + +
+ + {/* Headline - editorial style */} +

+ Three nations choose +
+ + open source + {" "} + to power +
+ their digital future. +

+ + {/* Short punchy copy */} +

+ France, Germany, and the Netherlands partner to build{" "} + + Docs + + , a collaborative writing tool for thousands of public servants.{" "} + BlockNote is the engine. +

+ + {/* Compelling social proof - simpler */} +

+ "Building Digital Commons means better tools, data sovereignty, + and shared progress." +

+ + {/* CTA */} + + Partner with us + β†’ + +
+ + {/* Right: Video - vertically centered */} +
+ {/* Glow effect */} +
+ +
+ + + {/* Play button overlay */} + {!isPlaying && ( + + )} +
+
+
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/FAQ.tsx b/docs/app/(home)/_components/FAQ.tsx new file mode 100644 index 0000000000..158c5c691a --- /dev/null +++ b/docs/app/(home)/_components/FAQ.tsx @@ -0,0 +1,51 @@ +import React from "react"; + +const faqs = [ + { + question: "Isn't it easier to use a Headless editor framework?", + answer: + "There are a number of really powerful headless text editor frameworks available. In fact, BlockNote is built on Prosemirror and TipTap. However, even when using a headless library, it takes several months and requires deep expertise to build a fully-featured editor with a polished UI that your users expect.", + }, + { + question: "Is BlockNote ready for production use?", + answer: + "BlockNote is used by dozens of companies in production, ranging from startups to large enterprises and public institutions. Also, we didn't reinvent the wheel. The core editor is built on top of Prosemirror - a battle tested framework that powers software from Atlassian, Gitlab, the New York Times, and many others.", + }, + { + question: "Can I add my own extensions to BlockNote?", + answer: + "BlockNote comes with lot of functionality out-of-the-box, but we understand that every use case is different. You can easily customize the built-in UI Components, or create your own custom Blocks, Inline Content, and Styles. If you want to go even further, you can extend the core editor with additional Prosemirror or TipTap plugins.", + }, + { + question: "Is BlockNote really free?", + answer: + "100% of BlockNote is open source. We offer consultancy, support services and commercial licenses for specific XL packages to help sustain BlockNote. Explore our pricing page for more details.", + }, +]; + +export const FAQ: React.FC = () => { + return ( +
+
+
+

+ Questions? +

+
+ +
+ {faqs.map((faq, index) => ( +
+

+ {faq.question} +

+

+ {faq.answer} +

+
+ ))} +
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/FeatureAI.tsx b/docs/app/(home)/_components/FeatureAI.tsx new file mode 100644 index 0000000000..5d7bc76991 --- /dev/null +++ b/docs/app/(home)/_components/FeatureAI.tsx @@ -0,0 +1,62 @@ +import React, { useState } from "react"; +import { FeatureSection } from "./FeatureSection"; +import { ContentItem, FeatureWindow } from "./ui/FeatureWindow"; + +export const FeatureAI: React.FC = () => { + const [activeTab, setActiveTab] = useState<"toolbar" | "models" | "human">( + "toolbar", + ); + + const content: Record = { + toolbar: { + type: "video", + src: "/video/ai-select.mp4", + className: "px-4", + }, + models: { + type: "image", + src: "/img/screenshots/home/any_model.png", + alt: "Bring Any Model", + }, + human: { + type: "image", + src: "/img/screenshots/home/human_in_the_loop.png", + alt: "Human in the Loop", + }, + }; + + const tabs = [ + { + id: "toolbar", + icon: ✨, + label: "AI in the Editor", + description: + "Context-aware completions and edits directly in the document.", + }, + { + id: "models", + icon: πŸ”Œ, + label: "Bring Any Model", + description: "Connect OpenAI, Anthropic, or your own endpoints.", + }, + { + id: "human", + icon: 🀝, + label: "Human in the Loop", + description: "Users accept, reject, or refine AI suggestions.", + }, + ]; + + return ( + setActiveTab(id as any)} + reverse={true} + > + + + ); +}; diff --git a/docs/app/(home)/_components/FeatureCollab.tsx b/docs/app/(home)/_components/FeatureCollab.tsx new file mode 100644 index 0000000000..1689e10d7d --- /dev/null +++ b/docs/app/(home)/_components/FeatureCollab.tsx @@ -0,0 +1,64 @@ +import React, { useState } from "react"; +import { FeatureSection } from "./FeatureSection"; +import { ContentItem, FeatureWindow } from "./ui/FeatureWindow"; + +export const FeatureCollab: React.FC<{ + code: { realtime: string }; +}> = ({ code }) => { + const [activeTab, setActiveTab] = useState< + "realtime" | "comments" | "suggestions" + >("realtime"); + + const content: Record = { + realtime: { + type: "code", + file: "CollaborativeEditor.tsx", + code: code.realtime, + }, + comments: { + type: "image", + src: "/img/screenshots/home/comments.png", + alt: "Comments", + }, + suggestions: { + type: "image", + src: "/img/screenshots/home/versioning.png", + alt: "Versioning", + }, + }; + + const tabs = [ + { + id: "realtime", + icon: πŸ‘―, + label: "Real-Time Sync", + description: "Yjs-powered with automatic conflict resolution.", + }, + { + id: "comments", + icon: πŸ’¬, + label: "Comments", + description: "Inline threads and mentions keep conversations in context.", + }, + { + id: "suggestions", + icon: πŸ“, + label: "Suggestions & Versioning (coming soon)", + description: + "Track changes, accept or reject edits. Full document history.", + }, + ]; + + return ( + setActiveTab(id as any)} + reverse={false} + > + + + ); +}; diff --git a/docs/app/(home)/_components/FeatureDX.tsx b/docs/app/(home)/_components/FeatureDX.tsx new file mode 100644 index 0000000000..5778ae067f --- /dev/null +++ b/docs/app/(home)/_components/FeatureDX.tsx @@ -0,0 +1,63 @@ +import React, { useState } from "react"; +import { FeatureSection } from "./FeatureSection"; +import { ContentItem, FeatureWindow } from "./ui/FeatureWindow"; + +export const FeatureDX: React.FC<{ + code: { theming: string; extend: string }; +}> = ({ code }) => { + const [activeTab, setActiveTab] = useState<"types" | "theming" | "extend">( + "types", + ); + + const content: Record = { + types: { + type: "image", + src: "/img/screenshots/home/code-typescript-support.png", + alt: "Type-Safe Schema", + }, + theming: { + type: "code", + file: "Editor.tsx", + code: code.theming, + }, + extend: { + type: "code", + file: "CustomBlock.tsx", + code: code.extend, + }, + }; + + const tabs = [ + { + id: "types", + icon: πŸ“, + label: "Type-Safe", + description: "Full autocompletion and type inference for custom schemas.", + }, + { + id: "theming", + icon: 🎨, + label: "Bring your Design System", + description: "Works with Mantine, shadcn/ui, or go headless.", + }, + { + id: "extend", + icon: πŸ”§, + label: "Extend Everything", + description: "Create custom blocks, inline content, menus and more.", + }, + ]; + + return ( + setActiveTab(id as any)} + reverse={true} + > + + + ); +}; diff --git a/docs/app/(home)/_components/FeatureSection.tsx b/docs/app/(home)/_components/FeatureSection.tsx new file mode 100644 index 0000000000..ef2c62eea1 --- /dev/null +++ b/docs/app/(home)/_components/FeatureSection.tsx @@ -0,0 +1,101 @@ +import React from "react"; + +interface FeatureTab { + id: string; + icon: React.ReactNode; + label: string; + description: string; +} + +interface FeatureSectionProps { + title: string; + description: string; + tabs: FeatureTab[]; + activeTabId: string; + onTabChange: (id: string) => void; + // The content to display on the right side (Visual or Code) + children: React.ReactNode; + // Optional: Swap order for visual variety (Left/Right) + reverse?: boolean; +} + +export const FeatureSection: React.FC = ({ + title, + description, + tabs, + activeTabId, + onTabChange, + children, + reverse = false, +}) => { + return ( +
+ {/* Left Text & Tabs */} +
+

+ {title} +

+

+ {description} +

+ +
+ {tabs.map((tab) => { + const isActive = activeTabId === tab.id; + // Dynamic styles based on active state could be passed or handled here + // For simplicity, we'll use a generic active style or specific color logic if needed. + // But CodePlayground had specific colors (purple, amber, blue). + // Let's rely on the parent or use a generic active style here for now, + // or we can add a 'color' prop to FeatureTab if we want distinct colors per tab. + + return ( + + ); + })} +
+
+ + {/* Right Visual */} +
+ {/*
*/} +
+ {children} +
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/FeatureUX.tsx b/docs/app/(home)/_components/FeatureUX.tsx new file mode 100644 index 0000000000..e1fa0893a4 --- /dev/null +++ b/docs/app/(home)/_components/FeatureUX.tsx @@ -0,0 +1,59 @@ +import React, { useState } from "react"; +import { FeatureSection } from "./FeatureSection"; +import { ContentItem, FeatureWindow } from "./ui/FeatureWindow"; +export const FeatureUX: React.FC = () => { + const [activeTab, setActiveTab] = useState<"components" | "ai" | "blocks">( + "components", + ); + + const content: Record = { + components: { + type: "video", + src: "/video/batteries-included.mp4", + }, + ai: { + type: "video", + src: "/video/ai-select.mp4", + className: "px-4", + }, + blocks: { + type: "video", + src: "/video/dragdrop.mp4", + }, + }; + + const tabs = [ + { + id: "components", + icon: πŸ”‹, + label: "Ready to Use", + description: + "Slash menus, formatting toolbars, and drag handles work instantly.", + }, + { + id: "ai", + icon: ✨, + label: "AI Assistance", + description: "Write and redact content with AI.", + }, + { + id: "blocks", + icon: 🧱, + label: "Block-Based", + description: "Drag, drop, and nest content blocks.", + }, + ]; + + return ( + setActiveTab(id as any)} + reverse={false} + > + + + ); +}; diff --git a/docs/app/(home)/_components/FrameworkPill.tsx b/docs/app/(home)/_components/FrameworkPill.tsx new file mode 100644 index 0000000000..eef63da2ba --- /dev/null +++ b/docs/app/(home)/_components/FrameworkPill.tsx @@ -0,0 +1,12 @@ +import React from "react"; + +export const FrameworkPill: React.FC<{ + name: string; + color: string; + icon?: React.ReactNode; +}> = ({ name, color, icon }) => ( +
+ {icon ||
} + {name} +
+); diff --git a/docs/app/(home)/_components/Hero.tsx b/docs/app/(home)/_components/Hero.tsx new file mode 100644 index 0000000000..1db7cc7898 --- /dev/null +++ b/docs/app/(home)/_components/Hero.tsx @@ -0,0 +1,70 @@ +"use client"; +import Link from "next/link"; +import React from "react"; +import { HeroVideo } from "./HeroVideo"; +import { TextLoop } from "./TextLoop"; + +export const Hero: React.FC = () => { + const BADGES = [ + { icon: "⭐️", text: "100k+ weekly installs" }, + { icon: "πŸ›‘οΈ", text: "100% Open source & self-hostable" }, + { icon: "✨", text: "AI Ready" }, + ]; + + return ( +
+ {/* Hero Section */} +
+ {/* Passive Neural Background */} +
+ {/* Badge */} + + {BADGES.map((badge, index) => ( +
+ + {badge.icon} + {badge.text} + +
+ ))} +
+ +

+ Build a Notion-style{" "} + editor in minutes. +

+

+ The AI-native, open source rich + text editor for React. Add a{" "} + fully customizable modern block-based editing + experience to your product that users will love. +

+ +
+ + View Demo + + β†’ + + + + Documentation + +
+
+
+ +
+
+
+ ); +}; diff --git a/docs/app/(home)/_components/HeroVideo.tsx b/docs/app/(home)/_components/HeroVideo.tsx new file mode 100644 index 0000000000..a36cccbe73 --- /dev/null +++ b/docs/app/(home)/_components/HeroVideo.tsx @@ -0,0 +1,77 @@ +"use client"; +import Link from "next/link"; +import React from "react"; + +export const HeroVideo: React.FC = () => { + return ( + <> + +
+ {/* Editor Placeholder */} + {/* Editor Preview */} + +
+
+ {/* Browser Chrome */} +