mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2026-06-05 16:55:29 -04:00
refactor: replace react-countup with @number-flow/react
This commit is contained in:
+2
-2
@@ -1,4 +1,4 @@
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
import { IconArrowUpRight } from "@tabler/icons-react";
|
||||
import Image, { type StaticImageData } from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
@@ -97,7 +97,7 @@ const Page = () => (
|
||||
</div>
|
||||
<span className="text-muted-foreground ml-9 text-xs text-pretty sm:ml-auto">
|
||||
{project.tagline}
|
||||
<ArrowUpRight
|
||||
<IconArrowUpRight
|
||||
className="group-hover:text-primary ml-1 inline size-3.5 shrink-0 transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
+10
-16
@@ -1,10 +1,4 @@
|
||||
import {
|
||||
CalendarDaysIcon,
|
||||
EyeIcon,
|
||||
MessagesSquareIcon,
|
||||
SquarePenIcon,
|
||||
TagIcon,
|
||||
} from "lucide-react";
|
||||
import { IconCalendarEvent, IconEdit, IconEye, IconMessages, IconTag } from "@tabler/icons-react";
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { notFound } from "next/navigation";
|
||||
@@ -110,14 +104,14 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="text-foreground/70 flex flex-wrap justify-items-start space-y-2.5 space-x-4 text-[13px] tracking-wide">
|
||||
<div className="text-foreground/70 flex flex-wrap items-center gap-x-4 gap-y-2.5 text-[13px] tracking-wide">
|
||||
<Link
|
||||
href={`/${POSTS_DIR}/${frontmatter?.slug}`}
|
||||
className={
|
||||
"text-foreground/70 flex flex-nowrap items-center gap-1.5 whitespace-nowrap hover:no-underline"
|
||||
"flex flex-nowrap items-center gap-1.5 whitespace-nowrap text-inherit hover:no-underline"
|
||||
}
|
||||
>
|
||||
<CalendarDaysIcon className="inline size-3 shrink-0" aria-hidden="true" />
|
||||
<IconCalendarEvent className="inline size-3.5 shrink-0" aria-hidden="true" />
|
||||
<time
|
||||
dateTime={formattedDates.dateISO}
|
||||
title={formattedDates.dateTitle}
|
||||
@@ -129,7 +123,7 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
|
||||
|
||||
{frontmatter?.tags && (
|
||||
<div className="flex flex-wrap items-center gap-1.5">
|
||||
<TagIcon className="inline size-3 shrink-0" aria-hidden="true" />
|
||||
<IconTag className="inline size-3.5 shrink-0" aria-hidden="true" />
|
||||
{frontmatter?.tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
@@ -146,23 +140,23 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
|
||||
href={`https://github.com/${process.env.NEXT_PUBLIC_GITHUB_REPO}/blob/main/${POSTS_DIR}/${frontmatter?.slug}/index.mdx`}
|
||||
title={`Edit "${frontmatter?.title}" on GitHub`}
|
||||
className={
|
||||
"text-foreground/70 flex flex-nowrap items-center gap-1.5 whitespace-nowrap hover:no-underline"
|
||||
"flex flex-nowrap items-center gap-1.5 whitespace-nowrap text-inherit hover:no-underline"
|
||||
}
|
||||
>
|
||||
<SquarePenIcon className="inline size-3 shrink-0" aria-hidden="true" />
|
||||
<IconEdit className="inline size-3.5 shrink-0" aria-hidden="true" />
|
||||
<span>Improve This Post</span>
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href={`/${POSTS_DIR}/${frontmatter?.slug}#comments`}
|
||||
className="text-foreground/70 flex flex-nowrap items-center gap-1.5 whitespace-nowrap hover:no-underline"
|
||||
className="flex flex-nowrap items-center gap-1.5 whitespace-nowrap text-inherit hover:no-underline"
|
||||
>
|
||||
<MessagesSquareIcon className="inline size-3 shrink-0" aria-hidden="true" />
|
||||
<IconMessages className="inline size-3.5 shrink-0" aria-hidden="true" />
|
||||
<CommentCount slug={`${POSTS_DIR}/${frontmatter?.slug}`} />
|
||||
</Link>
|
||||
|
||||
<div className="flex min-w-14 flex-nowrap items-center gap-1.5 whitespace-nowrap">
|
||||
<EyeIcon className="inline size-3 shrink-0" aria-hidden="true" />
|
||||
<IconEye className="inline size-3.5 shrink-0" aria-hidden="true" />
|
||||
<ViewCounter slug={`${POSTS_DIR}/${frontmatter?.slug}`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ExternalLinkIcon, GitForkIcon, StarIcon } from "lucide-react";
|
||||
import { IconExternalLink, IconGitFork, IconStar } from "@tabler/icons-react";
|
||||
import { notFound } from "next/navigation";
|
||||
import { Suspense } from "react";
|
||||
|
||||
@@ -113,7 +113,7 @@ const Page = async () => {
|
||||
rel="noopener noreferrer"
|
||||
className="text-muted-foreground hover:text-primary inline-flex flex-nowrap items-center gap-1.5 hover:no-underline"
|
||||
>
|
||||
<StarIcon className="inline-block size-3.5 shrink-0" aria-hidden="true" />
|
||||
<IconStar className="inline-block size-3.5 shrink-0" aria-hidden="true" />
|
||||
<span>
|
||||
{Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(
|
||||
repo?.stargazerCount,
|
||||
@@ -130,7 +130,7 @@ const Page = async () => {
|
||||
title={`${Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(repo?.forkCount)} ${repo?.forkCount === 1 ? "fork" : "forks"}`}
|
||||
className="text-muted-foreground hover:text-primary inline-flex flex-nowrap items-center gap-1.5 hover:no-underline"
|
||||
>
|
||||
<GitForkIcon className="inline-block size-3.5 shrink-0" aria-hidden="true" />
|
||||
<IconGitFork className="inline-block size-3.5 shrink-0" aria-hidden="true" />
|
||||
<span>
|
||||
{Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(
|
||||
repo?.forkCount,
|
||||
@@ -169,7 +169,7 @@ const Page = async () => {
|
||||
}
|
||||
>
|
||||
View all
|
||||
<ExternalLinkIcon className="inline-block size-3.5 shrink-0" aria-hidden="true" />
|
||||
<IconExternalLink className="inline-block size-3.5 shrink-0" aria-hidden="true" />
|
||||
</Button>
|
||||
</p>
|
||||
</FadeTransition>
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"iconLibrary": "tabler",
|
||||
"rtl": false,
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
|
||||
@@ -48,8 +48,8 @@ const ActivityCalendar = ({
|
||||
renderBlock={(block, activity) => (
|
||||
<Tooltip>
|
||||
<TooltipTrigger render={block} />
|
||||
<TooltipContent>
|
||||
<span className="text-[0.825rem] font-medium">{`${activity.count === 0 ? "No" : activity.count} ${noun}${activity.count === 1 ? "" : "s"} on ${new Date(activity.date).toLocaleDateString("en-US", { month: "short", day: "numeric" })}`}</span>
|
||||
<TooltipContent sideOffset={8}>
|
||||
{`${activity.count === 0 ? "No" : activity.count} ${noun}${activity.count === 1 ? "" : "s"} on ${new Date(activity.date).toLocaleDateString("en-US", { month: "short", day: "numeric" })}`}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import NumberFlow from "@number-flow/react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { getCommentCount } from "@/lib/server/comments";
|
||||
@@ -28,11 +29,11 @@ const CommentCount = ({ slug }: { slug: string }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
title={`${Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(count)} ${count === 1 ? "comment" : "comments"}`}
|
||||
>
|
||||
{Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(count)}
|
||||
</span>
|
||||
<NumberFlow
|
||||
className={count === null ? "motion-safe:animate-pulse" : undefined}
|
||||
locales={process.env.NEXT_PUBLIC_SITE_LOCALE}
|
||||
value={count}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { EditIcon, EllipsisIcon, Loader2Icon, ReplyIcon, Trash2Icon } from "lucide-react";
|
||||
import { IconDots, IconEdit, IconMessageReply, IconTrash } from "@tabler/icons-react";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { useSession } from "@/lib/auth-client";
|
||||
import { type CommentWithUser, deleteComment } from "@/lib/server/comments";
|
||||
|
||||
@@ -75,19 +76,19 @@ const CommentActions = ({ comment }: { comment: CommentWithUser }) => {
|
||||
setMode(mode.type === "replying" ? { type: "idle" } : { type: "replying" })
|
||||
}
|
||||
>
|
||||
<ReplyIcon />
|
||||
<IconMessageReply />
|
||||
Reply
|
||||
</Button>
|
||||
|
||||
{session.user.id === comment.user.id && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger render={<Button variant="outline" size="sm" />}>
|
||||
<EllipsisIcon />
|
||||
<IconDots />
|
||||
<span className="sr-only">Actions Menu</span>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={() => setMode({ type: "editing" })}>
|
||||
<EditIcon />
|
||||
<IconEdit />
|
||||
Edit
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
@@ -95,7 +96,7 @@ const CommentActions = ({ comment }: { comment: CommentWithUser }) => {
|
||||
disabled={isDeleting}
|
||||
variant="destructive"
|
||||
>
|
||||
{isDeleting ? <Loader2Icon className="animate-spin" /> : <Trash2Icon />}
|
||||
{isDeleting ? <Spinner /> : <IconTrash />}
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { InfoIcon, Loader2Icon } from "lucide-react";
|
||||
import { IconInfoCircle, IconMarkdown } from "@tabler/icons-react";
|
||||
import { createContext, useContext, useMemo, useState, useTransition } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { MarkdownIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useSession } from "@/lib/auth-client";
|
||||
import { createComment, updateComment } from "@/lib/server/comments";
|
||||
@@ -122,7 +122,7 @@ const SubmitButton = ({
|
||||
<Button type="submit" disabled={isPending || disabled}>
|
||||
{isPending ? (
|
||||
<>
|
||||
<Loader2Icon className="animate-spin" />
|
||||
<Spinner />
|
||||
{pendingLabel}
|
||||
</>
|
||||
) : (
|
||||
@@ -134,7 +134,7 @@ const SubmitButton = ({
|
||||
// Markdown help popover (only shown for new comments)
|
||||
const MarkdownHelp = () => (
|
||||
<p className="text-muted-foreground text-[0.8rem] leading-relaxed">
|
||||
<MarkdownIcon className="mr-1.5 inline-block size-4 align-text-top" />
|
||||
<IconMarkdown className="mr-1.5 inline-block size-4 align-text-top" />
|
||||
<span className="max-md:hidden">Basic </span>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
@@ -150,7 +150,7 @@ const MarkdownHelp = () => (
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start">
|
||||
<p className="text-sm leading-loose">
|
||||
<InfoIcon className="mr-1.5 inline size-4.5 align-text-top" />
|
||||
<IconInfoCircle className="mr-1.5 inline size-4.5 align-text-top" />
|
||||
Examples:
|
||||
</p>
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { Loader2Icon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { GitHubIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { signIn } from "@/lib/auth-client";
|
||||
|
||||
const SignIn = ({ callbackPath }: { callbackPath?: string }) => {
|
||||
@@ -28,7 +28,7 @@ const SignIn = ({ callbackPath }: { callbackPath?: string }) => {
|
||||
|
||||
return (
|
||||
<Button onClick={handleSignIn} disabled={isLoading} size="lg" variant="outline">
|
||||
{isLoading ? <Loader2Icon className="animate-spin" /> : <GitHubIcon />}
|
||||
{isLoading ? <Spinner /> : <GitHubIcon />}
|
||||
Sign in with GitHub
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { IconCheck, IconClipboardCheck, IconCopy } from "@tabler/icons-react";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { CheckIcon, ClipboardCheckIcon, CopyIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -34,7 +34,7 @@ function CopyButton({
|
||||
copy(value);
|
||||
setHasCopied(true);
|
||||
toast.success("Copied!", {
|
||||
icon: <ClipboardCheckIcon className="text-foreground/85 size-4" aria-hidden="true" />,
|
||||
icon: <IconClipboardCheck className="text-foreground/85 size-4" aria-hidden="true" />,
|
||||
duration: 2000,
|
||||
id: "copy-button-toast-success",
|
||||
});
|
||||
@@ -52,7 +52,7 @@ function CopyButton({
|
||||
size="icon"
|
||||
variant={variant}
|
||||
className={cn(
|
||||
"bg-code hover:bg-accent dark:hover:bg-accent absolute top-3 right-2 z-10 size-7.5 hover:opacity-100 focus-visible:opacity-100",
|
||||
"text-muted-foreground bg-code hover:bg-accent dark:hover:bg-accent absolute top-3 right-2 z-10 size-7.5 hover:opacity-100 focus-visible:opacity-100",
|
||||
hasCopied ? "cursor-default" : "cursor-pointer",
|
||||
className,
|
||||
)}
|
||||
@@ -61,9 +61,9 @@ function CopyButton({
|
||||
{...props}
|
||||
>
|
||||
{hasCopied ? (
|
||||
<CheckIcon className="text-green-600 dark:text-green-400" aria-hidden="true" />
|
||||
<IconCheck className="text-green-600 dark:text-green-400" aria-hidden="true" />
|
||||
) : (
|
||||
<CopyIcon aria-hidden="true" />
|
||||
<IconCopy aria-hidden="true" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
"use client";
|
||||
|
||||
// marking the library as a proper client component so that react doesn't complain about hydration whenever we use it in
|
||||
// a server component.
|
||||
// see: https://react.dev/reference/rsc/use-client#using-third-party-libraries
|
||||
export { default as CountUp } from "react-countup";
|
||||
@@ -1,5 +1,3 @@
|
||||
// miscellaneous icons that are not part of lucide-react
|
||||
|
||||
export const Win95Icon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -14,20 +12,6 @@ export const Win95Icon = ({ className }: { className?: string }) => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const MarkdownIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
strokeWidth="0"
|
||||
viewBox="0 0 24 24"
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M22.27 19.385H1.73A1.73 1.73 0 010 17.655V6.345a1.73 1.73 0 011.73-1.73h20.54A1.73 1.73 0 0124 6.345v11.308a1.73 1.73 0 01-1.73 1.731zM5.769 15.923v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.232 12h-2.309V8.077h-2.307V12h-2.308l3.461 4.039z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const GitHubIcon = ({ className }: { className?: string }) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
||||
@@ -40,6 +40,7 @@ const ImageDiff = ({
|
||||
style={{ aspectRatio }}
|
||||
itemOne={<ReactCompareSliderImage {...beforeImageProps} className="size-full object-cover" />}
|
||||
itemTwo={<ReactCompareSliderImage {...afterImageProps} className="size-full object-cover" />}
|
||||
suppressHydrationWarning
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { AtSignIcon, ExternalLinkIcon, MoonIcon, SunIcon } from "lucide-react";
|
||||
import { IconAt, IconExternalLink, IconMoon, IconSun } from "@tabler/icons-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
@@ -228,15 +228,15 @@ const ContactPopover = () => (
|
||||
delay={0}
|
||||
render={<Button variant="ghost" size="icon" aria-label="Open contact links" />}
|
||||
>
|
||||
<AtSignIcon aria-hidden="true" />
|
||||
<IconAt aria-hidden="true" />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="max-h-(--available-height) overflow-y-auto">
|
||||
<PopoverContent align="end" className="max-h-(--available-height) gap-2 overflow-y-auto p-2">
|
||||
<PopoverHeader className="mt-1 px-1">
|
||||
<PopoverTitle>Get in touch:</PopoverTitle>
|
||||
<PopoverDescription className="sr-only">Email and social links.</PopoverDescription>
|
||||
</PopoverHeader>
|
||||
|
||||
<nav aria-label="Contact links" className="flex flex-col gap-1">
|
||||
<nav aria-label="Contact links" className="flex flex-col gap-0.5">
|
||||
{contactLinks.map((link) => (
|
||||
<a
|
||||
key={link.href}
|
||||
@@ -255,7 +255,7 @@ const ContactPopover = () => (
|
||||
{link.value}
|
||||
</span>
|
||||
{link.external ? (
|
||||
<ExternalLinkIcon className="text-muted-foreground/70 size-3.5" aria-hidden="true" />
|
||||
<IconExternalLink className="text-muted-foreground/70 size-3.5" aria-hidden="true" />
|
||||
) : null}
|
||||
</a>
|
||||
))}
|
||||
@@ -339,8 +339,8 @@ const Header = ({ className }: { className?: string }) => {
|
||||
aria-label="Toggle theme"
|
||||
className="group"
|
||||
>
|
||||
<SunIcon className="group-hover:stroke-orange-600 dark:hidden" aria-hidden="true" />
|
||||
<MoonIcon
|
||||
<IconSun className="group-hover:stroke-orange-600 dark:hidden" aria-hidden="true" />
|
||||
<IconMoon
|
||||
className="not-dark:hidden group-hover:stroke-yellow-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
@@ -39,7 +39,7 @@ const Menu = () => {
|
||||
nativeButton={false}
|
||||
aria-label={item.text}
|
||||
data-current={isCurrent || undefined}
|
||||
className="data-current:bg-accent/60 data-current:text-accent-foreground text-sm leading-none"
|
||||
className="data-current:bg-accent/60 data-current:text-accent-foreground px-2.5 py-3.5 text-sm leading-none"
|
||||
render={<Link href={item.href} transitionTypes={transitionTypes} />}
|
||||
>
|
||||
{item.text}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { EyeIcon, MessagesSquareIcon } from "lucide-react";
|
||||
import { IconEye, IconMessages } from "@tabler/icons-react";
|
||||
import Link from "next/link";
|
||||
import { createContext, type ReactNode, useContext, useEffect, useState } from "react";
|
||||
|
||||
@@ -74,7 +74,7 @@ const PostStats = ({ slug }: { slug: string }) => {
|
||||
variant="secondary"
|
||||
className="text-foreground/80 gap-[5px] text-[11px] tabular-nums"
|
||||
>
|
||||
<EyeIcon className="text-foreground/65" aria-hidden="true" />
|
||||
<IconEye className="text-foreground/65" aria-hidden="true" />
|
||||
{numberFormatter.format(viewCount)}
|
||||
</Badge>
|
||||
)}
|
||||
@@ -90,7 +90,7 @@ const PostStats = ({ slug }: { slug: string }) => {
|
||||
/>
|
||||
}
|
||||
>
|
||||
<MessagesSquareIcon className="text-foreground/65" aria-hidden="true" />
|
||||
<IconMessages className="text-foreground/65" aria-hidden="true" />
|
||||
{numberFormatter.format(commentCount)}
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
|
||||
import { XIcon } from "lucide-react";
|
||||
import { IconX } from "@tabler/icons-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -61,7 +61,7 @@ function DialogContent({
|
||||
data-slot="dialog-close"
|
||||
render={<Button variant="ghost" className="absolute top-2 right-2" size="icon-sm" />}
|
||||
>
|
||||
<XIcon />
|
||||
<IconX />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Menu as MenuPrimitive } from "@base-ui/react/menu";
|
||||
import { ChevronRightIcon, CheckIcon } from "lucide-react";
|
||||
import { IconCheck, IconChevronRight } from "@tabler/icons-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -119,7 +119,7 @@ function DropdownMenuSubTrigger({
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto" />
|
||||
<IconChevronRight className="ml-auto" />
|
||||
</MenuPrimitive.SubmenuTrigger>
|
||||
);
|
||||
}
|
||||
@@ -173,7 +173,7 @@ function DropdownMenuCheckboxItem({
|
||||
data-slot="dropdown-menu-checkbox-item-indicator"
|
||||
>
|
||||
<MenuPrimitive.CheckboxItemIndicator>
|
||||
<CheckIcon />
|
||||
<IconCheck />
|
||||
</MenuPrimitive.CheckboxItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
@@ -208,7 +208,7 @@ function DropdownMenuRadioItem({
|
||||
data-slot="dropdown-menu-radio-item-indicator"
|
||||
>
|
||||
<MenuPrimitive.RadioItemIndicator>
|
||||
<CheckIcon />
|
||||
<IconCheck />
|
||||
</MenuPrimitive.RadioItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Select as SelectPrimitive } from "@base-ui/react/select";
|
||||
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react";
|
||||
import { IconCheck, IconChevronDown, IconChevronUp } from "@tabler/icons-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -48,7 +48,7 @@ function SelectTrigger({
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon
|
||||
render={<ChevronDownIcon className="text-muted-foreground pointer-events-none size-4" />}
|
||||
render={<IconChevronDown className="text-muted-foreground pointer-events-none size-4" />}
|
||||
/>
|
||||
</SelectPrimitive.Trigger>
|
||||
);
|
||||
@@ -124,7 +124,7 @@ function SelectItem({ className, children, ...props }: SelectPrimitive.Item.Prop
|
||||
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
|
||||
}
|
||||
>
|
||||
<CheckIcon className="pointer-events-none" />
|
||||
<IconCheck className="pointer-events-none" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</SelectPrimitive.Item>
|
||||
);
|
||||
@@ -153,7 +153,7 @@ function SelectScrollUpButton({
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon />
|
||||
<IconChevronUp />
|
||||
</SelectPrimitive.ScrollUpArrow>
|
||||
);
|
||||
}
|
||||
@@ -171,7 +171,7 @@ function SelectScrollDownButton({
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
<IconChevronDown />
|
||||
</SelectPrimitive.ScrollDownArrow>
|
||||
);
|
||||
}
|
||||
|
||||
+12
-11
@@ -1,15 +1,16 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
CircleCheckIcon,
|
||||
InfoIcon,
|
||||
TriangleAlertIcon,
|
||||
OctagonXIcon,
|
||||
Loader2Icon,
|
||||
} from "lucide-react";
|
||||
IconAlertTriangle,
|
||||
IconCircleCheck,
|
||||
IconCircleX,
|
||||
IconInfoCircle,
|
||||
} from "@tabler/icons-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
||||
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme();
|
||||
|
||||
@@ -18,11 +19,11 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
icons={{
|
||||
success: <CircleCheckIcon className="size-4" />,
|
||||
info: <InfoIcon className="size-4" />,
|
||||
warning: <TriangleAlertIcon className="size-4" />,
|
||||
error: <OctagonXIcon className="size-4" />,
|
||||
loading: <Loader2Icon className="size-4 animate-spin" />,
|
||||
success: <IconCircleCheck className="size-4" />,
|
||||
info: <IconInfoCircle className="size-4" />,
|
||||
warning: <IconAlertTriangle className="size-4" />,
|
||||
error: <IconCircleX className="size-4" />,
|
||||
loading: <Spinner className="size-4" />,
|
||||
}}
|
||||
style={
|
||||
{
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Loader2Icon } from "lucide-react";
|
||||
import { IconLoader2 } from "@tabler/icons-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
||||
return (
|
||||
<Loader2Icon
|
||||
<IconLoader2
|
||||
role="status"
|
||||
aria-label="Loading"
|
||||
className={cn("size-4 animate-spin", className)}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import NumberFlow from "@number-flow/react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { CountUp } from "@/components/count-up";
|
||||
import { incrementViews } from "@/lib/server/views";
|
||||
|
||||
const ViewCounter = ({ slug }: { slug: string }) => {
|
||||
@@ -25,16 +25,12 @@ const ViewCounter = ({ slug }: { slug: string }) => {
|
||||
return <span title="Error getting views! :(">?</span>;
|
||||
}
|
||||
|
||||
if (views === null) {
|
||||
return <span className="motion-safe:animate-pulse">0</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<span
|
||||
title={`${Intl.NumberFormat(process.env.NEXT_PUBLIC_SITE_LOCALE).format(views)} ${views === 1 ? "view" : "views"}`}
|
||||
>
|
||||
<CountUp start={0} end={views} delay={0} duration={1.5} />
|
||||
</span>
|
||||
<NumberFlow
|
||||
className={views === null ? "motion-safe:animate-pulse" : undefined}
|
||||
locales={process.env.NEXT_PUBLIC_SITE_LOCALE}
|
||||
value={views ?? 0}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+8
-8
@@ -27,10 +27,12 @@
|
||||
"@mdx-js/loader": "^3.1.1",
|
||||
"@mdx-js/react": "^3.1.1",
|
||||
"@next/mdx": "16.2.4",
|
||||
"@number-flow/react": "^0.6.0",
|
||||
"@octokit/graphql": "^9.0.3",
|
||||
"@octokit/graphql-schema": "^15.26.1",
|
||||
"@tabler/icons-react": "^3.41.1",
|
||||
"@vercel/analytics": "^2.0.1",
|
||||
"@vercel/functions": "^3.4.4",
|
||||
"@vercel/functions": "^3.4.6",
|
||||
"@vercel/speed-insights": "^2.0.0",
|
||||
"better-auth": "^1.6.9",
|
||||
"cheerio": "^1.2.0",
|
||||
@@ -41,14 +43,12 @@
|
||||
"fast-glob": "^3.3.3",
|
||||
"feed": "^5.2.1",
|
||||
"html-entities": "^2.6.0",
|
||||
"lucide-react": "1.11.0",
|
||||
"next": "16.2.4",
|
||||
"next-themes": "^0.4.6",
|
||||
"pg": "^8.20.0",
|
||||
"react": "19.2.5",
|
||||
"react-activity-calendar": "^3.2.0",
|
||||
"react-compare-slider": "^4.0.0",
|
||||
"react-countup": "^6.5.3",
|
||||
"react-dom": "19.2.5",
|
||||
"react-lite-youtube-embed": "~3.5.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
@@ -72,12 +72,12 @@
|
||||
"remark-smartypants": "^3.0.2",
|
||||
"remark-strip-mdx-imports-exports": "^1.0.1",
|
||||
"server-only": "0.0.1",
|
||||
"shadcn": "^4.5.0",
|
||||
"shadcn": "^4.6.0",
|
||||
"shiki": "^4.0.2",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"unified": "^11.0.5",
|
||||
"zod": "^4.3.6"
|
||||
"zod": "^4.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.2.4",
|
||||
@@ -90,9 +90,9 @@
|
||||
"babel-plugin-react-compiler": "1.0.0",
|
||||
"dotenv": "^17.4.2",
|
||||
"drizzle-kit": "^0.31.10",
|
||||
"oxfmt": "^0.46.0",
|
||||
"oxlint": "^1.61.0",
|
||||
"postcss": "^8.5.10",
|
||||
"oxfmt": "^0.47.0",
|
||||
"oxlint": "^1.62.0",
|
||||
"postcss": "^8.5.12",
|
||||
"schema-dts": "^2.0.0",
|
||||
"tailwindcss": "^4.2.4",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
|
||||
Generated
+324
-325
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -6,5 +6,5 @@
|
||||
"prHourlyLimit": 0,
|
||||
"rangeStrategy": "bump",
|
||||
"postUpdateOptions": ["pnpmDedupe"],
|
||||
"ignoreDeps": ["@types/node", "lucide-react"]
|
||||
"ignoreDeps": ["@types/node"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user