mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2025-09-13 07:10:51 +02:00
* feat: enhance github stars button to be better looking and more compact to make mobile compatibility easier in the future * feat: introduce a new Button component
207 lines
6.1 KiB
TypeScript
207 lines
6.1 KiB
TypeScript
"use client";
|
|
|
|
import type { HTMLMotionProps } from "motion/react";
|
|
|
|
import { motion } from "motion/react";
|
|
import * as React from "react";
|
|
|
|
import type { SlidingNumberProps } from "@/components/animate-ui/primitives/texts/sliding-number";
|
|
import type { ParticlesEffectProps } from "@/components/animate-ui/primitives/effects/particles";
|
|
import type { WithAsChild } from "@/components/animate-ui/primitives/animate/slot";
|
|
import type { UseIsInViewOptions } from "@/hooks/use-is-in-view";
|
|
|
|
import { Particles, ParticlesEffect } from "@/components/animate-ui/primitives/effects/particles";
|
|
import { SlidingNumber } from "@/components/animate-ui/primitives/texts/sliding-number";
|
|
import { Slot } from "@/components/animate-ui/primitives/animate/slot";
|
|
import { getStrictContext } from "@/lib/get-strict-context";
|
|
import { useIsInView } from "@/hooks/use-is-in-view";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
type GithubStarsContextType = {
|
|
stars: number;
|
|
setStars: (stars: number) => void;
|
|
currentStars: number;
|
|
setCurrentStars: (stars: number) => void;
|
|
isCompleted: boolean;
|
|
isLoading: boolean;
|
|
};
|
|
|
|
const [GithubStarsProvider, useGithubStars] = getStrictContext<GithubStarsContextType>("GithubStarsContext");
|
|
|
|
type GithubStarsProps = WithAsChild<
|
|
{
|
|
children: React.ReactNode;
|
|
username?: string;
|
|
repo?: string;
|
|
value?: number;
|
|
delay?: number;
|
|
} & UseIsInViewOptions
|
|
& HTMLMotionProps<"div">
|
|
>;
|
|
|
|
function GithubStars({
|
|
ref,
|
|
children,
|
|
username,
|
|
repo,
|
|
value,
|
|
delay = 0,
|
|
inView = false,
|
|
inViewMargin = "0px",
|
|
inViewOnce = true,
|
|
asChild = false,
|
|
...props
|
|
}: GithubStarsProps) {
|
|
const { ref: localRef, isInView } = useIsInView(ref as React.Ref<HTMLDivElement>, {
|
|
inView,
|
|
inViewOnce,
|
|
inViewMargin,
|
|
});
|
|
|
|
const [stars, setStars] = React.useState(value ?? 0);
|
|
const [currentStars, setCurrentStars] = React.useState(0);
|
|
const [isLoading, setIsLoading] = React.useState(true);
|
|
const isCompleted = React.useMemo(() => currentStars === stars, [currentStars, stars]);
|
|
|
|
const Component = asChild ? Slot : motion.div;
|
|
|
|
React.useEffect(() => {
|
|
if (value !== undefined && username && repo)
|
|
return;
|
|
if (!isInView) {
|
|
setStars(0);
|
|
setIsLoading(true);
|
|
return;
|
|
}
|
|
|
|
const timeout = setTimeout(() => {
|
|
fetch(`https://api.github.com/repos/${username}/${repo}`)
|
|
.then(response => response.json())
|
|
.then((data) => {
|
|
if (data && typeof data.stargazers_count === "number") {
|
|
setStars(data.stargazers_count);
|
|
}
|
|
})
|
|
.catch(console.error)
|
|
.finally(() => setIsLoading(false));
|
|
}, delay);
|
|
|
|
return () => clearTimeout(timeout);
|
|
}, [username, repo, value, isInView, delay]);
|
|
|
|
return (
|
|
<GithubStarsProvider
|
|
value={{
|
|
stars,
|
|
currentStars,
|
|
isCompleted,
|
|
isLoading,
|
|
setStars,
|
|
setCurrentStars,
|
|
}}
|
|
>
|
|
{!isLoading && (
|
|
<Component ref={localRef} {...props}>
|
|
{children}
|
|
</Component>
|
|
)}
|
|
</GithubStarsProvider>
|
|
);
|
|
}
|
|
|
|
type GithubStarsNumberProps = Omit<SlidingNumberProps, "number" | "fromNumber">;
|
|
|
|
function GithubStarsNumber({ padStart = true, ...props }: GithubStarsNumberProps) {
|
|
const { stars, setCurrentStars } = useGithubStars();
|
|
|
|
return (
|
|
<SlidingNumber number={stars} fromNumber={0} onNumberChange={setCurrentStars} padStart={padStart} {...props} />
|
|
);
|
|
}
|
|
|
|
type GithubStarsIconProps<T extends React.ElementType> = {
|
|
icon: React.ReactElement<T>;
|
|
color?: string;
|
|
activeClassName?: string;
|
|
} & React.ComponentProps<T>;
|
|
|
|
function GithubStarsIcon<T extends React.ElementType>({
|
|
icon: Icon,
|
|
color = "currentColor",
|
|
activeClassName,
|
|
className,
|
|
...props
|
|
}: GithubStarsIconProps<T>) {
|
|
const { stars, currentStars, isCompleted } = useGithubStars();
|
|
const fillPercentage = (currentStars / stars) * 100;
|
|
|
|
return (
|
|
<div style={{ position: "relative" }}>
|
|
<Icon aria-hidden="true" className={cn(className)} {...props} />
|
|
<Icon
|
|
aria-hidden="true"
|
|
style={{
|
|
position: "absolute",
|
|
top: 0,
|
|
left: 0,
|
|
fill: color,
|
|
stroke: color,
|
|
clipPath: `inset(${100 - (isCompleted ? fillPercentage : fillPercentage - 10)}% 0 0 0)`,
|
|
}}
|
|
className={cn(className, activeClassName)}
|
|
{...props}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
type GithubStarsParticlesProps = ParticlesEffectProps & {
|
|
children: React.ReactElement;
|
|
size?: number;
|
|
};
|
|
|
|
function GithubStarsParticles({ children, size = 4, style, ...props }: GithubStarsParticlesProps) {
|
|
const { isCompleted } = useGithubStars();
|
|
|
|
return (
|
|
<Particles animate={isCompleted}>
|
|
{children}
|
|
<ParticlesEffect
|
|
style={{
|
|
backgroundColor: "currentcolor",
|
|
borderRadius: "50%",
|
|
width: size,
|
|
height: size,
|
|
...style,
|
|
}}
|
|
{...props}
|
|
/>
|
|
</Particles>
|
|
);
|
|
}
|
|
|
|
type GithubStarsLogoProps = React.SVGProps<SVGSVGElement>;
|
|
|
|
function GithubStarsLogo(props: GithubStarsLogoProps) {
|
|
return (
|
|
<svg role="img" viewBox="0 0 24 24" fill="currentColor" aria-label="GitHub" {...props}>
|
|
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
export {
|
|
GithubStars,
|
|
type GithubStarsContextType,
|
|
GithubStarsIcon,
|
|
type GithubStarsIconProps,
|
|
GithubStarsLogo,
|
|
type GithubStarsLogoProps,
|
|
GithubStarsNumber,
|
|
type GithubStarsNumberProps,
|
|
GithubStarsParticles,
|
|
type GithubStarsParticlesProps,
|
|
type GithubStarsProps,
|
|
useGithubStars,
|
|
};
|