mirror of
https://github.com/Xevion/xevion.dev.git
synced 2025-12-08 04:09:11 -06:00
Add global links & open icon links in new tab
This commit is contained in:
@@ -1,11 +1,12 @@
|
|||||||
import React, {useRef} from "react";
|
import React, {useRef} from "react";
|
||||||
import {useOnClickOutside, useToggle} from "usehooks-ts";
|
import {useOnClickOutside, useToggle} from "usehooks-ts";
|
||||||
import {classNames, isHoverable} from "../utils/helpers";
|
import {classNames, isHoverable, stopPropagation} from "../utils/helpers";
|
||||||
import DependentImage from "./DependentImage";
|
import DependentImage from "./DependentImage";
|
||||||
import ReactMarkdown from 'react-markdown'
|
import ReactMarkdown from 'react-markdown'
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import {LinkIcon, LinkIcons} from "../pages";
|
import {LinkIcon, LinkIcons} from "../pages";
|
||||||
|
import {useRouter} from "next/router";
|
||||||
|
|
||||||
type ItemCardProps = {
|
type ItemCardProps = {
|
||||||
banner: string;
|
banner: string;
|
||||||
@@ -13,11 +14,13 @@ type ItemCardProps = {
|
|||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
links?: LinkIcon[];
|
links?: LinkIcon[];
|
||||||
|
location: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ItemCard = ({banner, bannerBlur, title, description, links}: ItemCardProps) => {
|
const ItemCard = ({banner, bannerBlur, title, description, links, location}: ItemCardProps) => {
|
||||||
const itemRef = useRef<HTMLDivElement>(null);
|
const itemRef = useRef<HTMLDivElement>(null);
|
||||||
const [active, toggleActive, setActive] = useToggle()
|
const [active, toggleActive, setActive] = useToggle()
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
useOnClickOutside(itemRef, () => {
|
useOnClickOutside(itemRef, () => {
|
||||||
setActive(false);
|
setActive(false);
|
||||||
@@ -27,6 +30,9 @@ const ItemCard = ({banner, bannerBlur, title, description, links}: ItemCardProps
|
|||||||
className={classNames("item", active ? "active" : null)}
|
className={classNames("item", active ? "active" : null)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isHoverable()) toggleActive();
|
if (!isHoverable()) toggleActive();
|
||||||
|
else {
|
||||||
|
router.push(location);
|
||||||
|
}
|
||||||
}}>
|
}}>
|
||||||
<DependentImage fill src={banner} blurDataURL={bannerBlur}
|
<DependentImage fill src={banner} blurDataURL={bannerBlur}
|
||||||
className={(loaded) => classNames("object-cover", loaded ? null : "blur-xl")}
|
className={(loaded) => classNames("object-cover", loaded ? null : "blur-xl")}
|
||||||
@@ -34,21 +40,24 @@ const ItemCard = ({banner, bannerBlur, title, description, links}: ItemCardProps
|
|||||||
alt={`Banner for ${title}`}
|
alt={`Banner for ${title}`}
|
||||||
/>
|
/>
|
||||||
<div className="elements grid grid-cols-12 h-full m-2 px-1 sm:px-4">
|
<div className="elements grid grid-cols-12 h-full m-2 px-1 sm:px-4">
|
||||||
<div className="col-span-9 lg:col-span-8 max-h-full overflow-hidden drop-shadow-2xl pb-2 md:p-1 pl-2">
|
<Link href={{pathname: location}}
|
||||||
|
className=" col-span-9 lg:col-span-8 max-h-full overflow-hidden drop-shadow-2xl pb-2 md:p-1 pl-2">
|
||||||
|
|
||||||
<div className="text-2xl sm:text-2xl md:text-3xl font-semibold">{title}</div>
|
<div className="text-2xl sm:text-2xl md:text-3xl font-semibold">{title}</div>
|
||||||
<div className="mt-0 md:mt-1.5 text-xl sm:text-lg md:text-xl overflow-hidden ">
|
<div className="mt-0 md:mt-1.5 text-xl sm:text-lg md:text-xl overflow-hidden ">
|
||||||
<ReactMarkdown>{description}</ReactMarkdown>
|
<ReactMarkdown children={description}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
{ (links?.length ?? 0) > 0 ? <div className="col-span-3 lg:col-span-4 w-full flex justify-end max-h-full md:py-5">
|
{(links?.length ?? 0) > 0 ?
|
||||||
<div className="grid grid-cols-2 grid-rows-2 p-2 md:gap-3 aspect-square icon-grid">
|
<div className="col-span-3 lg:col-span-4 w-full flex justify-end max-h-full md:py-5">
|
||||||
{links!.map(({icon, location}) => {
|
<div className="grid grid-cols-2 grid-rows-2 p-2 md:gap-3 aspect-square icon-grid">
|
||||||
return <Link href={location} >
|
{links!.map(({icon, location, newTab}) =>
|
||||||
{LinkIcons[icon]({})}
|
<Link href={location} target={(newTab ?? true) ? "_blank" : "_self"}
|
||||||
</Link>
|
onClick={stopPropagation}>
|
||||||
})}
|
{LinkIcons[icon]!({})}
|
||||||
</div>
|
</Link>)}
|
||||||
</div> : null}
|
</div>
|
||||||
|
</div> : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,19 +14,19 @@ export type Project = {
|
|||||||
longDescription: string;
|
longDescription: string;
|
||||||
shortDescription: string;
|
shortDescription: string;
|
||||||
links?: LinkIcon[];
|
links?: LinkIcon[];
|
||||||
|
location: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LinkIconType = "github" | "external" | "link";
|
|
||||||
|
|
||||||
export const LinkIcons: Record<LinkIconType, IconType> = {
|
export const LinkIcons: Record<string, IconType> = {
|
||||||
github: AiFillGithub,
|
github: AiFillGithub,
|
||||||
external: RxOpenInNewWindow,
|
external: RxOpenInNewWindow,
|
||||||
link: AiOutlineLink
|
link: AiOutlineLink
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LinkIcon = {
|
export type LinkIcon = {
|
||||||
icon: LinkIconType;
|
icon: keyof typeof LinkIcons;
|
||||||
location: string;
|
location: string;
|
||||||
|
newTab?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectWithBlur = Project & { bannerBlur: string };
|
type ProjectWithBlur = Project & { bannerBlur: string };
|
||||||
@@ -41,6 +41,7 @@ export async function getStaticProps(context: GetStaticPropsContext) {
|
|||||||
{
|
{
|
||||||
title: "Phototag",
|
title: "Phototag",
|
||||||
banner: "/phototag.png",
|
banner: "/phototag.png",
|
||||||
|
location: "/phototag",
|
||||||
longDescription: `Using Google's Vision API and supporting metadata formats on Windows, Phototag makes it easy to quickly add rich, descriptive tags to your photos, saving you time and effort.`,
|
longDescription: `Using Google's Vision API and supporting metadata formats on Windows, Phototag makes it easy to quickly add rich, descriptive tags to your photos, saving you time and effort.`,
|
||||||
shortDescription: "Effortlessly add rich & descriptive tags to your photos with Phototag.",
|
shortDescription: "Effortlessly add rich & descriptive tags to your photos with Phototag.",
|
||||||
links: [
|
links: [
|
||||||
@@ -57,13 +58,14 @@ export async function getStaticProps(context: GetStaticPropsContext) {
|
|||||||
{
|
{
|
||||||
title: "Paths",
|
title: "Paths",
|
||||||
banner: "/paths.png",
|
banner: "/paths.png",
|
||||||
|
location: "/paths",
|
||||||
shortDescription: "Discover the power of graph traversal algorithms with my interactive application.",
|
shortDescription: "Discover the power of graph traversal algorithms with my interactive application.",
|
||||||
longDescription: `Discover the power of graph traversal algorithms with my interactive Unity application!
|
longDescription: `Discover the power of graph traversal algorithms with my interactive Unity application!
|
||||||
Easily generate and edit graphs, create mazes, and experiment with different algorithm configurations to find the most efficient path.`,
|
Easily generate and edit graphs, create mazes, and experiment with different algorithm configurations to find the most efficient path.`,
|
||||||
links: [
|
links: [
|
||||||
{
|
{
|
||||||
icon: "github",
|
icon: "github",
|
||||||
location: "https://github.com/Xevion/Paths"
|
location: "https://github.com/Xevion/Paths",
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,14 @@ export function classNames(...classes: (string | null | undefined)[]) {
|
|||||||
return classes.filter(Boolean).join(" ");
|
return classes.filter(Boolean).join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler that simply calls the `stopPropagation` method on an event.
|
||||||
|
* @param event The event to prevent propagation on.
|
||||||
|
*/
|
||||||
|
export const stopPropagation = (event: Event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
const isClient = (): boolean => {
|
const isClient = (): boolean => {
|
||||||
return typeof window !== "undefined";
|
return typeof window !== "undefined";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user