This commit is contained in:
parent
9905673b1e
commit
a1dfa6b6fa
Binary file not shown.
@ -13,10 +13,13 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/react": "^2.1.3",
|
"@heroicons/react": "^2.1.3",
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
|
"@radix-ui/react-context-menu": "^2.1.5",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
|
"@radix-ui/react-toast": "^1.1.5",
|
||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"clipboard-copy": "^4.0.1",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
"lucide-react": "^0.370.0",
|
"lucide-react": "^0.370.0",
|
||||||
"next": "14.2.1",
|
"next": "14.2.1",
|
||||||
|
@ -59,7 +59,7 @@ const PlayerPage = async ({ params }: PageProps): Promise<ReactElement> => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Player Result */}
|
{/* Player Result */}
|
||||||
{result && <PlayerResult player={result} />}
|
{result && <PlayerResult query={query} player={result} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@ -92,11 +92,13 @@ export const generateMetadata = async ({
|
|||||||
if (code === 400) {
|
if (code === 400) {
|
||||||
return Embed({
|
return Embed({
|
||||||
title: "Invalid Player",
|
title: "Invalid Player",
|
||||||
|
color: "#EB4034",
|
||||||
description: "The player you searched for is invalid.",
|
description: "The player you searched for is invalid.",
|
||||||
});
|
});
|
||||||
} else if (code === 404) {
|
} else if (code === 404) {
|
||||||
return Embed({
|
return Embed({
|
||||||
title: "Player Not Found",
|
title: "Player Not Found",
|
||||||
|
color: "#EB4034",
|
||||||
description: "The player you searched for was not found.",
|
description: "The player you searched for was not found.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
28
Frontend/src/app/components/copy-button.tsx
Normal file
28
Frontend/src/app/components/copy-button.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { ReactElement, ReactNode } from "react";
|
||||||
|
import copy from "clipboard-copy";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Props for the copy button.
|
||||||
|
*/
|
||||||
|
type CopyButtonProps = {
|
||||||
|
/**
|
||||||
|
* The content to copy when
|
||||||
|
* this component is clicked.
|
||||||
|
*/
|
||||||
|
content: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The children to render in this button.
|
||||||
|
*/
|
||||||
|
children: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A component that copies
|
||||||
|
*/
|
||||||
|
const CopyButton = ({ content, children }: CopyButtonProps): ReactElement => (
|
||||||
|
<button onClick={async () => await copy(content)}>{children}</button>
|
||||||
|
);
|
||||||
|
export default CopyButton;
|
@ -9,6 +9,12 @@ type EmbedProps = {
|
|||||||
*/
|
*/
|
||||||
title: string;
|
title: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The color of this embed, undefined
|
||||||
|
* for no custom color.
|
||||||
|
*/
|
||||||
|
color?: string | undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The description of the embed.
|
* The description of the embed.
|
||||||
*/
|
*/
|
||||||
@ -28,6 +34,7 @@ type EmbedProps = {
|
|||||||
*/
|
*/
|
||||||
const Embed = ({
|
const Embed = ({
|
||||||
title,
|
title,
|
||||||
|
color,
|
||||||
description,
|
description,
|
||||||
thumbnail = "",
|
thumbnail = "",
|
||||||
}: EmbedProps): Metadata => {
|
}: EmbedProps): Metadata => {
|
||||||
@ -45,6 +52,7 @@ const Embed = ({
|
|||||||
twitter: {
|
twitter: {
|
||||||
card: "summary",
|
card: "summary",
|
||||||
},
|
},
|
||||||
|
themeColor: color,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export default Embed;
|
export default Embed;
|
||||||
|
@ -8,7 +8,7 @@ import { ReactElement } from "react";
|
|||||||
*/
|
*/
|
||||||
const StatisticCounters = (): ReactElement => (
|
const StatisticCounters = (): ReactElement => (
|
||||||
<div className="py-56 flex justify-center items-center">
|
<div className="py-56 flex justify-center items-center">
|
||||||
<div className="grid grid-flow-row grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-24">
|
<div className="grid grid-flow-row grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-24 pointer-events-none">
|
||||||
<Counter name="Testing" amount={1_000_000} />
|
<Counter name="Testing" amount={1_000_000} />
|
||||||
<Counter name="Testing" amount={1_000_000} />
|
<Counter name="Testing" amount={1_000_000} />
|
||||||
<Counter name="Testing" amount={1_000_000} />
|
<Counter name="Testing" amount={1_000_000} />
|
||||||
|
@ -4,6 +4,14 @@ import { CachedPlayer, SkinPart } from "restfulmc-lib";
|
|||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import config from "@/config";
|
import config from "@/config";
|
||||||
|
import {
|
||||||
|
ContextMenu,
|
||||||
|
ContextMenuContent,
|
||||||
|
ContextMenuItem,
|
||||||
|
ContextMenuTrigger,
|
||||||
|
} from "@/components/ui/context-menu";
|
||||||
|
import CopyButton from "@/components/copy-button";
|
||||||
|
import * as querystring from "node:querystring";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The result of a player search.
|
* The result of a player search.
|
||||||
@ -12,6 +20,7 @@ import config from "@/config";
|
|||||||
* @returns the player result jsx
|
* @returns the player result jsx
|
||||||
*/
|
*/
|
||||||
const PlayerResult = ({
|
const PlayerResult = ({
|
||||||
|
query,
|
||||||
player: {
|
player: {
|
||||||
uniqueId,
|
uniqueId,
|
||||||
username,
|
username,
|
||||||
@ -19,77 +28,99 @@ const PlayerResult = ({
|
|||||||
legacy,
|
legacy,
|
||||||
},
|
},
|
||||||
}: {
|
}: {
|
||||||
|
query: string;
|
||||||
player: CachedPlayer;
|
player: CachedPlayer;
|
||||||
}): ReactElement => (
|
}): ReactElement => (
|
||||||
<div className="relative px-2 py-3 flex flex-col items-center bg-muted rounded-xl">
|
<ContextMenu>
|
||||||
{/* Raw Json */}
|
<ContextMenuTrigger>
|
||||||
<div className="absolute top-[7.25rem] right-3">
|
<div className="relative px-2 py-3 flex flex-col items-center bg-muted rounded-xl">
|
||||||
<Link
|
{/* Raw Json */}
|
||||||
href={`${config.apiEndpoint}/player/${username}`}
|
<div className="absolute top-[7.25rem] right-5">
|
||||||
target="_blank"
|
<Link
|
||||||
>
|
href={`${config.apiEndpoint}/player/${username}`}
|
||||||
<Badge className="bg-minecraft-green-2 hover:bg-minecraft-green-2 hover:opacity-85 text-white font-semibold uppercase transition-all transform-gpu">
|
target="_blank"
|
||||||
Raw Json
|
>
|
||||||
</Badge>
|
<Badge className="bg-minecraft-green-2 hover:bg-minecraft-green-2 hover:opacity-85 text-white font-semibold uppercase transition-all transform-gpu">
|
||||||
</Link>
|
Raw Json
|
||||||
</div>
|
</Badge>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="w-full flex flex-col gap-3 justify-center items-center divide-y divide-zinc-700">
|
<div className="w-full flex flex-col gap-3 justify-center items-center divide-y divide-zinc-700">
|
||||||
{/* Details */}
|
{/* Details */}
|
||||||
<div className="flex gap-5 items-center">
|
<div className="flex gap-5 items-center">
|
||||||
{/* Player Head */}
|
{/* Player Head */}
|
||||||
<Image
|
<Image
|
||||||
className="w-24 h-24 sm:w-28 sm:h-28 md:w-32 md:h-32"
|
className="w-24 h-24 sm:w-28 sm:h-28 md:w-32 md:h-32"
|
||||||
src={parts.HEAD}
|
src={parts.HEAD}
|
||||||
alt={`${username}'s Head`}
|
alt={`${username}'s Head`}
|
||||||
width={128}
|
width={128}
|
||||||
height={128}
|
height={128}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Name, Unique ID, and Badges */}
|
{/* Name, Unique ID, and Badges */}
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<h1 className="text-xl font-bold text-minecraft-green-3">
|
<h1 className="text-xl font-bold text-minecraft-green-3">
|
||||||
{username}
|
{username}
|
||||||
</h1>
|
</h1>
|
||||||
<code className="text-xs xs:text-sm text-zinc-300">
|
<code className="text-xs xs:text-sm text-zinc-300">
|
||||||
{uniqueId}
|
{uniqueId}
|
||||||
</code>
|
</code>
|
||||||
|
|
||||||
{/* Legacy Badge */}
|
{/* Legacy Badge */}
|
||||||
{legacy && (
|
{legacy && (
|
||||||
<p className="text-sm font-semibold uppercase">
|
<p className="text-sm font-semibold uppercase">
|
||||||
Legacy
|
Legacy
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Skin Parts */}
|
||||||
|
<div className="pt-3 w-[90%] flex flex-col gap-3">
|
||||||
|
{/* Header */}
|
||||||
|
<h1 className="font-semibold uppercase">Skin Parts</h1>
|
||||||
|
|
||||||
|
{/* Skin Parts */}
|
||||||
|
<div className="flex gap-5">
|
||||||
|
{Object.entries(parts)
|
||||||
|
.filter(
|
||||||
|
([part]) =>
|
||||||
|
part === SkinPart.HEAD ||
|
||||||
|
part === SkinPart.FACE ||
|
||||||
|
part === SkinPart.BODY_FLAT
|
||||||
|
)
|
||||||
|
.map(([part, url], index) => (
|
||||||
|
<Link
|
||||||
|
key={index}
|
||||||
|
href={url}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="h-20 sm:h-24 md:h-28 hover:scale-[1.02] transition-all transform-gpu"
|
||||||
|
src={url}
|
||||||
|
alt={`${username}'s ${part}`}
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</ContextMenuTrigger>
|
||||||
{/* Skin Parts */}
|
<ContextMenuContent>
|
||||||
<div className="pt-3 w-[90%] flex flex-col gap-3">
|
<ContextMenuItem>
|
||||||
{/* Header */}
|
<CopyButton content={username}>Copy Player Username</CopyButton>
|
||||||
<h1 className="font-semibold uppercase">Skin Parts</h1>
|
</ContextMenuItem>
|
||||||
|
<ContextMenuItem>
|
||||||
{/* Skin Parts */}
|
<CopyButton content={uniqueId}>Copy Player UUID</CopyButton>
|
||||||
<div className="flex gap-5">
|
</ContextMenuItem>
|
||||||
{Object.entries(parts)
|
<ContextMenuItem>
|
||||||
.filter(
|
<CopyButton content={`${config.siteUrl}/player/${query}`}>
|
||||||
([part]) =>
|
Copy Share URL
|
||||||
part === SkinPart.HEAD ||
|
</CopyButton>
|
||||||
part === SkinPart.FACE ||
|
</ContextMenuItem>
|
||||||
part === SkinPart.BODY_FLAT
|
</ContextMenuContent>
|
||||||
)
|
</ContextMenu>
|
||||||
.map(([part, url], index) => (
|
|
||||||
<Link key={index} href={url} target="_blank">
|
|
||||||
<img
|
|
||||||
className="h-20 sm:h-24 md:h-28 hover:scale-[1.02] transition-all transform-gpu"
|
|
||||||
src={url}
|
|
||||||
alt={`${username}'s ${part}`}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
export default PlayerResult;
|
export default PlayerResult;
|
||||||
|
200
Frontend/src/app/components/ui/context-menu.tsx
Normal file
200
Frontend/src/app/components/ui/context-menu.tsx
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
|
||||||
|
import { Check, ChevronRight, Circle } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/app/lib/utils"
|
||||||
|
|
||||||
|
const ContextMenu = ContextMenuPrimitive.Root
|
||||||
|
|
||||||
|
const ContextMenuTrigger = ContextMenuPrimitive.Trigger
|
||||||
|
|
||||||
|
const ContextMenuGroup = ContextMenuPrimitive.Group
|
||||||
|
|
||||||
|
const ContextMenuPortal = ContextMenuPrimitive.Portal
|
||||||
|
|
||||||
|
const ContextMenuSub = ContextMenuPrimitive.Sub
|
||||||
|
|
||||||
|
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
|
||||||
|
|
||||||
|
const ContextMenuSubTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, children, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.SubTrigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<ChevronRight className="ml-auto h-4 w-4" />
|
||||||
|
</ContextMenuPrimitive.SubTrigger>
|
||||||
|
))
|
||||||
|
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
|
||||||
|
|
||||||
|
const ContextMenuSubContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.SubContent
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
|
||||||
|
|
||||||
|
const ContextMenuContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.Portal>
|
||||||
|
<ContextMenuPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</ContextMenuPrimitive.Portal>
|
||||||
|
))
|
||||||
|
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const ContextMenuItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const ContextMenuCheckboxItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
|
||||||
|
>(({ className, children, checked, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.CheckboxItem
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
checked={checked}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<ContextMenuPrimitive.ItemIndicator>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</ContextMenuPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</ContextMenuPrimitive.CheckboxItem>
|
||||||
|
))
|
||||||
|
ContextMenuCheckboxItem.displayName =
|
||||||
|
ContextMenuPrimitive.CheckboxItem.displayName
|
||||||
|
|
||||||
|
const ContextMenuRadioItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.RadioItem
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<ContextMenuPrimitive.ItemIndicator>
|
||||||
|
<Circle className="h-2 w-2 fill-current" />
|
||||||
|
</ContextMenuPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</ContextMenuPrimitive.RadioItem>
|
||||||
|
))
|
||||||
|
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
|
||||||
|
|
||||||
|
const ContextMenuLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
|
||||||
|
inset?: boolean
|
||||||
|
}
|
||||||
|
>(({ className, inset, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"px-2 py-1.5 text-sm font-semibold text-foreground",
|
||||||
|
inset && "pl-8",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
|
||||||
|
|
||||||
|
const ContextMenuSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ContextMenuPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
const ContextMenuShortcut = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ContextMenuShortcut.displayName = "ContextMenuShortcut"
|
||||||
|
|
||||||
|
export {
|
||||||
|
ContextMenu,
|
||||||
|
ContextMenuTrigger,
|
||||||
|
ContextMenuContent,
|
||||||
|
ContextMenuItem,
|
||||||
|
ContextMenuCheckboxItem,
|
||||||
|
ContextMenuRadioItem,
|
||||||
|
ContextMenuLabel,
|
||||||
|
ContextMenuSeparator,
|
||||||
|
ContextMenuShortcut,
|
||||||
|
ContextMenuGroup,
|
||||||
|
ContextMenuPortal,
|
||||||
|
ContextMenuSub,
|
||||||
|
ContextMenuSubContent,
|
||||||
|
ContextMenuSubTrigger,
|
||||||
|
ContextMenuRadioGroup,
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
import Footer from "@/components/footer";
|
|
||||||
import Navbar from "@/components/navbar";
|
import Navbar from "@/components/navbar";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import config from "@/config";
|
import config from "@/config";
|
||||||
|
@ -1,86 +1,86 @@
|
|||||||
import type { Config } from "tailwindcss";
|
import type { Config } from "tailwindcss";
|
||||||
const { fontFamily, screens } = require("tailwindcss/defaultTheme");
|
const { screens } = require("tailwindcss/defaultTheme");
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
darkMode: ["class"],
|
darkMode: ["class"],
|
||||||
content: ["./src/app/**/*.{ts,tsx}"],
|
content: ["./src/app/**/*.{ts,tsx}"],
|
||||||
theme: {
|
theme: {
|
||||||
container: {
|
container: {
|
||||||
center: true,
|
center: true,
|
||||||
padding: "2rem",
|
padding: "2rem",
|
||||||
screens: {
|
screens: {
|
||||||
"2xl": "1400px",
|
"2xl": "1400px",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
border: "hsl(var(--border))",
|
border: "hsl(var(--border))",
|
||||||
input: "hsl(var(--input))",
|
input: "hsl(var(--input))",
|
||||||
ring: "hsl(var(--ring))",
|
ring: "hsl(var(--ring))",
|
||||||
background: "hsl(var(--background))",
|
background: "hsl(var(--background))",
|
||||||
foreground: "hsl(var(--foreground))",
|
foreground: "hsl(var(--foreground))",
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: "hsl(var(--primary))",
|
DEFAULT: "hsl(var(--primary))",
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
foreground: "hsl(var(--primary-foreground))",
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
DEFAULT: "hsl(var(--secondary))",
|
DEFAULT: "hsl(var(--secondary))",
|
||||||
foreground: "hsl(var(--secondary-foreground))",
|
foreground: "hsl(var(--secondary-foreground))",
|
||||||
},
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
DEFAULT: "hsl(var(--destructive))",
|
DEFAULT: "hsl(var(--destructive))",
|
||||||
foreground: "hsl(var(--destructive-foreground))",
|
foreground: "hsl(var(--destructive-foreground))",
|
||||||
},
|
},
|
||||||
muted: {
|
muted: {
|
||||||
DEFAULT: "hsl(var(--muted))",
|
DEFAULT: "hsl(var(--muted))",
|
||||||
foreground: "hsl(var(--muted-foreground))",
|
foreground: "hsl(var(--muted-foreground))",
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: "hsl(var(--accent))",
|
DEFAULT: "hsl(var(--accent))",
|
||||||
foreground: "hsl(var(--accent-foreground))",
|
foreground: "hsl(var(--accent-foreground))",
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
DEFAULT: "hsl(var(--popover))",
|
DEFAULT: "hsl(var(--popover))",
|
||||||
foreground: "hsl(var(--popover-foreground))",
|
foreground: "hsl(var(--popover-foreground))",
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
DEFAULT: "hsl(var(--card))",
|
DEFAULT: "hsl(var(--card))",
|
||||||
foreground: "hsl(var(--card-foreground))",
|
foreground: "hsl(var(--card-foreground))",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Custom Colors
|
// Custom Colors
|
||||||
"navbar-background": "hsl(var(--navbar-background))",
|
"navbar-background": "hsl(var(--navbar-background))",
|
||||||
"minecraft-green-1": "hsl(var(--minecraft-green-1))",
|
"minecraft-green-1": "hsl(var(--minecraft-green-1))",
|
||||||
"minecraft-green-2": "hsl(var(--minecraft-green-2))",
|
"minecraft-green-2": "hsl(var(--minecraft-green-2))",
|
||||||
"minecraft-green-3": "hsl(var(--minecraft-green-3))",
|
"minecraft-green-3": "hsl(var(--minecraft-green-3))",
|
||||||
"minecraft-green-4": "hsl(var(--minecraft-green-4))",
|
"minecraft-green-4": "hsl(var(--minecraft-green-4))",
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: "var(--radius)",
|
lg: "var(--radius)",
|
||||||
md: "calc(var(--radius) - 2px)",
|
md: "calc(var(--radius) - 2px)",
|
||||||
sm: "calc(var(--radius) - 4px)",
|
sm: "calc(var(--radius) - 4px)",
|
||||||
},
|
},
|
||||||
keyframes: {
|
keyframes: {
|
||||||
"accordion-down": {
|
"accordion-down": {
|
||||||
from: { height: "0" },
|
from: { height: "0" },
|
||||||
to: { height: "var(--radix-accordion-content-height)" },
|
to: { height: "var(--radix-accordion-content-height)" },
|
||||||
},
|
},
|
||||||
"accordion-up": {
|
"accordion-up": {
|
||||||
from: { height: "var(--radix-accordion-content-height)" },
|
from: { height: "var(--radix-accordion-content-height)" },
|
||||||
to: { height: "0" },
|
to: { height: "0" },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
"accordion-up": "accordion-up 0.2s ease-out",
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
screens: {
|
screens: {
|
||||||
xs: "475px",
|
xs: "475px",
|
||||||
...screens,
|
...screens,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate")],
|
plugins: [require("tailwindcss-animate")],
|
||||||
} satisfies Config;
|
} satisfies Config;
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
Loading…
Reference in New Issue
Block a user