diff --git a/.gitignore b/.gitignore index 30ea395..ee7eb1f 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,7 @@ yarn-error.log* next-env.d.ts # content-collections -.content-collections \ No newline at end of file +.content-collections + +# agents +.agents \ No newline at end of file diff --git a/apps/website/next.config.ts b/apps/website/next.config.ts index dbb372a..122bd94 100644 --- a/apps/website/next.config.ts +++ b/apps/website/next.config.ts @@ -19,6 +19,11 @@ const nextConfig: NextConfig = { destination: "/docs/getting-started/prerequisites", permanent: true, }, + { + source: "/docs/react/code-block-mdx", + destination: "/docs/react/code-block-mdx-shiki", + permanent: true, + }, { source: "/components", destination: "/docs/react/copy-button", diff --git a/apps/website/src/app/page.tsx b/apps/website/src/app/page.tsx index d8479bc..2872245 100644 --- a/apps/website/src/app/page.tsx +++ b/apps/website/src/app/page.tsx @@ -1,16 +1,26 @@ import type { Metadata } from "next"; +import { cn } from "@/utils/cn"; import { globals } from "@/globals"; import { notFound } from "next/navigation"; import { getGeneralDocument } from "@/utils/docs"; -import MDX from "@/components/mdx"; import Article from "@/components/docs/doc-article"; import Container from "@/components/container"; import Header from "@/components/header"; import Footer from "@/components/footer"; +import { GitHub } from "@/components/ui/svgs"; +import { ArrowUpRightIcon } from "lucide-react"; + +import MDX from "@/components/mdx"; +import GetStartedLink from "@/components/home/get-started-link"; +import InstallCommands from "@/components/home/install-commands"; + +import { buttonVariants } from "@/components/ui/button"; +import { ExternalLink } from "@/components/ui/external-link"; + export async function generateMetadata(): Promise { const websiteUrl = "https://code-blocks.pheralb.dev"; const data = getGeneralDocument("home"); @@ -37,9 +47,52 @@ const Home = () => {
-
- -
+
+
+

+ Build beautiful code blocks +

+

+ Ready to use UI components and utilities to show your snippets + beautifully. +

+
+
+ + +
+ + GitHub +
+ +
+
+ +
+
+
+
+ +
+
diff --git a/apps/website/src/components/code-block/blocks/copy-text-morph.tsx b/apps/website/src/components/code-block/blocks/copy-text-morph.tsx index b2f5eb6..7bb672e 100644 --- a/apps/website/src/components/code-block/blocks/copy-text-morph.tsx +++ b/apps/website/src/components/code-block/blocks/copy-text-morph.tsx @@ -1,97 +1,15 @@ "use client"; -import { - useMemo, - useId, - useEffect, - useState, - type ComponentProps, -} from "react"; - -import { - motion, - AnimatePresence, - type Transition, - type Variants, -} from "motion/react"; - import { cn } from "@/utils/cn"; import { copyToClipboard } from "@/utils/copy"; +import { TextMorph } from "@/components/ui/text-morph"; +import { useEffect, useState, type ComponentProps } from "react"; interface CopyTextAnimatedProps extends ComponentProps<"button"> { content: string; size?: "xs" | "sm"; } -export type TextMorphProps = { - children: string; - as?: React.ElementType; - className?: string; - style?: React.CSSProperties; - variants?: Variants; - transition?: Transition; -}; - -export function TextMorph({ - children, - as: Component = "p", - className, - style, - variants, - transition, -}: TextMorphProps) { - const uniqueId = useId(); - - const characters = useMemo(() => { - const charCounts: Record = {}; - - return children.split("").map((char) => { - const lowerChar = char.toLowerCase(); - charCounts[lowerChar] = (charCounts[lowerChar] || 0) + 1; - - return { - id: `${uniqueId}-${lowerChar}${charCounts[lowerChar]}`, - label: char === " " ? "\u00A0" : char, - }; - }); - }, [children, uniqueId]); - - const defaultVariants: Variants = { - initial: { opacity: 0 }, - animate: { opacity: 1 }, - exit: { opacity: 0 }, - }; - - const defaultTransition: Transition = { - type: "spring", - stiffness: 280, - damping: 18, - mass: 0.3, - }; - - return ( - - - {characters.map((character) => ( - - ))} - - - ); -} - const CopyTextMorph = ({ content, size = "sm", diff --git a/apps/website/src/components/code-block/client/shiki.tsx b/apps/website/src/components/code-block/client/shiki.tsx index 70e52a0..3b76059 100644 --- a/apps/website/src/components/code-block/client/shiki.tsx +++ b/apps/website/src/components/code-block/client/shiki.tsx @@ -52,7 +52,6 @@ const CodeblockShiki = ({ const classNames = cn("w-full overflow-x-auto", className); - // SSR fallback return highlightedHtml ? (
) : (
-
+      
         {code}
       
diff --git a/apps/website/src/components/code-block/mdx/pre-shiki.tsx b/apps/website/src/components/code-block/mdx/pre-shiki.tsx index acc96d8..c41e13d 100644 --- a/apps/website/src/components/code-block/mdx/pre-shiki.tsx +++ b/apps/website/src/components/code-block/mdx/pre-shiki.tsx @@ -1,6 +1,7 @@ import type { ComponentProps } from "react"; import type { MDXComponents } from "mdx/types"; +import { cn } from "@/utils/cn"; import { reactToText } from "@/utils/react-to-text"; import { @@ -23,15 +24,23 @@ const PreShikiComponent: MDXComponents = { const title = props["data-title"]; const language = props["data-language"]; return ( - - - - - {title ?? `.${language}`} - - - - + + {title && ( + + + + {title} + + + + )} + + {!title && ( + + )}
{children}
diff --git a/apps/website/src/components/code-block/mdx/pre-sugar-high.tsx b/apps/website/src/components/code-block/mdx/pre-sugar-high.tsx index a34c78f..2b8e6c9 100644 --- a/apps/website/src/components/code-block/mdx/pre-sugar-high.tsx +++ b/apps/website/src/components/code-block/mdx/pre-sugar-high.tsx @@ -6,7 +6,6 @@ import { reactToText } from "@/utils/react-to-text"; import { CodeBlock, - CodeBlockHeader, CodeBlockContent, } from "@/components/code-block/code-block"; import { CopyButton } from "@/components/code-block/copy-button"; @@ -20,12 +19,13 @@ const PreSugarHighComponent: MDXComponents = { code: content, }); return ( - - - - - -
+      
+        
+          
+          
             
           
diff --git a/apps/website/src/components/docs/doc-options.tsx b/apps/website/src/components/docs/doc-options.tsx index 90da02d..2772ed1 100644 --- a/apps/website/src/components/docs/doc-options.tsx +++ b/apps/website/src/components/docs/doc-options.tsx @@ -18,9 +18,12 @@ import { ArrowUpRightIcon, CheckCheckIcon, ChevronDownIcon, + CodeIcon, CopyIcon, } from "lucide-react"; +import { TextMorph } from "@/components/ui/text-morph"; import { ExternalLink } from "@/components/ui/external-link"; +import { GitHub, Claude, OpenAI } from "@/components/ui/svgs"; interface DocOptionsProps extends ComponentProps<"div"> { content: string; @@ -28,10 +31,27 @@ interface DocOptionsProps extends ComponentProps<"div"> { file: string; } +const aiPrompt = ({ url }: { url: string }) => + `Read ${url} I want to ask questions about it`; + +const aiLinks = [ + { + label: "Open in ChatGPT", + icon: OpenAI, + href: "https://chat.openai.com/?prompt=", + }, + { + label: "Open in Claude", + icon: Claude, + href: "https://claude.ai/new?q=", + }, +]; + const DocOptions = ({ content, folder, file }: DocOptionsProps) => { const [isCopied, setIsCopied] = useState(false); const [openDropdown, setOpenDropdown] = useState(false); const pathname = usePathname(); + const fullUrl = `${globals.websiteUrl}${pathname}.mdx`; const handleCopyMarkdown = () => { copyToClipboard(content); @@ -49,7 +69,7 @@ const DocOptions = ({ content, folder, file }: DocOptionsProps) => { className="w-full rounded-r-none border-r-0" > {isCopied ? : } - {isCopied ? "Copied" : "Copy"} + {isCopied ? "Copied" : "Copy"} {
- - - View as Markdown - - + + } + > + + View as Markdown + - - - Edit on GitHub - - + + } + > + + Edit on GitHub + + {aiLinks.map((link) => ( + + } + > + + {link.label} + + + ))} ); diff --git a/apps/website/src/components/docs/show-source.tsx b/apps/website/src/components/docs/show-source.tsx index be18d7c..942fc5b 100644 --- a/apps/website/src/components/docs/show-source.tsx +++ b/apps/website/src/components/docs/show-source.tsx @@ -3,6 +3,7 @@ import type { RegistryComponent } from "@/types/registry"; interface ShowSourceProps extends ComponentProps<"div"> { component: RegistryComponent["title"]; + title?: string; children?: ReactNode; } diff --git a/apps/website/src/components/docs/sidebar-data.ts b/apps/website/src/components/docs/sidebar-data.ts index 660ec0d..b2b3e4a 100644 --- a/apps/website/src/components/docs/sidebar-data.ts +++ b/apps/website/src/components/docs/sidebar-data.ts @@ -63,7 +63,16 @@ export const ReactComponentsData: SidebarGroupData = { { title: "Code Block MDX", icon: FileCodeCornerIcon, - href: "/docs/react/code-block-mdx", + subItems: [ + { + title: "Shiki", + href: "/docs/react/code-block-mdx-shiki", + }, + { + title: "Sugar High", + href: "/docs/react/code-block-mdx-sugar-high", + }, + ], }, { title: "Code Block Client", @@ -178,5 +187,3 @@ export const SugarHighData: SidebarGroupData = { }, ], }; - - diff --git a/apps/website/src/components/features.tsx b/apps/website/src/components/features.tsx deleted file mode 100644 index a210816..0000000 --- a/apps/website/src/components/features.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import type { ReactNode } from "react"; - -import { cn } from "@/utils/cn"; -import { BoxIcon, ClipboardIcon, HighlighterIcon } from "lucide-react"; -import { ShadcnUI } from "@/components/ui/svgs"; - -interface FeatureCardProps { - icon: ReactNode; - title: string; - description: string; - children?: ReactNode; -} - -const iconSize = 24; -const iconClassName = cn("text-neutral-600 dark:text-neutral-400"); - -const CardDecorator = () => ( - <> - - - - - -); - -const FeatureCard = ({ - icon, - title, - description, - children, -}: FeatureCardProps) => { - return ( -
- -
{icon}
-

- {title} -

-

- {description} -

- {children} -
- ); -}; - -export default function Features() { - return ( -
- } - title="Copy-Paste" - description="Copy the components and utilities you need and paste them into your project. It's 100% yours." - /> - } - title="Syntax Highlighter" - description="Use Shiki or Sugar-High to add syntax highlighting to your code blocks." - /> - } - title="Blocks" - description="Built-in components to extend your code blocks with extra content and interactivity." - /> - - } - title="shadcn/ui compatible" - description="Add components & utilities using shadcn/ui CLI." - /> -
- ); -} diff --git a/apps/website/src/components/hero.tsx b/apps/website/src/components/hero.tsx deleted file mode 100644 index 7eb62ee..0000000 --- a/apps/website/src/components/hero.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { ArrowUpRightIcon } from "lucide-react"; - -import { cn } from "@/utils/cn"; -import { globals } from "@/globals"; - -import { GitHub } from "@/components/ui/svgs/github"; -import { buttonVariants } from "@/components/ui/button"; -import GetStartedLink from "@/components/get-started-link"; -import { ExternalLink } from "@/components/ui/external-link"; - -const Hero = () => { - return ( -
-
-

- Build beautiful code blocks -

-

- Ready to use UI components and utilities to show your snippets - beautifully. -

-
-
- - -
- - View on GitHub -
- -
-
-
- ); -}; - -export default Hero; diff --git a/apps/website/src/components/get-started-link.tsx b/apps/website/src/components/home/get-started-link.tsx similarity index 100% rename from apps/website/src/components/get-started-link.tsx rename to apps/website/src/components/home/get-started-link.tsx diff --git a/apps/website/src/components/home/install-commands.tsx b/apps/website/src/components/home/install-commands.tsx new file mode 100644 index 0000000..016b0a1 --- /dev/null +++ b/apps/website/src/components/home/install-commands.tsx @@ -0,0 +1,113 @@ +"use client"; + +import { useState } from "react"; + +import { usePackageManager } from "@/stores/packageManager"; +import { + CodeBlock, + CodeBlockContent, + CodeBlockHeader, +} from "@/components/code-block/code-block"; +import { CopyButton } from "@/components/code-block/copy-button"; +import { CodeblockShiki } from "@/components/code-block/client/shiki"; +import { SelectPackageManager } from "@/components/code-block/blocks/copy-with-select-package-manager"; +import { ShadcnUI } from "@/components/ui/svgs/shadcn"; +import { Shiki, SugarHigh } from "@/components/ui/svgs"; +import { cn } from "@/utils/cn"; + +type Highlighter = "shiki" | "sugar-high"; +type Variant = "client" | "mdx"; + +const registryUrl = "https://code-blocks.pheralb.dev/r/"; + +const Highlighters = [ + { id: "shiki" as Highlighter, label: "Shiki", icon: Shiki }, + { id: "sugar-high" as Highlighter, label: "Sugar High", icon: SugarHigh }, +]; + +const Variants: { id: Variant; label: string }[] = [ + { id: "client", label: "Client" }, + { id: "mdx", label: "MDX" }, +]; + +const pkgCommands: Record = { + npm: "npx shadcn@latest add", + pnpm: "pnpm dlx shadcn@latest add", + yarn: "yarn shadcn@latest add", + bun: "bunx --bun shadcn@latest add", +}; + +const segmentBase = + "flex cursor-pointer items-center gap-1.5 rounded-md px-3 py-1 text-xs font-medium transition-all duration-150 ease-out"; +const segmentActive = + "bg-neutral-900 text-white shadow-sm dark:bg-neutral-100 dark:text-neutral-900"; +const segmentInactive = + "text-neutral-500 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-200"; + +const InstallCommands = () => { + const { packageManager } = usePackageManager(); + const [highlighter, setHighlighter] = useState("shiki"); + const [variant, setVariant] = useState("client"); + + const selectedHL = + Highlighters.find((h) => h.id === highlighter) ?? Highlighters[0]; + const command = pkgCommands[packageManager] ?? pkgCommands.npm; + const fullCommand = `${command} ${registryUrl}${variant}-${selectedHL.id}.json`; + + return ( +
+ + +
+ + shadcn/ui Command +
+
+ + +
+
+ + + +
+
+
+ {Variants.map((v) => ( + + ))} +
+
+
+ {Highlighters.map((hl) => { + const Icon = hl.icon; + return ( + + ); + })} +
+
+
+ ); +}; + +export default InstallCommands; diff --git a/apps/website/src/components/mdx/mdx-components.tsx b/apps/website/src/components/mdx/mdx-components.tsx index 9168424..2903d73 100644 --- a/apps/website/src/components/mdx/mdx-components.tsx +++ b/apps/website/src/components/mdx/mdx-components.tsx @@ -5,7 +5,6 @@ import Showcase from "@/components/docs/showcase"; import ShowProps from "@/components/docs/show-props"; import ShowSource from "@/components/docs/show-source"; import ComponentPreview from "@/components/docs/component-preview"; -import MDXHighlightTabs from "@/components/docs/mdx-highlight-tabs"; import CopyShadcnCommand from "@/components/docs/copy-shadcn-command"; import DocNeutralColors from "@/components/docs/doc-neutral-colors"; import CodeBlockSugarHighExample from "@/components/previews/code-block-sugar-high-example"; @@ -20,13 +19,9 @@ import { // MDX Components: import Grid from "@/components/ui/grid"; -import { TabsContent } from "@/components/ui/tabs"; import { AMDXComponent } from "@/components/mdx/a-component"; import { PreShikiComponent } from "@/components/code-block/mdx/pre-shiki"; - -// Homepage: -import Hero from "@/components/hero"; -import Features from "@/components/features"; +import { SugarHighPreview } from "@/components/previews/sugar-high-mdx-preview"; // From Registry: import { CopyButton } from "@/components/code-block/copy-button"; @@ -36,9 +31,6 @@ const MDXCustomComponents: MDXComponents = { ...AMDXComponent, //
 Shiki:
   ...PreShikiComponent,
-  // MDX CodeBlock Source Tabs:
-  MDXHighlightTabs,
-  TabsContent,
   // Blocks:
   Grid,
   CodeBlockSelectPkg,
@@ -51,12 +43,11 @@ const MDXCustomComponents: MDXComponents = {
   Showcase,
   CopyButton,
   ComponentPreview,
-  Hero,
-  Features,
   CreateReactApp,
   HighlightsAvailable,
   DocNeutralColors,
   LineAnchorsExample,
+  SugarHighPreview,
 };
 
 export { MDXCustomComponents };
diff --git a/apps/website/src/components/previews/sugar-high-mdx-preview.tsx b/apps/website/src/components/previews/sugar-high-mdx-preview.tsx
new file mode 100644
index 0000000..042910e
--- /dev/null
+++ b/apps/website/src/components/previews/sugar-high-mdx-preview.tsx
@@ -0,0 +1,43 @@
+import { highlight } from "@/utils/sugar-high/highlight";
+
+import {
+  CodeBlock,
+  CodeBlockHeader,
+  CodeBlockContent,
+  CodeBlockGroup,
+} from "@/components/code-block/code-block";
+
+import { cn } from "@/utils/cn";
+import { CopyButton } from "@/components/code-block/copy-button";
+
+interface SugarHighPreviewProps {
+  code: string;
+  title?: string;
+}
+
+export function SugarHighPreview({ code, title }: SugarHighPreviewProps) {
+  const codeHTML = highlight({ code });
+  return (
+    
+      {title && (
+        
+          
+            {title}
+          
+          
+        
+      )}
+      
+        {!title && (
+          
+        )}
+        
+          
+        
+
+
+ ); +} diff --git a/apps/website/src/components/registry/data.tsx b/apps/website/src/components/registry/data.tsx index facf0cc..d78e8cb 100644 --- a/apps/website/src/components/registry/data.tsx +++ b/apps/website/src/components/registry/data.tsx @@ -7,6 +7,7 @@ const stylesFolder = "src/styles"; const storesFolder = "src/stores"; const componentsFolder = "src/components"; const codeblockComponent = "src/components/code-block"; +const uiComponentsFolder = "src/components/ui"; // CSS Files: const CSSFiles: RegistryComponent[] = [ @@ -272,6 +273,17 @@ const UIComponents: RegistryComponent[] = [ target: "src/components/code-block/copy-button.tsx", }, }, + { + title: "Text Morph", + fileType: "tsx", + fileSource: `${uiComponentsFolder}/text-morph.tsx`, + shadcnRegistry: { + name: "text-morph", + type: "registry:ui", + dependencies: ["motion"], + target: "src/components/ui/text-morph.tsx", + }, + }, ]; // Blocks: @@ -310,6 +322,7 @@ const Blocks: RegistryComponent[] = [ name: "block-copy-text-morph", type: "registry:block", dependencies: ["motion"], + registryDependencies: ["copy-to-clipboard", "text-morph"], target: "src/components/code-block/blocks/copy-text-morph.tsx", }, }, diff --git a/apps/website/src/components/ui/button.tsx b/apps/website/src/components/ui/button.tsx index eb8de7c..52da2bc 100644 --- a/apps/website/src/components/ui/button.tsx +++ b/apps/website/src/components/ui/button.tsx @@ -17,7 +17,7 @@ const buttonVariants = cva( secondary: "bg-neutral-100 text-neutral-900 hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80", ghost: - "hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", + "hover:bg-neutral-200 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50", link: "text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50", }, size: { diff --git a/apps/website/src/components/ui/dropdown-menu.tsx b/apps/website/src/components/ui/dropdown-menu.tsx index 635ea9f..cd39dd1 100644 --- a/apps/website/src/components/ui/dropdown-menu.tsx +++ b/apps/website/src/components/ui/dropdown-menu.tsx @@ -1,8 +1,9 @@ "use client"; +import type { ComponentProps } from "react"; + import { cn } from "@/utils/cn"; import { Menu as MenuPrimitive } from "@base-ui/react/menu"; -import type { ComponentProps } from "react"; function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) { return ; @@ -69,7 +70,7 @@ function DropdownMenuItem({ data-inset={inset} data-variant={variant} className={cn( - "group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-neutral-100 focus:text-neutral-900 not-data-[variant=destructive]:focus:**:text-neutral-900 data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 data-[variant=destructive]:text-red-500 data-[variant=destructive]:focus:bg-red-500/10 data-[variant=destructive]:focus:text-red-500 dark:focus:bg-neutral-800 dark:focus:text-neutral-50 dark:not-data-[variant=destructive]:focus:**:text-neutral-50 dark:data-[variant=destructive]:text-red-900 dark:dark:data-[variant=destructive]:focus:bg-red-900/20 dark:data-[variant=destructive]:focus:bg-red-500/20 dark:data-[variant=destructive]:focus:text-red-900 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:text-neutral-600 dark:[&_svg]:text-neutral-400 data-[variant=destructive]:*:[svg]:text-red-500 dark:data-[variant=destructive]:*:[svg]:text-red-900", + "group/dropdown-menu-item relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-neutral-100 focus:text-neutral-900 not-data-[variant=destructive]:focus:**:text-neutral-900 data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-8 data-[variant=destructive]:text-red-500 data-[variant=destructive]:focus:bg-red-500/10 data-[variant=destructive]:focus:text-red-500 dark:focus:bg-neutral-800 dark:focus:text-neutral-50 dark:not-data-[variant=destructive]:focus:**:text-neutral-50 dark:data-[variant=destructive]:text-red-900 dark:dark:data-[variant=destructive]:focus:bg-red-900/20 dark:data-[variant=destructive]:focus:bg-red-500/20 dark:data-[variant=destructive]:focus:text-red-900 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:text-neutral-600 dark:[&_svg]:text-neutral-400 data-[variant=destructive]:*:[svg]:text-red-500 dark:data-[variant=destructive]:*:[svg]:text-red-900", className, )} {...props} diff --git a/apps/website/src/components/ui/svgs/claude.tsx b/apps/website/src/components/ui/svgs/claude.tsx new file mode 100644 index 0000000..538fcbe --- /dev/null +++ b/apps/website/src/components/ui/svgs/claude.tsx @@ -0,0 +1,12 @@ +import type { SVGProps } from "react"; + +const Claude = (props: SVGProps) => ( + + + +); + +export { Claude }; diff --git a/apps/website/src/components/ui/svgs/index.tsx b/apps/website/src/components/ui/svgs/index.tsx index 8cdfb1c..fa2de7c 100644 --- a/apps/website/src/components/ui/svgs/index.tsx +++ b/apps/website/src/components/ui/svgs/index.tsx @@ -11,4 +11,6 @@ export * from "./vite"; export * from "./radix-ui"; export * from "./base-ui"; export * from "./tanstack"; -export * from "./motion"; \ No newline at end of file +export * from "./motion"; +export * from "./claude"; +export * from "./openai"; \ No newline at end of file diff --git a/apps/website/src/components/ui/svgs/openai.tsx b/apps/website/src/components/ui/svgs/openai.tsx new file mode 100644 index 0000000..364029f --- /dev/null +++ b/apps/website/src/components/ui/svgs/openai.tsx @@ -0,0 +1,12 @@ +import type { SVGProps } from "react"; + +const OpenAI = (props: SVGProps) => ( + + + +); + +export { OpenAI }; diff --git a/apps/website/src/components/ui/text-morph.tsx b/apps/website/src/components/ui/text-morph.tsx new file mode 100644 index 0000000..c713ac3 --- /dev/null +++ b/apps/website/src/components/ui/text-morph.tsx @@ -0,0 +1,79 @@ +"use client"; + +import { + AnimatePresence, + motion, + type Transition, + type Variants, +} from "motion/react"; +import { cn } from "@/utils/cn"; +import { useMemo, useId } from "react"; + +export type TextMorphProps = { + children: string; + as?: React.ElementType; + className?: string; + style?: React.CSSProperties; + variants?: Variants; + transition?: Transition; +}; + +export function TextMorph({ + children, + as: Component = "p", + className, + style, + variants, + transition, +}: TextMorphProps) { + const uniqueId = useId(); + + const characters = useMemo(() => { + const charCounts: Record = {}; + + return children.split("").map((char) => { + const lowerChar = char.toLowerCase(); + charCounts[lowerChar] = (charCounts[lowerChar] || 0) + 1; + + return { + id: `${uniqueId}-${lowerChar}${charCounts[lowerChar]}`, + label: char === " " ? "\u00A0" : char, + }; + }); + }, [children, uniqueId]); + + const defaultVariants: Variants = { + initial: { opacity: 0 }, + animate: { opacity: 1 }, + exit: { opacity: 0 }, + }; + + const defaultTransition: Transition = { + type: "spring", + stiffness: 280, + damping: 18, + mass: 0.3, + }; + + return ( + + + {characters.map((character) => ( + + ))} + + + ); +} diff --git a/apps/website/src/docs/home.mdx b/apps/website/src/docs/home.mdx index 5f68fa3..78e8eaa 100644 --- a/apps/website/src/docs/home.mdx +++ b/apps/website/src/docs/home.mdx @@ -4,11 +4,11 @@ description: Ready to use UI components and utilities to show your snippets beau category: [General] --- - +## Basic Usage -
+Render a syntax-highlighted code block with no extra configuration needed. -```tsx {5} lineNumbers title="Code Block Example" +```tsx export default function MyApp() { return (
@@ -19,6 +19,116 @@ export default function MyApp() { } ``` - +## Header -
+Add a `title` to the code block to display a filename or label in the header bar. + +```tsx title="MyButton.tsx" +export default function MyButton() { + return ( + + ); +} +``` + +## Line Numbers + +Enable `lineNumbers` to show line numbers on the left side of the code block. + +```tsx lineNumbers +export default function MyButton() { + return ( + + ); +} +``` + +## Notation Diff + +Use `[!code --]` and `[!code ++]` comments to mark removed and added lines with colored backgrounds. + +```tsx +export default function MyButton() { + return ( + + ); +} +``` + +## Notation Focus + +Use `[!code focus]` to dim all lines except the focused one, drawing attention to a specific part of the code. + +```tsx +export default function MyButton() { + return ( + + ); +} +``` + +## Meta Highlight + +Pass line numbers in the meta string (e.g. `{3,4}` or `{3-5}`) to highlight specific lines with a background color. + +```tsx {3-5} +export default function MyButton() { + return ( + + ); +} +``` + +## Word Highlight + +Wrap a word or phrase in `/slashes/` in the meta string to highlight every occurrence of that token in the block. + +```tsx /bg-blue-500/ +export default function MyButton() { + return ( + + ); +} +``` + +## Word Wrap + +Long lines wrap automatically inside the container instead of overflowing horizontally — no meta string required. + +```tsx wordWrap +export default function MyButton() { + return ( + + ); +} +``` + +## Line Anchors + +Each line gets a unique `id` and `data-line` attribute, enabling deep-linking directly to a specific line via the URL hash. + +```tsx prefix="example" lineNumbers +export default function MyButton() { + return ( + + ); +} +``` diff --git a/apps/website/src/docs/react/code-block-mdx.mdx b/apps/website/src/docs/react/code-block-mdx-shiki.mdx similarity index 58% rename from apps/website/src/docs/react/code-block-mdx.mdx rename to apps/website/src/docs/react/code-block-mdx-shiki.mdx index 06be582..6bcc2c7 100644 --- a/apps/website/src/docs/react/code-block-mdx.mdx +++ b/apps/website/src/docs/react/code-block-mdx-shiki.mdx @@ -1,10 +1,10 @@ --- -title: Code Block MDX -description: Create a MDX Code Block component with syntax highlighting and copy button. -category: [React, MDX, Components] +title: Code Block MDX with Shiki +description: Create a Code Block component for MDX using Shiki for syntax highlighting. +category: [React, MDX, Shiki, Components] --- - + ```tsx import type { ComponentProps } from "react"; @@ -22,19 +22,8 @@ export default MyComponent; ### shadcn/ui - - - - - - - - - - - ### Manual Before creating the MDX component, make sure you have the basic component structure: @@ -55,11 +44,6 @@ Before creating the MDX component, make sure you have the basic component struct ## Highlighter Setup -Setup your MDX highlighter: - - - - 1. Setup `data-title` & `data-language` properties: @@ -93,31 +77,3 @@ const MDXComponents: MDXComponentsType = { ...PreShikiComponent, }; ``` - - - - -1. Setup Sugar-High highlighter: - - - -2. Create your `PreSugarHighComponent` component: - - - -3. Usage: - -In your MDX Components object, add the new `PreSugarHighComponent` component: - -```ts {3,6} -import type { MDXComponents as MDXComponentsType } from "mdx/types"; - -import { PreShikiComponent } from "@/components/code-block/pre-shiki"; - -const MDXComponents: MDXComponentsType = { - ...PreShikiComponent, -}; -``` - - - diff --git a/apps/website/src/docs/react/code-block-mdx-sugar-high.mdx b/apps/website/src/docs/react/code-block-mdx-sugar-high.mdx new file mode 100644 index 0000000..9f17623 --- /dev/null +++ b/apps/website/src/docs/react/code-block-mdx-sugar-high.mdx @@ -0,0 +1,65 @@ +--- +title: Code Block MDX with Sugar-High +description: Create a Code Block component for MDX using Sugar-High for syntax highlighting. +category: [React, MDX, Sugar-High, Components] +--- + + + +) => { + return
Hello, World!
; +}; + +export default MyComponent;`} /> + +
+ +## Installation + +### shadcn/ui + + + +### Manual + +Before creating the MDX component, make sure you have the basic component structure: + + + +1. Install the following dependencies: + + + +2. Create the Copy Button component: + + + +3. Create a `react-to-text` utility to get plain text from `pre` elements: + + + +## Highlighter Setup + +1. Setup Sugar-High highlighter: + + + +2. Create your `PreSugarHighComponent` component: + + + +3. Usage: + +In your MDX Components object, add the new `PreSugarHighComponent` component: + +```ts {3,6} +import type { MDXComponents as MDXComponentsType } from "mdx/types"; + +import { PreSugarHighComponent } from "@/components/code-block/pre-sugar-high"; + +const MDXComponents: MDXComponentsType = { + ...PreSugarHighComponent, +}; +``` diff --git a/apps/website/src/docs/shiki/notation-diff.mdx b/apps/website/src/docs/shiki/notation-diff.mdx index 08bb6ec..f81fa0e 100644 --- a/apps/website/src/docs/shiki/notation-diff.mdx +++ b/apps/website/src/docs/shiki/notation-diff.mdx @@ -51,6 +51,8 @@ export { rehypeShikiOptions }; 2. To add diff notation to your code blocks, use the `[!code ++]` and `[!code --]` markers to indicate added and removed lines, respectively: +- Typescript/JavaScript: + ````mdx {5,6} ``` const code = `type Languages = "javascript" | "typescript";`; @@ -62,3 +64,20 @@ const html = highlighter.codeToHtml(code, { }); ``` ```` + +- React Code (TSX/JSX): + +Use the `{/* [!code ++] */}` and `{/* [!code --] */}` comment syntax instead: + +````mdx {4,5} +```tsx +export default function Page() { + return ( +

Hello

{/* [!code --] */} +

Hello World

{/* [!code ++] */} + ); +} +``` +```` + +The transformer automatically detects `tsx` and `jsx` languages and handles the `{ /* */ }` comment syntax accordingly. diff --git a/apps/website/src/globals.ts b/apps/website/src/globals.ts index 5495b06..8e23dda 100644 --- a/apps/website/src/globals.ts +++ b/apps/website/src/globals.ts @@ -4,4 +4,5 @@ export const globals = { githubUrl: "https://github.com/pheralb/code-blocks", twitterUrl: "https://twitter.com/pheralb_", websiteUrl: "https://code-blocks.pheralb.dev", + apiWebsiteUrl: "https://code-blocks.pheralb.dev/api", }; diff --git a/apps/website/src/mdx/plugins/rehypeComponent.ts b/apps/website/src/mdx/plugins/rehypeComponent.ts index c2a2cdb..6820216 100644 --- a/apps/website/src/mdx/plugins/rehypeComponent.ts +++ b/apps/website/src/mdx/plugins/rehypeComponent.ts @@ -104,6 +104,12 @@ export function rehypeComponent() { ); } + const customTitle = getNodeAttributeByName(node, "title")?.value as + | string + | undefined; + const codeTitle = + customTitle ?? component.shadcnRegistry?.target ?? undefined; + node.children?.push( u("element", { tagName: "pre", @@ -115,6 +121,9 @@ export function rehypeComponent() { tagName: "code", properties: { className: [`language-${component.fileType}`], + ...(codeTitle && { + metastring: `title="${codeTitle}"`, + }), }, children: [ { diff --git a/apps/website/src/styles/globals.css b/apps/website/src/styles/globals.css index 6ecf5f7..47dd074 100644 --- a/apps/website/src/styles/globals.css +++ b/apps/website/src/styles/globals.css @@ -37,7 +37,7 @@ } .prose :where(h2):not(:where([class~="not-prose"], [class~="not-prose"] *)) { - @apply mb-5 scroll-mt-20 text-2xl font-semibold; + @apply mb-4 scroll-mt-20 text-2xl font-semibold; } .prose :where(h3):not(:where([class~="not-prose"], [class~="not-prose"] *)) { diff --git a/apps/website/src/styles/sugar-high.css b/apps/website/src/styles/sugar-high.css index ac53ae6..d1c8323 100644 --- a/apps/website/src/styles/sugar-high.css +++ b/apps/website/src/styles/sugar-high.css @@ -32,6 +32,15 @@ code { @apply font-mono; } +/* Spacing to match Shiki layout */ +pre.sh-pre { + @apply py-3; +} + +pre.sh-pre code { + @apply block whitespace-pre px-4 leading-[1.6]; +} + /* Line Numbers */ pre.sh-line-numbers code { counter-reset: sh-line-number;