mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-07 12:27:44 +00:00
chore: update to Fumadocs 15.7 (#4154)
Co-authored-by: KinfeMichael Tariku <65047246+Kinfe123@users.noreply.github.com> Co-authored-by: Kinfe123 <kinfishtech@gmail.com>
This commit is contained in:
354
docs/components/ui/code-block.tsx
Normal file
354
docs/components/ui/code-block.tsx
Normal file
@@ -0,0 +1,354 @@
|
||||
"use client";
|
||||
import { Check, Copy } from "lucide-react";
|
||||
import {
|
||||
ButtonHTMLAttributes,
|
||||
type ComponentProps,
|
||||
createContext,
|
||||
forwardRef,
|
||||
type HTMLAttributes,
|
||||
ReactElement,
|
||||
type ReactNode,
|
||||
type RefObject,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useCopyButton } from "./use-copy-button";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { mergeRefs } from "@/lib/utils";
|
||||
import { ScrollArea, ScrollBar, ScrollViewport } from "./scroll-area";
|
||||
|
||||
export interface CodeBlockProps extends ComponentProps<"figure"> {
|
||||
/**
|
||||
* Icon of code block
|
||||
*
|
||||
* When passed as a string, it assumes the value is the HTML of icon
|
||||
*/
|
||||
icon?: ReactNode;
|
||||
|
||||
/**
|
||||
* Allow to copy code with copy button
|
||||
*
|
||||
* @defaultValue true
|
||||
*/
|
||||
allowCopy?: boolean;
|
||||
|
||||
/**
|
||||
* Keep original background color generated by Shiki or Rehype Code
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
keepBackground?: boolean;
|
||||
|
||||
viewportProps?: HTMLAttributes<HTMLElement>;
|
||||
|
||||
/**
|
||||
* show line numbers
|
||||
*/
|
||||
"data-line-numbers"?: boolean;
|
||||
|
||||
/**
|
||||
* @defaultValue 1
|
||||
*/
|
||||
"data-line-numbers-start"?: number;
|
||||
|
||||
Actions?: (props: { className?: string; children?: ReactNode }) => ReactNode;
|
||||
}
|
||||
|
||||
const TabsContext = createContext<{
|
||||
containerRef: RefObject<HTMLDivElement | null>;
|
||||
nested: boolean;
|
||||
} | null>(null);
|
||||
|
||||
export function Pre(props: ComponentProps<"pre">) {
|
||||
return (
|
||||
<pre
|
||||
{...props}
|
||||
className={cn("min-w-full w-max *:flex *:flex-col", props.className)}
|
||||
>
|
||||
{props.children}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
||||
export function CodeBlock({
|
||||
ref,
|
||||
title,
|
||||
allowCopy,
|
||||
keepBackground = false,
|
||||
icon,
|
||||
viewportProps = {},
|
||||
children,
|
||||
Actions = (props) => (
|
||||
<div {...props} className={cn("empty:hidden", props.className)} />
|
||||
),
|
||||
...props
|
||||
}: CodeBlockProps) {
|
||||
const isTab = useContext(TabsContext) !== null;
|
||||
const areaRef = useRef<HTMLDivElement>(null);
|
||||
allowCopy ??= !isTab;
|
||||
const bg = cn(
|
||||
"bg-fd-secondary",
|
||||
keepBackground && "bg-(--shiki-light-bg) dark:bg-(--shiki-dark-bg)",
|
||||
);
|
||||
const onCopy = useCallback(() => {
|
||||
const pre = areaRef.current?.getElementsByTagName("pre").item(0);
|
||||
if (!pre) return;
|
||||
const clone = pre.cloneNode(true) as HTMLElement;
|
||||
clone.querySelectorAll(".nd-copy-ignore").forEach((node) => {
|
||||
node.remove();
|
||||
});
|
||||
void navigator.clipboard.writeText(clone.textContent ?? "");
|
||||
}, []);
|
||||
return (
|
||||
<figure
|
||||
ref={ref}
|
||||
dir="ltr"
|
||||
{...props}
|
||||
className={cn(
|
||||
isTab ? [bg, "rounded-lg"] : "my-4 rounded-lg bg-fd-card",
|
||||
"group shiki relative border shadow-sm outline-none not-prose overflow-hidden text-sm",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{title ? (
|
||||
<div
|
||||
className={cn(
|
||||
"group flex text-fd-muted-foreground items-center gap-2 ps-3 h-9.5 pr-1 bg-fd-muted",
|
||||
isTab && "border-b",
|
||||
)}
|
||||
>
|
||||
{typeof icon === "string" ? (
|
||||
<div
|
||||
className="[&_svg]:size-3.5"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: icon,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
icon
|
||||
)}
|
||||
<figcaption className="flex-1 truncate">{title}</figcaption>
|
||||
{Actions({
|
||||
children: allowCopy && <CopyButton onCopy={onCopy} />,
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
Actions({
|
||||
className: "absolute top-1 right-1 z-2 text-fd-muted-foreground",
|
||||
children: allowCopy && <CopyButton onCopy={onCopy} />,
|
||||
})
|
||||
)}
|
||||
<div
|
||||
ref={areaRef}
|
||||
{...viewportProps}
|
||||
className={cn(
|
||||
!isTab && [bg, "rounded-none border border-x-0 border-b-0"],
|
||||
"text-[13px] overflow-auto max-h-[600px] bg-fd-muted/50 fd-scroll-container",
|
||||
viewportProps.className,
|
||||
)}
|
||||
style={
|
||||
{
|
||||
// space for toolbar
|
||||
"--padding-right": !title ? "calc(var(--spacing) * 8)" : undefined,
|
||||
counterSet: props["data-line-numbers"]
|
||||
? `line ${Number(props["data-line-numbers-start"] ?? 1) - 1}`
|
||||
: undefined,
|
||||
...viewportProps.style,
|
||||
} as object
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
function CopyButton({
|
||||
className,
|
||||
onCopy,
|
||||
...props
|
||||
}: ButtonHTMLAttributes<HTMLButtonElement> & {
|
||||
onCopy: () => void;
|
||||
}): ReactElement {
|
||||
const [checked, onClick] = useCopyButton(onCopy);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
variant: "ghost",
|
||||
size: "icon",
|
||||
}),
|
||||
"transition-opacity border-none group-hover:opacity-100",
|
||||
"opacity-0 group-hover:opacity-100",
|
||||
"group-hover:opacity-100",
|
||||
className,
|
||||
)}
|
||||
aria-label="Copy Text"
|
||||
onClick={onClick}
|
||||
{...props}
|
||||
>
|
||||
<Check
|
||||
className={cn("size-3.5 transition-transform", !checked && "scale-0")}
|
||||
/>
|
||||
<Copy
|
||||
className={cn(
|
||||
"absolute size-3.5 transition-transform",
|
||||
checked && "scale-0",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
export function CodeBlockTabs({ ref, ...props }: ComponentProps<typeof Tabs>) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const nested = useContext(TabsContext) !== null;
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
ref={mergeRefs(containerRef, ref)}
|
||||
{...props}
|
||||
className={cn(
|
||||
"bg-fd-card p-1 rounded-xl border overflow-hidden",
|
||||
!nested && "my-4",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
<TabsContext.Provider
|
||||
value={useMemo(
|
||||
() => ({
|
||||
containerRef,
|
||||
nested,
|
||||
}),
|
||||
[nested],
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</TabsContext.Provider>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
export function CodeBlockTabsList(props: ComponentProps<typeof TabsList>) {
|
||||
const { containerRef, nested } = useContext(TabsContext)!;
|
||||
|
||||
return (
|
||||
<TabsList
|
||||
{...props}
|
||||
className={cn(
|
||||
"flex flex-row overflow-x-auto px-1 -mx-1 text-fd-muted-foreground",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</TabsList>
|
||||
);
|
||||
}
|
||||
|
||||
export function CodeBlockTabsTrigger({
|
||||
children,
|
||||
...props
|
||||
}: ComponentProps<typeof TabsTrigger>) {
|
||||
return (
|
||||
<TabsTrigger
|
||||
{...props}
|
||||
className={cn(
|
||||
"relative group inline-flex text-sm font-medium text-nowrap items-center transition-colors gap-2 px-2 first:ms-1 py-1.5 hover:text-fd-accent-foreground data-[state=active]:text-fd-primary [&_svg]:size-3.5",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
<div className="absolute inset-x-2 bottom-0 h-px group-data-[state=active]:bg-fd-primary" />
|
||||
{children}
|
||||
</TabsTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: currently Vite RSC plugin has problem with adding `asChild` here, maybe revisit this in future
|
||||
export const CodeBlockTab = TabsContent;
|
||||
|
||||
export const CodeBlockOld = forwardRef<HTMLElement, CodeBlockProps>(
|
||||
(
|
||||
{
|
||||
title,
|
||||
allowCopy = true,
|
||||
keepBackground = false,
|
||||
icon,
|
||||
viewportProps,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const areaRef = useRef<HTMLDivElement>(null);
|
||||
const onCopy = useCallback(() => {
|
||||
const pre = areaRef.current?.getElementsByTagName("pre").item(0);
|
||||
|
||||
if (!pre) return;
|
||||
|
||||
const clone = pre.cloneNode(true) as HTMLElement;
|
||||
clone.querySelectorAll(".nd-copy-ignore").forEach((node) => {
|
||||
node.remove();
|
||||
});
|
||||
|
||||
void navigator.clipboard.writeText(clone.textContent ?? "");
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<figure
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={cn(
|
||||
"not-prose group fd-codeblock relative my-6 overflow-hidden rounded-lg border bg-fd-secondary/50 text-sm",
|
||||
keepBackground &&
|
||||
"bg-[var(--shiki-light-bg)] dark:bg-[var(--shiki-dark-bg)]",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{title ? (
|
||||
<div className="flex flex-row items-center gap-2 border-b bg-fd-muted px-4 py-1.5">
|
||||
{icon ? (
|
||||
<div
|
||||
className="text-fd-muted-foreground [&_svg]:size-3.5"
|
||||
dangerouslySetInnerHTML={
|
||||
typeof icon === "string"
|
||||
? {
|
||||
__html: icon,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{typeof icon !== "string" ? icon : null}
|
||||
</div>
|
||||
) : null}
|
||||
<figcaption className="flex-1 truncate text-fd-muted-foreground">
|
||||
{title}
|
||||
</figcaption>
|
||||
{allowCopy ? (
|
||||
<CopyButton className="-me-2" onCopy={onCopy} />
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
allowCopy && (
|
||||
<CopyButton
|
||||
className="absolute right-2 top-2 z-[2] backdrop-blur-md"
|
||||
onCopy={onCopy}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<ScrollArea ref={areaRef} dir="ltr">
|
||||
<ScrollViewport
|
||||
{...viewportProps}
|
||||
className={cn("max-h-[600px]", viewportProps?.className)}
|
||||
>
|
||||
{props.children}
|
||||
</ScrollViewport>
|
||||
<ScrollBar orientation="horizontal" />
|
||||
</ScrollArea>
|
||||
</figure>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
CodeBlockOld.displayName = "CodeBlockOld";
|
||||
Reference in New Issue
Block a user