Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: view code #67

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ export default function RootLayout({
enableSystem
disableTransitionOnChange
>
<div className="relative flex min-h-screen flex-col overflow-hidden supports-[overflow:clip]:overflow-clip">
<Header />
{children}
<div className="vaul-drawer-wrapper bg-background">
<div className="relative flex min-h-screen flex-col overflow-hidden supports-[overflow:clip]:overflow-clip">
<Header />
{children}
</div>
</div>
</ThemeProvider>
</body>
Expand Down
146 changes: 146 additions & 0 deletions components/ui/credenza.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
"use client";

import * as React from "react";

import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import { useMediaQuery } from "@/hooks/use-media-query";
import { cn } from "@/lib/utils";

interface BaseProps {
children: React.ReactNode;
}

interface RootCredenzaProps extends BaseProps {
open?: boolean;
onOpenChange?: (open: boolean) => void;
}

interface CredenzaProps extends BaseProps {
className?: string;
asChild?: true;
}

const desktop = "(min-width: 768px)";

const Credenza = ({ children, ...props }: RootCredenzaProps) => {
const isDesktop = useMediaQuery(desktop);
const Credenza = isDesktop ? Dialog : Drawer;

return <Credenza {...props}>{children}</Credenza>;
};

const CredenzaTrigger = ({ className, children, ...props }: CredenzaProps) => {
const isDesktop = useMediaQuery(desktop);
const CredenzaTrigger = isDesktop ? DialogTrigger : DrawerTrigger;

return (
<CredenzaTrigger className={className} {...props}>
{children}
</CredenzaTrigger>
);
};

const CredenzaClose = ({ className, children, ...props }: CredenzaProps) => {
const isDesktop = useMediaQuery(desktop);
const CredenzaClose = isDesktop ? DialogClose : DrawerClose;

return (
<CredenzaClose className={className} {...props}>
{children}
</CredenzaClose>
);
};

const CredenzaContent = ({ className, children, ...props }: CredenzaProps) => {
const isDesktop = useMediaQuery(desktop);
const CredenzaContent = isDesktop ? DialogContent : DrawerContent;

return (
<CredenzaContent className={className} {...props}>
{children}
</CredenzaContent>
);
};

const CredenzaDescription = ({ className, children, ...props }: CredenzaProps) => {
const isDesktop = useMediaQuery(desktop);
const CredenzaDescription = isDesktop ? DialogDescription : DrawerDescription;

return (
<CredenzaDescription className={className} {...props}>
{children}
</CredenzaDescription>
);
};

const CredenzaHeader = ({ className, children, ...props }: CredenzaProps) => {
const isDesktop = useMediaQuery(desktop);
const CredenzaHeader = isDesktop ? DialogHeader : DrawerHeader;

return (
<CredenzaHeader className={className} {...props}>
{children}
</CredenzaHeader>
);
};

const CredenzaTitle = ({ className, children, ...props }: CredenzaProps) => {
const isDesktop = useMediaQuery(desktop);
const CredenzaTitle = isDesktop ? DialogTitle : DrawerTitle;

return (
<CredenzaTitle className={className} {...props}>
{children}
</CredenzaTitle>
);
};

const CredenzaBody = ({ className, children, ...props }: CredenzaProps) => {
return (
<div className={cn("px-4 md:px-0", className)} {...props}>
{children}
</div>
);
};

const CredenzaFooter = ({ className, children, ...props }: CredenzaProps) => {
const isDesktop = useMediaQuery(desktop);
const CredenzaFooter = isDesktop ? DialogFooter : DrawerFooter;

return (
<CredenzaFooter className={className} {...props}>
{children}
</CredenzaFooter>
);
};

export {
Credenza,
CredenzaBody,
CredenzaClose,
CredenzaContent,
CredenzaDescription,
CredenzaFooter,
CredenzaHeader,
CredenzaTitle,
CredenzaTrigger,
};
100 changes: 100 additions & 0 deletions components/ui/drawer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"use client";

import * as React from "react";
import { Drawer as DrawerPrimitive } from "vaul";

import { cn } from "@/lib/utils";

const Drawer = ({
shouldScaleBackground = true,
...props
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
<DrawerPrimitive.Root shouldScaleBackground={shouldScaleBackground} {...props} />
);
Drawer.displayName = "Drawer";

const DrawerTrigger = DrawerPrimitive.Trigger;

const DrawerPortal = DrawerPrimitive.Portal;

const DrawerClose = DrawerPrimitive.Close;

const DrawerOverlay = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Overlay
ref={ref}
className={cn("fixed inset-0 z-50 bg-black/80", className)}
{...props}
/>
));
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;

const DrawerContent = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DrawerPortal>
<DrawerOverlay />
<DrawerPrimitive.Content
ref={ref}
className={cn(
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
className,
)}
{...props}
>
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
{children}
</DrawerPrimitive.Content>
</DrawerPortal>
));
DrawerContent.displayName = "DrawerContent";

const DrawerHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)} {...props} />
);
DrawerHeader.displayName = "DrawerHeader";

const DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />
);
DrawerFooter.displayName = "DrawerFooter";

const DrawerTitle = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
{...props}
/>
));
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;

const DrawerDescription = React.forwardRef<
React.ElementRef<typeof DrawerPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
>(({ className, ...props }, ref) => (
<DrawerPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
DrawerDescription.displayName = DrawerPrimitive.Description.displayName;

export {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
DrawerPortal,
DrawerTitle,
DrawerTrigger,
};
38 changes: 38 additions & 0 deletions demo/code-block.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { toJsxRuntime } from "hast-util-to-jsx-runtime";
import React, { Fragment } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
import { codeToHast } from "shiki";

export async function CodeBlock({ code }: { code: string }) {
const out = await getCachedHighlightedCode(code);

// @ts-expect-error - the type is wrong
return toJsxRuntime(out, {
Fragment,
jsx,
jsxs,
components: {
pre: ({ style, className, ...props }) => (
<pre
data-custom-codeblock
{...props}
className="max-h-[calc(100vh-260px)] overflow-x-auto rounded-lg border bg-zinc-950 py-4 ps-2 dark:bg-zinc-900 md:max-h-[calc(100vh-350px)]"
/>
),
code: ({ style, className, ...props }) => (
<code
data-custom-code
{...props}
className="relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm"
/>
),
},
});
}

const getCachedHighlightedCode = React.cache(async (code: string) => {
return await codeToHast(code, {
lang: "ts",
theme: "github-dark-default",
});
});
23 changes: 23 additions & 0 deletions demo/composable-copy-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use client";

/**
* Eventually, if merged, we can compose this with the regular copy button
* for now, it's just a simple copy button
*
* Had to add it in another file for client functionality
*/

import { Button, ButtonProps } from "@/components/ui/button";

export function ComposableCopyButton({ text, onClick, ...props }: { text: string } & ButtonProps) {
const handleCopy = async (e: React.MouseEvent<HTMLButtonElement>) => {
try {
await navigator.clipboard.writeText(text);
onClick?.(e);
} catch (err) {
console.error("Failed to copy text: ", err);
}
};

return <Button onClick={handleCopy} {...props} />;
}
3 changes: 3 additions & 0 deletions demo/demo-component.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { cn } from "@/lib/utils";
import CopyButton from "./copy-button";
import { readComponentSource } from "./read-component-source";
import ViewCodeButton from "./view-code-button";

export default async function DemoComponent({
directory,
Expand All @@ -17,7 +18,9 @@ export default async function DemoComponent({
return (
<div className={cn("group/item relative", className)}>
<Component />

<CopyButton componentSource={source || ""} />
<ViewCodeButton componentSource={source || ""} />
</div>
);
}
Loading