1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-06-27 14:45:41 -04:00

do react types more better too 🧠

This commit is contained in:
2025-05-19 17:58:03 -04:00
parent 51708c9b17
commit f4c69292df
41 changed files with 141 additions and 194 deletions

View File

@ -1,13 +1,11 @@
import { env } from "@/lib/env";
import { EyeIcon } from "lucide-react";
import Link from "@/components/link";
import { getFrontMatter, POSTS_DIR } from "@/lib/posts";
import { getFrontMatter, POSTS_DIR, type FrontMatter } from "@/lib/posts";
import { createMetadata } from "@/lib/metadata";
import { formatDate, formatDateISO } from "@/lib/date";
import authorConfig from "@/lib/config/author";
import { getViews } from "@/lib/views";
import type { ReactElement } from "react";
import type { FrontMatter } from "@/lib/posts";
export const revalidate = 300; // 5 minutes
@ -33,7 +31,7 @@ const Page = async () => {
});
});
const sections: ReactElement[] = [];
const sections: React.ReactNode[] = [];
Object.entries(postsByYear).forEach(([year, posts]) => {
sections.push(

View File

@ -16,7 +16,7 @@ export const Terminal = () => (
backgroundImage: `url(${backgroundImg.src})`,
}}
>
<code className="border-ring block rounded-lg border border-solid bg-black/60 p-4 text-sm break-all text-white/90 backdrop-blur-xs backdrop-saturate-150">
<code className="border-ring block rounded-lg border border-solid bg-black/60 p-4 text-sm break-all text-white/90 backdrop-blur-sm backdrop-saturate-150">
<span style={{ color: "#f95757" }}>sundar</span>@<span style={{ color: "#3b9dd2" }}>google</span>:
<span style={{ color: "#78df55" }}>~</span>$ <span style={{ color: "#d588fb" }}>mv</span> /root
<a href="https://killedbygoogle.com/" style={{ color: "inherit" }} className="hover:no-underline">

View File

@ -1,9 +1,7 @@
"use client";
import { ActivityCalendar } from "react-activity-calendar";
import { ActivityCalendar, type Activity } from "react-activity-calendar";
import { formatDate } from "@/lib/date";
import type { ComponentPropsWithoutRef } from "react";
import type { Activity } from "react-activity-calendar";
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
@ -12,7 +10,7 @@ const Calendar = ({
noun = "thing",
className,
...rest
}: ComponentPropsWithoutRef<"div"> & {
}: React.ComponentProps<"div"> & {
data: Activity[];
noun?: string;
}) => {

View File

@ -3,7 +3,6 @@ import reactToText from "react-to-text";
import { CodeIcon, TerminalIcon } from "lucide-react";
import CopyButton from "@/components/copy-button";
import { cn } from "@/lib/utils";
import type { ComponentProps, ComponentPropsWithoutRef } from "react";
const CodeBlock = async ({
showLineNumbers = false,
@ -11,7 +10,7 @@ const CodeBlock = async ({
className,
children,
...rest
}: ComponentPropsWithoutRef<"pre"> & {
}: React.ComponentProps<"pre"> & {
showLineNumbers?: boolean;
showCopyButton?: boolean;
}) => {
@ -24,7 +23,7 @@ const CodeBlock = async ({
);
}
const codeProps = children.props as ComponentProps<"code">;
const codeProps = children.props as React.ComponentProps<"code">;
const codeString = reactToText(codeProps.children).trim();
// the language set in the markdown is passed as a className
@ -51,7 +50,7 @@ const CodeBlock = async ({
dangerouslySetInnerHTML={{ __html: codeHighlighted }}
/>
{lang && (
<span className="[&_svg]:stroke-primary/90 text-foreground/75 bg-muted/40 absolute top-0 left-0 z-10 flex items-center gap-[8px] rounded-tl-md rounded-br-lg border-r-2 border-b-2 px-[10px] py-[5px] font-mono text-xs font-medium tracking-wide uppercase backdrop-blur-xs select-none [&_svg]:size-[14px] [&_svg]:shrink-0">
<span className="[&_svg]:stroke-primary/90 text-foreground/75 bg-muted/40 absolute top-0 left-0 z-10 flex items-center gap-[8px] rounded-tl-md rounded-br-lg border-r-2 border-b-2 px-[10px] py-[5px] font-mono text-xs font-medium tracking-wide uppercase backdrop-blur-sm select-none [&_svg]:size-[14px] [&_svg]:shrink-0">
{["sh", "bash", "zsh"].includes(lang) ? (
<>
<TerminalIcon />
@ -68,7 +67,7 @@ const CodeBlock = async ({
{showCopyButton && (
<CopyButton
source={codeString}
className="text-foreground/75 hover:text-primary bg-muted/40 absolute top-0 right-0 z-10 size-10 rounded-tr-md rounded-bl-lg border-b-2 border-l-2 p-0 backdrop-blur-xs select-none [&_svg]:my-auto [&_svg]:inline-block [&_svg]:size-4.5 [&_svg]:align-text-bottom"
className="text-foreground/75 hover:text-primary bg-muted/40 absolute top-0 right-0 z-10 size-10 rounded-tr-md rounded-bl-lg border-b-2 border-l-2 p-0 backdrop-blur-sm select-none [&_svg]:my-auto [&_svg]:inline-block [&_svg]:size-4.5 [&_svg]:align-text-bottom"
/>
)}
</div>

View File

@ -12,7 +12,6 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
import { MarkdownIcon } from "@/components/icons";
import { useSession } from "@/lib/auth-client";
import { createComment, updateComment } from "@/lib/server/comments";
import type { FormEvent } from "react";
const CommentForm = ({
slug,
@ -36,7 +35,7 @@ const CommentForm = ({
const { data: session } = useSession();
const handleSubmit = (e: FormEvent) => {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!content.trim()) {

View File

@ -4,7 +4,6 @@ import { forwardRef, useState, useEffect } from "react";
import copy from "copy-to-clipboard";
import { ClipboardIcon, CheckIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import type { Ref, ComponentPropsWithoutRef, ComponentRef, MouseEventHandler } from "react";
const CopyButton = (
{
@ -12,15 +11,15 @@ const CopyButton = (
timeout = 2000,
className,
...rest
}: ComponentPropsWithoutRef<"button"> & {
}: React.ComponentProps<"button"> & {
source: string;
timeout?: number;
},
ref: Ref<ComponentRef<"button">>
ref: React.Ref<React.ComponentRef<"button">>
) => {
const [copied, setCopied] = useState(false);
const handleCopy: MouseEventHandler<ComponentRef<"button">> = (e) => {
const handleCopy: React.MouseEventHandler<React.ComponentRef<"button">> = (e) => {
// prevent unintentional double-clicks by unfocusing button
e.currentTarget.blur();

View File

@ -1,12 +1,12 @@
/* eslint-disable jsx-a11y/alt-text, @next/next/no-img-element */
"use client";
import { ReactElement, Children, useState, useRef, useEffect } from "react";
import { useState, useRef, useEffect, Children } from "react";
import { getImageProps } from "next/image";
import { cn } from "@/lib/utils";
import { ChevronsLeftRightIcon } from "lucide-react";
const ImageDiff = ({ children, className }: { children: ReactElement[]; className?: string }) => {
const ImageDiff = ({ children, className }: { children: React.ReactElement[]; className?: string }) => {
const containerRef = useRef<HTMLDivElement>(null);
const [sliderPosition, setSliderPosition] = useState(50);
const [isDragging, setIsDragging] = useState(false);

View File

@ -4,9 +4,8 @@ import Link from "@/components/link";
import { NextjsIcon } from "@/components/icons";
import { cn } from "@/lib/utils";
import siteConfig from "@/lib/config/site";
import type { ComponentPropsWithoutRef } from "react";
const Footer = ({ className, ...rest }: ComponentPropsWithoutRef<"footer">) => {
const Footer = ({ className, ...rest }: React.ComponentProps<"footer">) => {
return (
<footer
className={cn("text-foreground/85 text-[0.8rem] leading-loose md:flex md:flex-row md:justify-between", className)}

View File

@ -3,11 +3,10 @@ import Link from "@/components/link";
import Menu from "@/components/layout/menu";
import { cn } from "@/lib/utils";
import siteConfig from "@/lib/config/site";
import type { ComponentPropsWithoutRef } from "react";
import avatarImg from "@/app/avatar.jpg";
const Header = ({ className, ...rest }: ComponentPropsWithoutRef<"header">) => {
const Header = ({ className, ...rest }: React.ComponentProps<"header">) => {
return (
<header className={cn("flex items-center justify-between", className)} {...rest}>
<Link

View File

@ -1,7 +1,6 @@
import { isValidElement } from "react";
import Link from "@/components/link";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
import type { MenuItemConfig } from "@/lib/config/menu";
const MenuItem = ({
text,
@ -10,17 +9,17 @@ const MenuItem = ({
current,
className,
...rest
}: Omit<ComponentPropsWithoutRef<typeof Link>, "href"> &
MenuItemConfig & {
current?: boolean;
}) => {
const Icon = icon;
}: Omit<React.ComponentProps<typeof Link>, "href"> & {
text?: string;
href?: `/${string}`;
icon?: React.ReactNode;
current?: boolean;
}) => {
const item = (
<>
{Icon && <Icon className="stroke-foreground/85 block h-7 w-7 md:h-5 md:w-5" />}
<div className="[&_svg]:stroke-foreground/85 inline-flex items-center [&_svg]:size-7 [&_svg]:md:size-5">
{isValidElement(icon) && icon}
{text && <span className="ml-3 text-sm leading-none font-medium tracking-wide max-md:sr-only">{text}</span>}
</>
</div>
);
// allow both navigational links and/or other interactive react components (e.g. the theme toggle)
@ -32,7 +31,7 @@ const MenuItem = ({
aria-label={text}
data-current={current || undefined}
className={cn(
"text-foreground/85 hover:border-ring data-current:border-primary/40! mb-[-3px] inline-flex items-center p-2.5 hover:border-b-[3px] hover:no-underline data-current:border-b-[3px]",
"text-foreground/85 hover:border-ring data-current:border-primary/40! inline-flex items-center p-2.5 hover:border-b-[3px] hover:no-underline data-current:border-b-[3px]",
className
)}
{...rest}

View File

@ -4,37 +4,52 @@ import { useSelectedLayoutSegment } from "next/navigation";
import MenuItem from "@/components/layout/menu-item";
import ThemeToggle from "@/components/layout/theme-toggle";
import { cn } from "@/lib/utils";
import { menuItems } from "@/lib/config/menu";
import type { ComponentPropsWithoutRef } from "react";
import { HomeIcon, PencilLineIcon, CodeXmlIcon, MailIcon } from "lucide-react";
const Menu = ({ className, ...rest }: ComponentPropsWithoutRef<"ul">) => {
const menuItems: React.ComponentProps<typeof MenuItem>[] = [
{
text: "Home",
href: "/",
icon: <HomeIcon />,
},
{
text: "Notes",
href: "/notes",
icon: <PencilLineIcon />,
},
{
text: "Projects",
href: "/projects",
icon: <CodeXmlIcon />,
},
{
text: "Contact",
href: "/contact",
icon: <MailIcon />,
},
{
icon: <ThemeToggle />,
},
];
const Menu = ({ className, ...rest }: React.ComponentProps<"div">) => {
const segment = useSelectedLayoutSegment() || "";
return (
<ul
className={cn(
"flex max-w-2/3 flex-row justify-between md:max-w-none md:justify-end md:gap-4 max-sm:[&>li]:first-of-type:hidden",
className
)}
<div
className={cn("flex max-w-2/3 flex-row justify-between md:max-w-none md:justify-end md:gap-4", className)}
{...rest}
>
{menuItems.map((item) => {
{menuItems.map((item, index) => {
const isCurrent = item.href?.split("/")[1] === segment;
return (
<li className="inline-block" key={item.href}>
<div className="mt-[3px] inline-block last:-mr-2.5 max-sm:first:hidden" key={index}>
<MenuItem {...item} current={isCurrent} />
</li>
</div>
);
})}
<li className="-mr-2.5 inline-block">
<MenuItem
// @ts-ignore
icon={ThemeToggle}
/>
</li>
</ul>
</div>
);
};

View File

@ -1,13 +1,12 @@
import Link from "@/components/link";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const PageTitle = ({
canonical,
className,
children,
...rest
}: ComponentPropsWithoutRef<"h1"> & {
}: React.ComponentProps<"h1"> & {
canonical: string;
}) => {
return (

View File

@ -2,7 +2,6 @@
import { createContext, useEffect, useState } from "react";
import { useLocalStorage, useMedia } from "react-use";
import type { PropsWithChildren } from "react";
export const ThemeContext = createContext<{
/**
@ -19,7 +18,7 @@ export const ThemeContext = createContext<{
});
// provider used once in _app.tsx to wrap entire app
export const ThemeProvider = ({ children }: PropsWithChildren) => {
export const ThemeProvider = ({ children }: React.PropsWithChildren) => {
// keep track of if/when the user has set their theme *on this site*
const [preferredTheme, setPreferredTheme] = useLocalStorage<string>("theme", undefined, { raw: true });
// keep track of changes to the user's OS/browser dark mode setting

View File

@ -3,20 +3,23 @@
import { useContext } from "react";
import { MoonIcon, SunIcon } from "lucide-react";
import { ThemeContext } from "@/components/layout/theme-context";
import type { ComponentPropsWithoutRef } from "react";
import type { LucideIcon } from "lucide-react";
import { cn } from "@/lib/utils";
const ThemeToggle = ({ ...rest }: ComponentPropsWithoutRef<LucideIcon>) => {
const ThemeToggle = ({ className, ...rest }: React.ComponentProps<"button">) => {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
onClick={() => setTheme(theme === "light" ? "dark" : "light")}
aria-label="Toggle theme"
className="hover:*:stroke-warning block cursor-pointer bg-transparent p-2.5 not-dark:[&_.lucide-moon]:hidden dark:[&_.lucide-sun]:hidden"
className={cn(
"hover:*:stroke-warning block cursor-pointer bg-transparent p-2.5 not-dark:[&_.lucide-moon]:hidden dark:[&_.lucide-sun]:hidden",
className
)}
{...rest}
>
<SunIcon {...rest} />
<MoonIcon {...rest} />
<SunIcon />
<MoonIcon />
<span className="sr-only">Toggle theme</span>
</button>
);

View File

@ -1,6 +1,5 @@
import NextLink from "next/link";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const Link = ({
href,
@ -10,7 +9,7 @@ const Link = ({
dynamicOnHover,
className,
...rest
}: ComponentPropsWithoutRef<typeof NextLink> & {
}: React.ComponentProps<typeof NextLink> & {
// https://github.com/vercel/next.js/pull/77866/files#diff-040f76a8f302dd3a8ec7de0867048475271f052b094cd73d2d0751b495c02f7dR30
dynamicOnHover?: boolean;
}) => {
@ -27,7 +26,7 @@ const Link = ({
className
),
...rest,
} as ComponentPropsWithoutRef<"a">;
} as React.ComponentProps<"a">;
// don't waste time with next's component if it's just an external link
if (isExternal) {

View File

@ -1,5 +1,4 @@
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
// https://magicui.design/docs/components/marquee
const Marquee = ({
@ -7,7 +6,7 @@ const Marquee = ({
className,
children,
...rest
}: ComponentPropsWithoutRef<"div"> & {
}: React.ComponentProps<"div"> & {
reverse?: boolean;
pauseOnHover?: boolean;
repeat?: number;

View File

@ -1,9 +1,8 @@
"use client";
import TimeAgo from "react-timeago";
import { type ComponentPropsWithoutRef } from "react";
const RelativeTime = ({ ...rest }: ComponentPropsWithoutRef<typeof TimeAgo>) => {
const RelativeTime = ({ ...rest }: React.ComponentProps<typeof TimeAgo>) => {
return (
<span suppressHydrationWarning>
<TimeAgo {...rest} />

View File

@ -1,5 +1,4 @@
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const CodePen = ({
username,
@ -15,7 +14,7 @@ const CodePen = ({
defaultTab?: string;
preview?: boolean;
editable?: boolean;
} & ComponentPropsWithoutRef<"iframe">) => {
} & React.ComponentProps<"iframe">) => {
return (
<iframe
src={`https://codepen.io/${username}/embed/${id}/?${new URLSearchParams({

View File

@ -1,13 +1,12 @@
import Link from "@/components/link";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const Gist = async ({
id,
file,
className,
...rest
}: { id: string; file?: string } & ComponentPropsWithoutRef<"iframe">) => {
}: { id: string; file?: string } & React.ComponentProps<"iframe">) => {
const iframeId = `gist-${id}${file ? `-${file}` : ""}`;
const scriptUrl = `https://gist.github.com/${id}.js${file ? `?file=${file}` : ""}`;

View File

@ -1,11 +1,10 @@
"use client";
import YouTubeEmbed from "react-lite-youtube-embed";
import type { ComponentPropsWithoutRef } from "react";
// lite-youtube-embed CSS is imported in app/global.css to save a request
const YouTube = ({ ...rest }: Omit<ComponentPropsWithoutRef<typeof YouTubeEmbed>, "title">) => {
const YouTube = ({ ...rest }: Omit<React.ComponentProps<typeof YouTubeEmbed>, "title">) => {
return <YouTubeEmbed cookie={false} containerElement="div" title="" {...rest} />;
};

View File

@ -3,21 +3,20 @@
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const AlertDialog = ({ ...rest }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Root>) => {
const AlertDialog = ({ ...rest }: React.ComponentProps<typeof AlertDialogPrimitive.Root>) => {
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...rest} />;
};
const AlertDialogTrigger = ({ ...rest }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Trigger>) => {
const AlertDialogTrigger = ({ ...rest }: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) => {
return <AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...rest} />;
};
const AlertDialogPortal = ({ ...rest }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Portal>) => {
const AlertDialogPortal = ({ ...rest }: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) => {
return <AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...rest} />;
};
const AlertDialogOverlay = ({ className, ...rest }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>) => {
const AlertDialogOverlay = ({ className, ...rest }: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) => {
return (
<AlertDialogPrimitive.Overlay
data-slot="alert-dialog-overlay"
@ -30,7 +29,7 @@ const AlertDialogOverlay = ({ className, ...rest }: ComponentPropsWithoutRef<typ
);
};
const AlertDialogContent = ({ className, ...rest }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>) => {
const AlertDialogContent = ({ className, ...rest }: React.ComponentProps<typeof AlertDialogPrimitive.Content>) => {
return (
<AlertDialogPortal>
<AlertDialogOverlay />
@ -46,7 +45,7 @@ const AlertDialogContent = ({ className, ...rest }: ComponentPropsWithoutRef<typ
);
};
const AlertDialogHeader = ({ className, ...rest }: ComponentPropsWithoutRef<"div">) => {
const AlertDialogHeader = ({ className, ...rest }: React.ComponentProps<"div">) => {
return (
<div
data-slot="alert-dialog-header"
@ -56,7 +55,7 @@ const AlertDialogHeader = ({ className, ...rest }: ComponentPropsWithoutRef<"div
);
};
const AlertDialogFooter = ({ className, ...rest }: ComponentPropsWithoutRef<"div">) => {
const AlertDialogFooter = ({ className, ...rest }: React.ComponentProps<"div">) => {
return (
<div
data-slot="alert-dialog-footer"
@ -66,7 +65,7 @@ const AlertDialogFooter = ({ className, ...rest }: ComponentPropsWithoutRef<"div
);
};
const AlertDialogTitle = ({ className, ...rest }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>) => {
const AlertDialogTitle = ({ className, ...rest }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) => {
return (
<AlertDialogPrimitive.Title
data-slot="alert-dialog-title"
@ -79,7 +78,7 @@ const AlertDialogTitle = ({ className, ...rest }: ComponentPropsWithoutRef<typeo
const AlertDialogDescription = ({
className,
...rest
}: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>) => {
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) => {
return (
<AlertDialogPrimitive.Description
data-slot="alert-dialog-description"
@ -89,11 +88,11 @@ const AlertDialogDescription = ({
);
};
const AlertDialogAction = ({ className, ...rest }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>) => {
const AlertDialogAction = ({ className, ...rest }: React.ComponentProps<typeof AlertDialogPrimitive.Action>) => {
return <AlertDialogPrimitive.Action className={cn(buttonVariants(), className)} {...rest} />;
};
const AlertDialogCancel = ({ className, ...rest }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>) => {
const AlertDialogCancel = ({ className, ...rest }: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) => {
return <AlertDialogPrimitive.Cancel className={cn(buttonVariants({ variant: "outline" }), className)} {...rest} />;
};

View File

@ -1,9 +1,8 @@
"use client";
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio";
import type { ComponentPropsWithoutRef } from "react";
const AspectRatio = ({ ...rest }: ComponentPropsWithoutRef<typeof AspectRatioPrimitive.Root>) => {
const AspectRatio = ({ ...rest }: React.ComponentProps<typeof AspectRatioPrimitive.Root>) => {
return <AspectRatioPrimitive.Root data-slot="aspect-ratio" {...rest} />;
};

View File

@ -2,9 +2,8 @@
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const Avatar = ({ className, ...rest }: ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>) => {
const Avatar = ({ className, ...rest }: React.ComponentProps<typeof AvatarPrimitive.Root>) => {
return (
<AvatarPrimitive.Root
data-slot="avatar"
@ -14,13 +13,13 @@ const Avatar = ({ className, ...rest }: ComponentPropsWithoutRef<typeof AvatarPr
);
};
const AvatarImage = ({ className, ...rest }: ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>) => {
const AvatarImage = ({ className, ...rest }: React.ComponentProps<typeof AvatarPrimitive.Image>) => {
return (
<AvatarPrimitive.Image data-slot="avatar-image" className={cn("aspect-square size-full", className)} {...rest} />
);
};
const AvatarFallback = ({ className, ...rest }: ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>) => {
const AvatarFallback = ({ className, ...rest }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) => {
return (
<AvatarPrimitive.Fallback
data-slot="avatar-fallback"

View File

@ -1,10 +1,9 @@
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
export const badgeVariants = cva(
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-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 transition-[color,box-shadow] overflow-hidden",
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-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 overflow-hidden",
{
variants: {
variant: {
@ -26,7 +25,7 @@ const Badge = ({
variant,
asChild = false,
...rest
}: ComponentPropsWithoutRef<"span"> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) => {
}: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & { asChild?: boolean }) => {
const Comp = asChild ? Slot : "span";
return <Comp data-slot="badge" className={cn(badgeVariants({ variant }), className)} {...rest} />;

View File

@ -1,7 +1,6 @@
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
export const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium cursor-pointer 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",
@ -37,7 +36,7 @@ const Button = ({
size,
asChild = false,
...rest
}: ComponentPropsWithoutRef<"button"> &
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}) => {

View File

@ -1,7 +1,6 @@
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const Card = ({ className, ...rest }: ComponentPropsWithoutRef<"div">) => {
const Card = ({ className, ...rest }: React.ComponentProps<"div">) => {
return (
<div
data-slot="card"
@ -11,7 +10,7 @@ const Card = ({ className, ...rest }: ComponentPropsWithoutRef<"div">) => {
);
};
const CardHeader = ({ className, ...rest }: ComponentPropsWithoutRef<"div">) => {
const CardHeader = ({ className, ...rest }: React.ComponentProps<"div">) => {
return (
<div
data-slot="card-header"
@ -24,15 +23,15 @@ const CardHeader = ({ className, ...rest }: ComponentPropsWithoutRef<"div">) =>
);
};
const CardTitle = ({ className, ...rest }: ComponentPropsWithoutRef<"div">) => {
const CardTitle = ({ className, ...rest }: React.ComponentProps<"div">) => {
return <div data-slot="card-title" className={cn("leading-none font-semibold", className)} {...rest} />;
};
const CardDescription = ({ className, ...rest }: ComponentPropsWithoutRef<"div">) => {
const CardDescription = ({ className, ...rest }: React.ComponentProps<"div">) => {
return <div data-slot="card-description" className={cn("text-muted-foreground text-sm", className)} {...rest} />;
};
const CardAction = ({ className, ...rest }: ComponentPropsWithoutRef<"div">) => {
const CardAction = ({ className, ...rest }: React.ComponentProps<"div">) => {
return (
<div
data-slot="card-action"
@ -42,11 +41,11 @@ const CardAction = ({ className, ...rest }: ComponentPropsWithoutRef<"div">) =>
);
};
const CardContent = ({ className, ...rest }: ComponentPropsWithoutRef<"div">) => {
const CardContent = ({ className, ...rest }: React.ComponentProps<"div">) => {
return <div data-slot="card-content" className={cn("px-6", className)} {...rest} />;
};
const CardFooter = ({ className, ...rest }: ComponentPropsWithoutRef<"div">) => {
const CardFooter = ({ className, ...rest }: React.ComponentProps<"div">) => {
return <div data-slot="card-footer" className={cn("flex items-center px-6 [.border-t]:pt-6", className)} {...rest} />;
};

View File

@ -3,17 +3,16 @@
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const DropdownMenu = ({ ...rest }: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root>) => {
const DropdownMenu = ({ ...rest }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) => {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...rest} />;
};
const DropdownMenuPortal = ({ ...rest }: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Portal>) => {
const DropdownMenuPortal = ({ ...rest }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) => {
return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...rest} />;
};
const DropdownMenuTrigger = ({ ...rest }: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>) => {
const DropdownMenuTrigger = ({ ...rest }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) => {
return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...rest} />;
};
@ -21,7 +20,7 @@ const DropdownMenuContent = ({
className,
sideOffset = 4,
...rest
}: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>) => {
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) => {
return (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
@ -37,7 +36,7 @@ const DropdownMenuContent = ({
);
};
const DropdownMenuGroup = ({ ...rest }: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Group>) => {
const DropdownMenuGroup = ({ ...rest }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) => {
return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...rest} />;
};
@ -46,7 +45,7 @@ const DropdownMenuItem = ({
inset,
variant = "default",
...rest
}: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean;
variant?: "default" | "destructive";
}) => {
@ -69,7 +68,7 @@ const DropdownMenuCheckboxItem = ({
children,
checked,
...rest
}: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>) => {
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) => {
return (
<DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item"
@ -90,7 +89,7 @@ const DropdownMenuCheckboxItem = ({
);
};
const DropdownMenuRadioGroup = ({ ...rest }: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioGroup>) => {
const DropdownMenuRadioGroup = ({ ...rest }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) => {
return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...rest} />;
};
@ -98,7 +97,7 @@ const DropdownMenuRadioItem = ({
className,
children,
...rest
}: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>) => {
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) => {
return (
<DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item"
@ -122,7 +121,7 @@ const DropdownMenuLabel = ({
className,
inset,
...rest
}: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean;
}) => {
return (
@ -138,7 +137,7 @@ const DropdownMenuLabel = ({
const DropdownMenuSeparator = ({
className,
...rest
}: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>) => {
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) => {
return (
<DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator"
@ -148,7 +147,7 @@ const DropdownMenuSeparator = ({
);
};
const DropdownMenuShortcut = ({ className, ...rest }: ComponentPropsWithoutRef<"span">) => {
const DropdownMenuShortcut = ({ className, ...rest }: React.ComponentProps<"span">) => {
return (
<span
data-slot="dropdown-menu-shortcut"
@ -158,7 +157,7 @@ const DropdownMenuShortcut = ({ className, ...rest }: ComponentPropsWithoutRef<"
);
};
const DropdownMenuSub = ({ ...rest }: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Sub>) => {
const DropdownMenuSub = ({ ...rest }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) => {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...rest} />;
};
@ -167,7 +166,7 @@ const DropdownMenuSubTrigger = ({
inset,
children,
...rest
}: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean;
}) => {
return (
@ -189,7 +188,7 @@ const DropdownMenuSubTrigger = ({
const DropdownMenuSubContent = ({
className,
...rest
}: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>) => {
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) => {
return (
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"

View File

@ -1,7 +1,6 @@
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const Input = ({ className, type, ...props }: ComponentPropsWithoutRef<"input">) => {
const Input = ({ className, type, ...props }: React.ComponentProps<"input">) => {
return (
<input
type={type}

View File

@ -2,9 +2,8 @@
import * as LabelPrimitive from "@radix-ui/react-label";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const Label = ({ className, ...rest }: ComponentPropsWithoutRef<typeof LabelPrimitive.Root>) => {
const Label = ({ className, ...rest }: React.ComponentProps<typeof LabelPrimitive.Root>) => {
return (
<LabelPrimitive.Root
data-slot="label"

View File

@ -2,13 +2,12 @@
import * as PopoverPrimitive from "@radix-ui/react-popover";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const Popover = ({ ...rest }: ComponentPropsWithoutRef<typeof PopoverPrimitive.Root>) => {
const Popover = ({ ...rest }: React.ComponentProps<typeof PopoverPrimitive.Root>) => {
return <PopoverPrimitive.Root data-slot="popover" {...rest} />;
};
const PopoverTrigger = ({ ...rest }: ComponentPropsWithoutRef<typeof PopoverPrimitive.Trigger>) => {
const PopoverTrigger = ({ ...rest }: React.ComponentProps<typeof PopoverPrimitive.Trigger>) => {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...rest} />;
};
@ -17,7 +16,7 @@ const PopoverContent = ({
align = "center",
sideOffset = 4,
...rest
}: ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>) => {
}: React.ComponentProps<typeof PopoverPrimitive.Content>) => {
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
@ -34,7 +33,7 @@ const PopoverContent = ({
);
};
const PopoverAnchor = ({ ...rest }: ComponentPropsWithoutRef<typeof PopoverPrimitive.Anchor>) => {
const PopoverAnchor = ({ ...rest }: React.ComponentProps<typeof PopoverPrimitive.Anchor>) => {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...rest} />;
};

View File

@ -2,14 +2,13 @@
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const ScrollArea = ({ className, children, ...rest }: ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>) => {
const ScrollArea = ({ className, children, ...rest }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) => {
return (
<ScrollAreaPrimitive.Root data-slot="scroll-area" className={cn("relative", className)} {...rest}>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
className="focus-visible:ring-ring/50 size-full rounded-[inherit] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
>
{children}
</ScrollAreaPrimitive.Viewport>
@ -23,13 +22,13 @@ const ScrollBar = ({
className,
orientation = "vertical",
...rest
}: ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) => {
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) => {
return (
<ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
"flex touch-none p-px transition-colors select-none",
"flex touch-none p-px select-none",
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
className

View File

@ -2,14 +2,13 @@
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const Separator = ({
className,
orientation = "horizontal",
decorative = true,
...rest
}: ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>) => {
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) => {
return (
<SeparatorPrimitive.Root
data-slot="separator-root"

View File

@ -1,7 +1,6 @@
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const Skeleton = ({ className, ...rest }: ComponentPropsWithoutRef<"div">) => {
const Skeleton = ({ className, ...rest }: React.ComponentProps<"div">) => {
return <div data-slot="skeleton" className={cn("bg-accent animate-pulse rounded-md", className)} {...rest} />;
};

View File

@ -1,7 +1,6 @@
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const Textarea = ({ className, ...props }: ComponentPropsWithoutRef<"textarea">) => {
const Textarea = ({ className, ...props }: React.ComponentProps<"textarea">) => {
return (
<textarea
data-slot="textarea"

View File

@ -2,16 +2,12 @@
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const TooltipProvider = ({
delayDuration = 0,
...rest
}: ComponentPropsWithoutRef<typeof TooltipPrimitive.Provider>) => {
const TooltipProvider = ({ delayDuration = 0, ...rest }: React.ComponentProps<typeof TooltipPrimitive.Provider>) => {
return <TooltipPrimitive.Provider data-slot="tooltip-provider" delayDuration={delayDuration} {...rest} />;
};
const Tooltip = ({ ...rest }: ComponentPropsWithoutRef<typeof TooltipPrimitive.Root>) => {
const Tooltip = ({ ...rest }: React.ComponentProps<typeof TooltipPrimitive.Root>) => {
return (
<TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...rest} />
@ -19,7 +15,7 @@ const Tooltip = ({ ...rest }: ComponentPropsWithoutRef<typeof TooltipPrimitive.R
);
};
const TooltipTrigger = ({ ...rest }: ComponentPropsWithoutRef<typeof TooltipPrimitive.Trigger>) => {
const TooltipTrigger = ({ ...rest }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) => {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...rest} />;
};
@ -28,7 +24,7 @@ const TooltipContent = ({
sideOffset = 0,
children,
...rest
}: ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>) => {
}: React.ComponentProps<typeof TooltipPrimitive.Content>) => {
return (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content

View File

@ -1,5 +1,4 @@
import { cn } from "@/lib/utils";
import type { ComponentPropsWithoutRef } from "react";
const Video = ({
src,
@ -7,7 +6,7 @@ const Video = ({
className,
children,
...rest
}: Omit<Partial<ComponentPropsWithoutRef<"video">>, "src"> & {
}: Omit<Partial<React.ComponentProps<"video">>, "src"> & {
src: string | string[] | undefined;
}) => {
return (

View File

@ -1,9 +1,8 @@
import { env } from "@/lib/env";
import { Feed } from "feed";
import { Feed, type Item as FeedItem } from "feed";
import { getFrontMatter, getContent } from "@/lib/posts";
import siteConfig from "@/lib/config/site";
import authorConfig from "@/lib/config/author";
import type { Item as FeedItem } from "feed";
import ogImage from "@/app/opengraph-image.jpg";

View File

@ -1,30 +0,0 @@
import { HomeIcon, PencilLineIcon, CodeXmlIcon, MailIcon, type LucideIcon } from "lucide-react";
export type MenuItemConfig = {
text?: string;
href?: `/${string}`;
icon?: LucideIcon;
};
export const menuItems: MenuItemConfig[] = [
{
text: "Home",
href: "/",
icon: HomeIcon,
},
{
text: "Notes",
href: "/notes",
icon: PencilLineIcon,
},
{
text: "Projects",
href: "/projects",
icon: CodeXmlIcon,
},
{
text: "Contact",
href: "/contact",
icon: MailIcon,
},
];

View File

@ -1,7 +1,6 @@
import { env } from "@/lib/env";
import siteConfig from "@/lib/config/site";
import authorConfig from "@/lib/config/author";
import type { Metadata } from "next";
export const defaultMetadata: Metadata = {

View File

@ -8,9 +8,7 @@ import Tweet from "@/components/third-party/tweet";
import YouTube from "@/components/third-party/youtube";
import Gist from "@/components/third-party/gist";
import CodePen from "@/components/third-party/codepen";
import { cn } from "@/lib/utils";
import type { MDXComponents } from "mdx/types";
export const useMDXComponents = (components: MDXComponents): MDXComponents => {

View File

@ -37,9 +37,9 @@ After converting a few more components, I started to feel better and better each
Don't get me wrong, I still think the syntax Tailwind forces you to write is an abomination. **But honestly, so was my CSS.**
Maybe that's on me, or maybe not, but my primary reason to hate on Tailwind for years — _"it makes my HTML/JSX ugly and design doesn't belong sprinkled throughout a markup language"_ — just flew out the window either way. Sure, I tried to make my CSS consistent and logical, making tons of variables for colors and sizes and border radii. But that wasn't nearly as comforting as being certain that `w-12` will **always** be twice the width of `w-6` no matter how badly I mess things up.
Maybe that's on me, or maybe not, but my primary reason to hate on Tailwind for years — _"it makes my HTML/JSX ugly and design doesn't belong sprinkled throughout a markup language"_ — just flew out the window either way. Sure, I tried to make my CSS consistent and logical, making tons of variables for colors and sizes and border radii. But that wasn't nearly as comforting as being certain that `w12` will **always** be twice the width of `w6` no matter how badly I mess things up.
And on top of all of the AI tools mentioned above being Tailwind experts, the [IDE support](https://tailwindcss.com/docs/editor-setup) is also excellent. One click to install the official [IntelliSense extension](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) for VS Code, and suddenly everywhere I wrote <span style={{ color: "#38bdf8" }}>`text-sky-400`</span> throughout my code had a lovely little light blue square next to it. The official [Prettier extension](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) ensures the order of class names doesn't cause unexpected specificity problems from a rule four layers up overriding a rule you thought you were currently looking at — historically my biggest painpoint of CSS by far.
And on top of all of the AI tools mentioned above being Tailwind experts, the [IDE support](https://tailwindcss.com/docs/editor-setup) is also excellent. One click to install the official [IntelliSense extension](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) for VS Code, and suddenly everywhere I wrote <span style={{ color: "#00bcff" }}>`textsky400`</span> throughout my code had a lovely little light blue square next to it. The official [Prettier extension](https://github.com/tailwindlabs/prettier-plugin-tailwindcss) ensures the order of class names doesn't cause unexpected specificity problems from a rule four layers up overriding a rule you thought you were currently looking at — historically my biggest painpoint of CSS by far.
All of these tools together actually made the [process](https://github.com/jakejarvis/jarv.is/pull/2387) of revamping this site oddly fun. It shined a spotlight on a lot of issues I had no idea were there — especially by forcing me to think ["mobile-first"](https://tailwindcss.com/docs/responsive-design#working-mobile-first) — and gave me an opportunity to put a new coat of paint on a design I haven't made major changes to [since my last blog post](/notes/hugo-to-nextjs)...three years ago.