asanshay
componentmarkdownsource: ok · refreshed
Markdown
A markdown component with a clean look and no-frills parsing.
npx shadcn add "https://ds.asanshay.com/r/markdown.json"free
Visual style
minimal
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lightui-primitives@radix-ui/react-slot
iconslucide-react
Files3 · source cached 10/8/2026, 8:08:50 AM
registry/new-york/blocks/markdown/markdown.tsx
import ReactMarkdown, { Components } from "react-markdown";
import remarkGfm from "remark-gfm";
import { cn } from "@/lib/utils";
import { CodeBlock } from "@/registry/new-york/blocks/code-block/code-block";
import { Button } from "@/registry/new-york/blocks/button/button";
const components: Components = {
// Text components
p: ({ children }) => <p className="text-base mb-3 last:mb-0">{children}</p>,
h1: ({ children }) => (
<h1 className="text-4xl font-semibold tracking-tight mb-4 mt-2">{children}</h1>
),
h2: ({ children }) => (
<h2 className="text-3xl font-semibold tracking-tight mt-4 mb-2 border-b border-border pb-2">
{children}
</h2>
),
h3: ({ children }) => (
<h3 className="text-2xl font-semibold tracking-tight mt-4 mb-2">{children}</h3>
),
h4: ({ children }) => (
<h4 className="text-xl font-semibold tracking-tight mt-3 mb-2">{children}</h4>
),
h5: ({ children }) => (
<h5 className="text-lg font-semibold tracking-tight mt-2 mb-1">{children}</h5>
),
h6: ({ children }) => (
<h6 className="text-base font-semibold tracking-tight mt-2 mb-1">{children}</h6>
),
// Lists
ul: ({ children }) => (
<ul className="text-base my-2 pl-6 list-disc marker:text-primary/70">{children}</ul>
),
ol: ({ children }) => <ol className="text-base my-2 pl-6 list-decimal">{children}</ol>,
li: ({ children }) => <li className="my-0.5">{children}</li>,
// Inline formatting
strong: ({ children }) => <strong className="font-bold">{children}</strong>,
em: ({ children }) => <em className="italic">{children}</em>,
del: ({ children }) => <del className="line-through">{children}</del>,
// Block elements
blockquote: ({ children }) => (
<blockquote className="pl-4 border-l-4 border-primary/20 my-2 italic text-primary/70">
{children}
</blockquote>
),
hr: () => <hr className="my-4 border-t border-border" />,
// Tables
table: ({ children }) => (
<div className="overflow-x-auto my-4">
<table className="min-w-full divide-y divide-border border border-border">{children}</table>
</div>
),
thead: ({ children }) => <thead className="bg-muted/50">{children}</thead>,
tbody: ({ children }) => <tbody className="divide-y divide-border">{children}</tbody>,
tr: ({ children }) => <tr>{children}</tr>,
th: ({ children }) => (
<th className="px-3 py-2 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">
{children}
</th>
),
td: ({ children }) => <td className="px-3 py-2 whitespace-nowrap text-sm">{children}</td>,
// Links and images
a: ({ children, href, title }) => (
<Button
variant="link"
size="sm"
className="text-primary hover:underline p-0 m-0"
title={title}
onClick={() => {
if (href) {
window.open(href, "_blank", "noopener noreferrer");
}
}}
disabled={!href}
asChild
>
{children}
</Button>
),
img: ({ src, alt, title }) => (
<div className="flex flex-col items-center w-fit">
<img
src={src || ""}
alt={alt || ""}
title={title || alt || ""}
className="max-w-sm h-auto my-2 rounded-xl"
/>
<p className="text-sm text-center text-muted-foreground">{alt}</p>
</div>
),
// Code blocks and inline code
code: ({ className, children }) => {
const match = /language-(\w+)/.exec(className || "");
const isInline = !match;
return !isInline && match ? (
<CodeBlock code={String(children).replace(/\n$/, "")} language={match[1]} />
) : (
<code className="bg-muted px-1.5 py-0.5 rounded-md text-sm text-primary font-mono">
{children}
</code>
);
},
pre: ({ children }) => <>{children}</>,
};
export default function Markdown({
children,
className,
}: {
children: string;
className?: string;
}) {
return (
<div className={cn("w-full", className)}>
<ReactMarkdown remarkPlugins={[remarkGfm]} components={components}>
{children}
</ReactMarkdown>
</div>
);
}
registry/new-york/blocks/button/button.tsx
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center gap-1 whitespace-nowrap rounded-xl text-md font-medium disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive duration-200 hover:duration-75 transition-all",
{
variants: {
variant: {
default:
"border border-primary bg-primary/70 bg-linear-to-t from-primary to-primary/30 px-4 py-2 font-medium text-primary-foreground shadow-md inset-shadow-[0px_1px_0px_1px] inset-shadow-white/30 transition-all duration-75 hover:bg-primary/60 active:translate-y-px active:inset-shadow-black/10 dark:bg-primary dark:from-primary dark:to-primary/80 dark:hover:bg-primary/90",
fancy:
"border border-primary bg-primary/30 bg-radial-[at_50%_25%] from-primary/40 to-primary text-primary-foreground shadow-md inset-shadow-sm inset-shadow-white/50 hover:bg-white hover:shadow-lg hover:inset-shadow-white active:bg-primary/30 active:shadow-none active:inset-shadow-primary/80 hover:-translate-y-px active:translate-y-px",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border shadow-lg hover:shadow-none shadow-shade bg-transparent text-primary hover:border-transparent hover:bg-primary/10 active:bg-primary/30",
secondary:
"border border-transparent bg-primary/10 text-primary hover:border-primary/50 hover:bg-transparent active:border-transparent active:border-primary active:ring-1 active:scale-95",
ghost:
"bg-transparent text-primary hover:border-transparent hover:bg-primary/10 active:bg-primary/30",
link: "text-foreground underline decoration-primary underline-offset-2 hover:underline-offset-2 active:decoration-4 hover:opacity-70 hover:decoration-2 active:text-foreground",
},
size: {
default: "px-4 py-2",
xs: "px-2 py-1.5 rounded-lg [&_svg:not([class*='size-'])]:size-3 text-xs",
sm: "px-3 py-1.5 text-sm rounded-lg",
lg: "px-5 py-2 text-lg",
xl: "px-6 py-3 [&_svg:not([class*='size-'])]:size-5 text-xl",
icon: "size-8 rounded-lg [&_svg:not([class*='size-'])]:size-4",
iconSm: "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
iconLg: "size-10 rounded-lg [&_svg:not([class*='size-'])]:size-5",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}) {
const Comp = asChild ? Slot : "button";
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
export { Button, buttonVariants };
registry/new-york/blocks/code-block/code-block.tsx
"use client";
import { cn } from "@/lib/utils";
import React, { useEffect, useState } from "react";
import { Button } from "@/registry/new-york/blocks/button/button";
import { Check, Copy } from "lucide-react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneLight, a11yDark as oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
import { cva } from "class-variance-authority";
import { useTheme } from "next-themes";
const customSyntaxTheme = {
...oneLight,
'pre[class*="language-"]': {
...oneLight['pre[class*="language-"]'],
background: "hsl(var(--muted))",
borderRadius: "0.5rem",
fontFamily: "Geist Mono, monospace",
textShadow: "none",
padding: 0,
margin: 0,
},
'code[class*="language-"]': {
...oneLight['code[class*="language-"]'],
background: "none",
fontFamily: "Geist Mono, monospace",
textShadow: "none",
fontSize: "13px",
padding: 0,
},
};
const customDarkSyntaxTheme = {
...oneDark,
'pre[class*="language-"]': {
...oneDark['pre[class*="language-"]'],
background: "hsl(var(--muted))",
borderRadius: "0.5rem",
fontFamily: "Geist Mono, monospace",
textShadow: "none",
padding: 0,
margin: 0,
},
'code[class*="language-"]': {
...oneDark['code[class*="language-"]'],
background: "none",
fontFamily: "Geist Mono, monospace",
textShadow: "none",
fontSize: "13px",
padding: 0,
},
};
const codeBlockVariants = cva("text-card-foreground rounded-xl p-4 flex flex-col gap-2", {
variants: {
variant: {
flat: "bg-muted",
default: "border border-border bg-card",
},
},
defaultVariants: {
variant: "default",
},
});
export type CodeBlockProps = {
code: string;
language?: string;
theme?: string;
className?: string;
title?: string;
children?: React.ReactNode;
variant?: "default" | "flat";
} & React.HTMLProps<HTMLDivElement>;
export function CodeBlock({
code,
language = "tsx",
theme = "github-light",
className,
title,
children,
variant = "default",
...props
}: CodeBlockProps) {
const { theme: themeName } = useTheme();
const codeClassNames = cn("w-full overflow-x-auto text-[13px] font-mono");
const [copied, setCopied] = useState(false);
return (
<div className={cn("relative group", codeBlockVariants({ variant }), className)} {...props}>
<Button
onClick={() => {
navigator.clipboard.writeText(String(code));
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 1000);
}}
variant="ghost"
size="icon"
className="absolute right-2 top-2 focus-visible:ring-0 text-muted-foreground hover:bg-muted active:bg-muted"
aria-label={copied ? "Copied" : "Copy to clipboard"}
>
<span className="sr-only">{copied ? "Copied" : "Copy"}</span>
<Copy
className={`size-4 transition-all duration-300 ${copied ? "scale-0" : "scale-100"}`}
/>
<Check
className={`absolute inset-0 m-auto size-4 transition-all duration-300 text-success ${
copied ? "scale-100" : "scale-0"
}`}
/>
</Button>
{title && <h3 className="text-sm text-muted-foreground tracking-tight font-mono">{title}</h3>}
<SyntaxHighlighter
language={language}
style={themeName === "dark" ? customDarkSyntaxTheme : customSyntaxTheme}
PreTag="div"
className={codeClassNames}
>
{code}
</SyntaxHighlighter>
</div>
);
}