feat : create share function item with rareity bg
This commit is contained in:
@@ -10,6 +10,7 @@ import { toast } from "sonner";
|
||||
|
||||
import { createStructuredGuide } from "@/app/admin/actions";
|
||||
import { MediaUploader, type UploadedMedia } from "@/components/admin/media-uploader";
|
||||
import { CatalogItemImage } from "@/components/catalog-item-image";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
@@ -18,8 +19,6 @@ import { InputGroup, InputGroupAddon } from "@/components/ui/input-group";
|
||||
import { ClearableInputGroupInput as InputGroupInput } from "@/components/admin/ui/clearable-input";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { characterRarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type Character = { key: string; name: string; aliases: string[]; element: string | null; rarity: number | null; imageUrl: string };
|
||||
|
||||
@@ -86,7 +85,7 @@ export function CreateGuideFlow({ characters, existingKeys }: { characters: Char
|
||||
const disabled = existing.has(character.key);
|
||||
return (
|
||||
<Card key={character.key} className="overflow-hidden py-0">
|
||||
<div className={cn("relative aspect-square", characterRarityGradientClass(character.key, character.rarity))}><Image src={character.imageUrl} alt={character.name} fill className="object-cover" /></div>
|
||||
<CatalogItemImage item={character} kind="character" alt={character.name} className="aspect-square" imageClassName="object-cover" />
|
||||
<CardHeader className="pt-4">
|
||||
<CardTitle>{character.name}</CardTitle>
|
||||
<CardDescription>{character.element ?? "Unknown element"}</CardDescription>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { deleteGuideExtra, saveGuideExtra } from "@/app/admin/actions";
|
||||
import { SaveStatus } from "@/components/admin/save-status";
|
||||
import { DeleteConfirmationDialog } from "@/components/admin/ui/delete-confirmation-dialog";
|
||||
import { CatalogDropdown } from "@/components/admin/ui/catalog-select";
|
||||
import { CatalogItemImage } from "@/components/catalog-item-image";
|
||||
import { MediaUploader } from "@/components/admin/media-uploader";
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -26,7 +27,6 @@ import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { useManualSave } from "@/hooks/use-manual-save";
|
||||
import { adminDraftKey } from "@/lib/editor/local-draft";
|
||||
import { extraSectionSchema } from "@/lib/guides/schemas";
|
||||
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import { finalizePercentInput, normalizePercentInput } from "@/lib/guides/percent-input";
|
||||
import { formatGuideValidationIssues } from "@/lib/guides/validation-messages";
|
||||
import { normalizeExtraRichText } from "@/lib/rich-text";
|
||||
@@ -156,9 +156,7 @@ export function ExtraCatalogItemsEditor({
|
||||
<Card size="sm">
|
||||
<CardContent className="flex items-center gap-3">
|
||||
{item ? (
|
||||
<div className={cn("relative size-12 shrink-0 overflow-hidden rounded-md", rarityGradientClass(item.rarity))}>
|
||||
<Image src={item.imageUrl} alt="" fill sizes="48px" className="object-contain" />
|
||||
</div>
|
||||
<CatalogItemImage item={item} kind={reference.kind} alt="" className="size-12 shrink-0 rounded-md" sizes="48px" />
|
||||
) : null}
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate font-medium">{label}</p>
|
||||
@@ -205,7 +203,7 @@ export function ExtraCatalogItemsEditor({
|
||||
placeholder={`เลือก${labels[kind]}`}
|
||||
showSelectedImage
|
||||
rarityBackground
|
||||
characterBackground={kind === "character"}
|
||||
itemKind={kind}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { ArtifactSelect, CatalogDropdown, CharacterSelect, WeaponSelect } from "
|
||||
import { ConstellationSelect, RefinementSelect } from "@/components/admin/ui/option-select";
|
||||
import { ScrollableNav } from "@/components/public/scrollable-nav";
|
||||
import { TeamArtifactIcon } from "@/components/public/team-search";
|
||||
import { CatalogItemImage } from "@/components/catalog-item-image";
|
||||
import { SimpleTooltip } from "@/components/tooltip";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -37,7 +38,6 @@ import { adminDraftKey } from "@/lib/editor/local-draft";
|
||||
import { teamSectionSchema } from "@/lib/guides/schemas";
|
||||
import { elementBackgroundColor } from "@/lib/catalog/element";
|
||||
import { supportsWeaponType } from "@/lib/catalog/lunaris";
|
||||
import { characterRarityGradientClass, rarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import { isCustomTravelerKey } from "@/lib/catalog/travelers";
|
||||
import { toTeamDraft } from "@/lib/guides/team-draft";
|
||||
import { applyPresetFields, applyTeamFields, cloneTeamImports, filterTeamImportsByName, findInvalidTeamCatalogReference, findUnsupportedTeamWeapon, limitPresets, parseTeamPresets, parseTeamTransfer, serializeTeamTransfer, teamConstellationLabel, teamRefinementLabel, teamTransferFileName, teamPresetCookieKey, teamPresetStorageKey, type TeamApplySelection, type TeamMemberApplyField, type TeamToolMember, type TeamPreset, type TeamToolValue } from "@/lib/guides/team-tools";
|
||||
@@ -224,14 +224,20 @@ export function TeamImportPreviewCard({
|
||||
return (
|
||||
<div key={`${member.characterKey}-${index}`} className="min-w-0 text-center">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<div className={cn("relative size-16 shrink-0 overflow-hidden rounded-lg bg-muted", characterRarityGradientClass(member.characterKey, isCustomTravelerKey(member.characterKey) ? 5 : character?.rarity))}>
|
||||
{character ? <Image src={character.imageUrl} alt={character.name} fill sizes="64px" className="object-contain" /> : null}
|
||||
</div>
|
||||
<CatalogItemImage
|
||||
item={{
|
||||
key: member.characterKey,
|
||||
imageUrl: character?.imageUrl ?? null,
|
||||
rarity: isCustomTravelerKey(member.characterKey) ? 5 : character?.rarity,
|
||||
}}
|
||||
kind="character"
|
||||
alt={character?.name ?? ""}
|
||||
className="size-16 shrink-0 rounded-lg bg-muted"
|
||||
sizes="64px"
|
||||
/>
|
||||
<div className="flex flex-col gap-1">
|
||||
{weapon ? (
|
||||
<div className={cn("relative size-7 overflow-hidden rounded-md", rarityGradientClass(weapon.rarity))}>
|
||||
<Image src={weapon.imageUrl} alt={weapon.name} fill sizes="28px" className="object-contain p-0.5" />
|
||||
</div>
|
||||
<CatalogItemImage item={weapon} kind="weapon" alt={weapon.name} className="size-7 rounded-md" imageClassName="p-0.5" sizes="28px" />
|
||||
) : null}
|
||||
{artifact || secondArtifact ? (
|
||||
<TeamArtifactIcon
|
||||
@@ -1545,7 +1551,7 @@ export function TeamEditor({
|
||||
label="เลือกตัวละครที่อยู่ใน Team"
|
||||
showSelectedImage
|
||||
rarityBackground
|
||||
characterBackground
|
||||
itemKind="character"
|
||||
/>
|
||||
</Field>
|
||||
<Field orientation="horizontal">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import Image from "@/components/ui/resilient-image";
|
||||
import { GripVerticalIcon, PlusIcon, Trash2Icon } from "lucide-react";
|
||||
import { useState, type ReactNode } from "react";
|
||||
import { closestCenter, DndContext, KeyboardSensor, PointerSensor, useSensor, useSensors, type DragEndEvent } from "@dnd-kit/core";
|
||||
@@ -10,6 +9,7 @@ import { saveWeaponSection } from "@/app/admin/actions";
|
||||
import { SaveStatus } from "@/components/admin/save-status";
|
||||
import { DeleteConfirmationDialog } from "@/components/admin/ui/delete-confirmation-dialog";
|
||||
import { WeaponSelect } from "@/components/admin/ui/catalog-select";
|
||||
import { CatalogItemImage } from "@/components/catalog-item-image";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Field, FieldGroup, FieldLabel } from "@/components/ui/field";
|
||||
@@ -20,7 +20,6 @@ import { useManualSave } from "@/hooks/use-manual-save";
|
||||
import { adminDraftKey } from "@/lib/editor/local-draft";
|
||||
import { weaponSectionSchema } from "@/lib/guides/schemas";
|
||||
import { supportsWeaponType } from "@/lib/catalog/lunaris";
|
||||
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import { finalizePercentInput, normalizePercentInput } from "@/lib/guides/percent-input";
|
||||
import { formatGuideValidationIssues } from "@/lib/guides/validation-messages";
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -255,20 +254,7 @@ export function WeaponEditor({ guide, weapons, characters, initial }: EditorBase
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-3">
|
||||
{selectedWeapon ? (
|
||||
<div
|
||||
className={cn(
|
||||
"relative size-10 shrink-0 overflow-hidden rounded-lg",
|
||||
rarityGradientClass(selectedWeapon.rarity),
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
src={selectedWeapon.imageUrl}
|
||||
alt=""
|
||||
fill
|
||||
sizes="40px"
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
<CatalogItemImage item={selectedWeapon} kind="weapon" alt="" className="size-10 shrink-0 rounded-lg" sizes="40px" />
|
||||
) : null}
|
||||
<span>อันดับ {rowIndex + 1}</span>
|
||||
</CardTitle>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import { searchItems } from "@/lib/search";
|
||||
|
||||
import Image from "@/components/ui/resilient-image";
|
||||
import { SearchIcon, Trash2Icon } from "lucide-react";
|
||||
import { useMemo, useState, useTransition } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
@@ -18,8 +17,8 @@ import { ClearableInput as Input } from "@/components/admin/ui/clearable-input";
|
||||
import { Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, PaginationNext, PaginationPrevious } from "@/components/ui/pagination";
|
||||
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Spinner } from "@/components/ui/spinner";
|
||||
import { CatalogItemImage } from "@/components/catalog-item-image";
|
||||
import type { GlossaryKind } from "@/lib/glossary";
|
||||
import { characterRarityGradientClass, rarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type CatalogItem = { key: string; name: string; imageUrl: string; rarity?: number | null };
|
||||
@@ -113,7 +112,7 @@ export function GlossaryManager({
|
||||
onValueChange={(key) => setItem(catalogs[kind].find((value) => value.key === key) ?? null)}
|
||||
placeholder="ค้นหารายการ…"
|
||||
rarityBackground
|
||||
characterBackground={kind === "character"}
|
||||
itemKind={kind}
|
||||
/>
|
||||
</Field>
|
||||
<Field>
|
||||
@@ -153,12 +152,7 @@ export function GlossaryManager({
|
||||
return (
|
||||
<div key={entry.id} className="flex items-center gap-3 rounded-lg border p-3">
|
||||
{catalogItem ? (
|
||||
<span className={cn(
|
||||
"relative size-10 shrink-0 overflow-hidden rounded-md bg-muted",
|
||||
entry.kind === "character" ? characterRarityGradientClass(catalogItem.key, catalogItem.rarity) : rarityGradientClass(catalogItem.rarity),
|
||||
)}>
|
||||
<Image src={catalogItem.imageUrl} alt="" fill sizes="40px" className="object-contain" />
|
||||
</span>
|
||||
<CatalogItemImage item={catalogItem} kind={entry.kind as GlossaryKind} alt="" className="size-10 shrink-0 rounded-md bg-muted" sizes="40px" />
|
||||
) : null}
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="font-medium">{entry.alias}</p>
|
||||
|
||||
@@ -221,7 +221,7 @@ function ItemPicker({
|
||||
placeholder={`Search ${kind}…`}
|
||||
emptyLabel="No matches"
|
||||
rarityBackground
|
||||
characterBackground={kind === "character"}
|
||||
itemKind={kind}
|
||||
/>
|
||||
</Field>
|
||||
) : null}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import Image from "@/components/ui/resilient-image";
|
||||
import { CircleHelpIcon } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { CatalogItemImage, type CatalogItemKind } from "@/components/catalog-item-image";
|
||||
import { TeamArtifactIcon } from "@/components/public/team-search";
|
||||
import {
|
||||
ComboboxContent,
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
ComboboxTrigger,
|
||||
FuzzyCombobox,
|
||||
} from "@/components/ui/combobox";
|
||||
import { characterRarityGradientClass, rarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export interface AdminCatalogItem {
|
||||
@@ -51,7 +50,7 @@ type CommonSelectProps<Item extends AdminCatalogItem> = {
|
||||
emptyLabel?: string;
|
||||
invalid?: boolean;
|
||||
rarityBackground?: boolean;
|
||||
characterBackground?: boolean;
|
||||
itemKind?: CatalogItemKind;
|
||||
searchNames?: (item: Item) => string[];
|
||||
};
|
||||
|
||||
@@ -70,8 +69,8 @@ export function CatalogDropdown<Item extends AdminCatalogItem>({
|
||||
placeholder,
|
||||
emptyLabel = "ไม่พบรายการ",
|
||||
invalid = false,
|
||||
rarityBackground = false,
|
||||
characterBackground = false,
|
||||
rarityBackground = true,
|
||||
itemKind = "weapon",
|
||||
showSelectedImage = false,
|
||||
showClear = true,
|
||||
label,
|
||||
@@ -82,14 +81,14 @@ export function CatalogDropdown<Item extends AdminCatalogItem>({
|
||||
return (
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
{showSelectedImage && selected ? (
|
||||
<div
|
||||
className={cn(
|
||||
"relative size-10 shrink-0 overflow-hidden rounded-md",
|
||||
rarityBackground && (characterBackground ? characterRarityGradientClass(selected.key, selected.rarity) : rarityGradientClass(selected.rarity)),
|
||||
)}
|
||||
>
|
||||
<Image src={selected.imageUrl} alt="" fill sizes="40px" className="object-contain" />
|
||||
</div>
|
||||
<CatalogItemImage
|
||||
item={selected}
|
||||
kind={itemKind}
|
||||
alt=""
|
||||
rarityBackground={rarityBackground}
|
||||
className="size-10 shrink-0 rounded-md"
|
||||
sizes="40px"
|
||||
/>
|
||||
) : null}
|
||||
<div className="min-w-0 flex-1">
|
||||
<FuzzyCombobox
|
||||
@@ -113,7 +112,7 @@ export function CatalogDropdown<Item extends AdminCatalogItem>({
|
||||
<ComboboxList>
|
||||
{(item) => (
|
||||
<ComboboxItem key={item.key} value={item}>
|
||||
<CatalogOptionImage item={item} rarityBackground={rarityBackground} characterBackground={characterBackground} />
|
||||
<CatalogOptionImage item={item} itemKind={itemKind} rarityBackground={rarityBackground} />
|
||||
<span className="whitespace-nowrap">{item.name}</span>
|
||||
</ComboboxItem>
|
||||
)}
|
||||
@@ -142,7 +141,7 @@ function CatalogSlotSelect<Item extends AdminCatalogItem>({
|
||||
emptyLabel = "ไม่พบรายการ",
|
||||
invalid = false,
|
||||
rarityBackground = true,
|
||||
characterBackground = false,
|
||||
itemKind = "weapon",
|
||||
searchNames = (item) => [item.name, ...(item.aliases ?? [])],
|
||||
className,
|
||||
placeholderIcon,
|
||||
@@ -164,12 +163,19 @@ function CatalogSlotSelect<Item extends AdminCatalogItem>({
|
||||
aria-invalid={invalid}
|
||||
className={cn(
|
||||
"relative flex aspect-square w-full items-center justify-center overflow-hidden rounded-md border bg-muted p-0 outline-none focus-visible:ring-2 focus-visible:ring-ring aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 [&>svg]:hidden",
|
||||
rarityBackground && selected && (characterBackground ? characterRarityGradientClass(selected.key, selected.rarity) : rarityGradientClass(selected.rarity)),
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{selected ? (
|
||||
<Image src={selected.imageUrl} alt="" fill sizes="96px" className="object-contain p-0.5" />
|
||||
<CatalogItemImage
|
||||
item={selected}
|
||||
kind={itemKind}
|
||||
alt=""
|
||||
rarityBackground={rarityBackground}
|
||||
className="absolute inset-0"
|
||||
imageClassName="p-0.5"
|
||||
sizes="96px"
|
||||
/>
|
||||
) : (
|
||||
<span className="flex size-full items-center justify-center text-muted-foreground">
|
||||
{placeholderIcon ?? <CircleHelpIcon className="size-8" aria-hidden="true" />}
|
||||
@@ -182,7 +188,7 @@ function CatalogSlotSelect<Item extends AdminCatalogItem>({
|
||||
<ComboboxList>
|
||||
{(item) => (
|
||||
<ComboboxItem key={item.key} value={item}>
|
||||
<CatalogOptionImage item={item} rarityBackground={rarityBackground} characterBackground={characterBackground} />
|
||||
<CatalogOptionImage item={item} itemKind={itemKind} rarityBackground={rarityBackground} />
|
||||
<span className="whitespace-nowrap">{item.name}</span>
|
||||
</ComboboxItem>
|
||||
)}
|
||||
@@ -210,6 +216,7 @@ function DomainCatalogSelect<Item extends AdminCatalogItem>({
|
||||
showSelectedImage,
|
||||
placeholderIcon,
|
||||
rarityBackground = true,
|
||||
itemKind = "weapon",
|
||||
...props
|
||||
}: DomainSelectProps<Item>) {
|
||||
if (presentation === "slot") {
|
||||
@@ -217,21 +224,22 @@ function DomainCatalogSelect<Item extends AdminCatalogItem>({
|
||||
<CatalogSlotSelect
|
||||
{...props}
|
||||
rarityBackground={rarityBackground}
|
||||
itemKind={itemKind}
|
||||
label={label ?? props.placeholder}
|
||||
className={className}
|
||||
placeholderIcon={placeholderIcon}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <CatalogDropdown {...props} label={label} rarityBackground={rarityBackground} showSelectedImage={showSelectedImage} />;
|
||||
return <CatalogDropdown {...props} label={label} rarityBackground={rarityBackground} itemKind={itemKind} showSelectedImage={showSelectedImage} />;
|
||||
}
|
||||
|
||||
export function CharacterSelect<Item extends AdminCatalogItem>(props: DomainSelectProps<Item>) {
|
||||
return <DomainCatalogSelect {...props} characterBackground />;
|
||||
return <DomainCatalogSelect {...props} itemKind="character" />;
|
||||
}
|
||||
|
||||
export function WeaponSelect<Item extends AdminCatalogItem>(props: DomainSelectProps<Item>) {
|
||||
return <DomainCatalogSelect {...props} />;
|
||||
return <DomainCatalogSelect {...props} itemKind="weapon" />;
|
||||
}
|
||||
|
||||
type SingleArtifactSelectProps<Item extends AdminCatalogItem> = DomainSelectProps<Item> & {
|
||||
@@ -240,7 +248,7 @@ type SingleArtifactSelectProps<Item extends AdminCatalogItem> = DomainSelectProp
|
||||
|
||||
type MultipleArtifactSelectProps<Item extends AdminCatalogItem> = Omit<
|
||||
CommonSelectProps<Item>,
|
||||
"rarityBackground"
|
||||
"rarityBackground" | "itemKind"
|
||||
> & {
|
||||
multiple: true;
|
||||
values: string[];
|
||||
@@ -253,7 +261,7 @@ type MultipleArtifactSelectProps<Item extends AdminCatalogItem> = Omit<
|
||||
export function ArtifactSelect<Item extends AdminCatalogItem>(
|
||||
props: SingleArtifactSelectProps<Item> | MultipleArtifactSelectProps<Item>,
|
||||
) {
|
||||
if (!props.multiple) return <DomainCatalogSelect {...props} />;
|
||||
if (!props.multiple) return <DomainCatalogSelect {...props} itemKind="artifact" />;
|
||||
|
||||
const {
|
||||
items,
|
||||
@@ -316,7 +324,7 @@ export function ArtifactSelect<Item extends AdminCatalogItem>(
|
||||
value={item}
|
||||
disabled={selected.length >= max && !selected.some((candidate) => candidate.key === item.key)}
|
||||
>
|
||||
<CatalogOptionImage item={item} rarityBackground />
|
||||
<CatalogOptionImage item={item} itemKind="artifact" rarityBackground />
|
||||
<span className="whitespace-nowrap">{item.name}</span>
|
||||
</ComboboxItem>
|
||||
)}
|
||||
@@ -328,21 +336,21 @@ export function ArtifactSelect<Item extends AdminCatalogItem>(
|
||||
|
||||
function CatalogOptionImage({
|
||||
item,
|
||||
itemKind,
|
||||
rarityBackground,
|
||||
characterBackground = false,
|
||||
}: {
|
||||
item: AdminCatalogItem;
|
||||
itemKind: CatalogItemKind;
|
||||
rarityBackground: boolean;
|
||||
characterBackground?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative size-8 shrink-0 overflow-hidden rounded-md",
|
||||
rarityBackground && (characterBackground ? characterRarityGradientClass(item.key, item.rarity) : rarityGradientClass(item.rarity)),
|
||||
)}
|
||||
>
|
||||
<Image src={item.imageUrl} alt="" fill sizes="32px" className="object-contain" />
|
||||
</div>
|
||||
<CatalogItemImage
|
||||
item={item}
|
||||
kind={itemKind}
|
||||
alt=""
|
||||
rarityBackground={rarityBackground}
|
||||
className="size-8 shrink-0 rounded-md"
|
||||
sizes="32px"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import Image from "@/components/ui/resilient-image";
|
||||
import { characterRarityGradientClass, rarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export type CatalogItemKind = "character" | "weapon" | "artifact";
|
||||
|
||||
export type CatalogItemImageData = {
|
||||
key: string;
|
||||
imageUrl: string | null;
|
||||
rarity?: number | null;
|
||||
};
|
||||
|
||||
export function CatalogItemImage({
|
||||
item,
|
||||
kind,
|
||||
alt,
|
||||
rarityBackground = true,
|
||||
className,
|
||||
imageClassName,
|
||||
sizes,
|
||||
loading,
|
||||
title,
|
||||
}: {
|
||||
item: CatalogItemImageData;
|
||||
kind: CatalogItemKind;
|
||||
alt: string;
|
||||
rarityBackground?: boolean;
|
||||
className?: string;
|
||||
imageClassName?: string;
|
||||
sizes?: string;
|
||||
loading?: "eager" | "lazy";
|
||||
title?: string;
|
||||
}) {
|
||||
const backgroundClass = rarityBackground
|
||||
? kind === "character"
|
||||
? characterRarityGradientClass(item.key, item.rarity)
|
||||
: rarityGradientClass(item.rarity)
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div title={title} className={cn("relative overflow-hidden", backgroundClass, className)}>
|
||||
{item.imageUrl ? (
|
||||
<Image
|
||||
src={item.imageUrl}
|
||||
alt={alt}
|
||||
fill
|
||||
sizes={sizes}
|
||||
loading={loading}
|
||||
className={cn("object-contain", imageClassName)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,21 +1,17 @@
|
||||
import Image from "@/components/ui/resilient-image";
|
||||
import { ArrowUpRightIcon } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { CatalogItemImage, type CatalogItemKind } from "@/components/catalog-item-image";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Kbd } from "@/components/ui/kbd";
|
||||
import { elementBackgroundColor } from "@/lib/catalog/element";
|
||||
import { characterRarityGradientClass, rarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import { hasCustomStats, type CommissionRequest } from "@/lib/commission/request";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
type Item = { key: string; name: string; imageUrl: string; rarity?: number | null; element?: string | null };
|
||||
type Catalog = { characters: Item[]; weapons: Item[]; artifacts: Item[] };
|
||||
|
||||
function ItemImage({ item, size = "small", character = false }: { item: Item; size?: "small" | "large"; character?: boolean }) {
|
||||
return <div title={item.name} className={cn("relative aspect-square min-w-0 overflow-hidden rounded-lg", character ? characterRarityGradientClass(item.key, item.rarity) : rarityGradientClass(item.rarity))}>
|
||||
<Image src={item.imageUrl} alt={item.name} fill sizes={size === "large" ? "160px" : "64px"} className="object-contain" />
|
||||
</div>;
|
||||
function ItemImage({ item, size = "small", kind = "weapon" }: { item: Item; size?: "small" | "large"; kind?: CatalogItemKind }) {
|
||||
return <CatalogItemImage item={item} kind={kind} alt={item.name} title={item.name} className="aspect-square min-w-0 rounded-lg" sizes={size === "large" ? "160px" : "64px"} />;
|
||||
}
|
||||
|
||||
function LunarisItemLink({ item, kind, children }: {
|
||||
@@ -25,7 +21,7 @@ function LunarisItemLink({ item, kind, children }: {
|
||||
target="_blank" rel="noreferrer"
|
||||
aria-label={`เปิดข้อมูล${kind === "weapon" ? "อาวุธ" : "อาร์ติแฟกต์"} ${item.name} บน Lunaris`}
|
||||
className="block min-w-0 rounded-lg outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
||||
<div className="relative"><ItemImage item={item} />
|
||||
<div className="relative"><ItemImage item={item} kind={kind} />
|
||||
<Kbd className="absolute top-0 right-0 h-3 min-w-3 bg-transparent! p-0 text-white">
|
||||
<ArrowUpRightIcon aria-hidden="true" />
|
||||
</Kbd>
|
||||
@@ -49,7 +45,7 @@ function CommissionTeamCard({ team, index, catalog }: { team: CommissionRequest[
|
||||
const artifact = artifacts.get(member.artifactKey);
|
||||
return <div key={memberIndex} className="min-w-0 rounded-lg p-1 lg:p-1.5" style={{ backgroundColor: elementBackgroundColor(character?.element) }}>
|
||||
<div className="grid grid-cols-1 gap-1 lg:grid-cols-[2fr_1fr]">
|
||||
{character ? <ItemImage item={character} size="large" character /> : <div className="aspect-square rounded-lg bg-muted" />}
|
||||
{character ? <ItemImage item={character} size="large" kind="character" /> : <div className="aspect-square rounded-lg bg-muted" />}
|
||||
<div className="grid min-w-0 grid-cols-2 gap-0.5 lg:grid-cols-1 lg:grid-rows-2 lg:gap-1">
|
||||
{weapon ? <LunarisItemLink item={weapon} kind="weapon" /> : <div title={member.weaponKey} className="aspect-square rounded-lg bg-muted" />}
|
||||
{artifact ? <LunarisItemLink item={artifact} kind="artifact" /> : <div title={member.artifactKey} className="aspect-square rounded-lg bg-muted" />}
|
||||
@@ -109,7 +105,7 @@ export function CommissionRequestSummary({ request, catalog, amountBaht }: {
|
||||
: <p key={key} className="w-16 wrap-break-word text-xs">{key}</p>;
|
||||
})}
|
||||
</div>
|
||||
{character ? <div className="w-16 min-w-0"><ItemImage item={character} size="large" character /><p className="mt-1 truncate text-center text-xs" title={character.name}>{character.name}</p></div> : null}
|
||||
{character ? <div className="w-16 min-w-0"><ItemImage item={character} size="large" kind="character" /><p className="mt-1 truncate text-center text-xs" title={character.name}>{character.name}</p></div> : null}
|
||||
{set.customStats !== undefined && <div className="col-span-2 flex flex-col gap-1">
|
||||
<Badge variant="secondary">Custom stats ของ {character?.name ?? set.characterKey}</Badge>
|
||||
<p className="whitespace-pre-wrap wrap-break-word text-sm">{set.customStats}</p>
|
||||
@@ -125,7 +121,7 @@ export function CommissionRequestSummary({ request, catalog, amountBaht }: {
|
||||
</CardHeader>
|
||||
<CardContent className="grid grid-cols-[minmax(0,1fr)_64px] items-center gap-3">
|
||||
<div className="flex min-w-0 flex-wrap gap-2">{set.levels.map((level) => <Badge key={level} variant="outline">C{level}</Badge>)}</div>
|
||||
{character ? <div className="w-16 min-w-0"><ItemImage item={character} size="large" character /><p className="mt-1 truncate text-center text-xs" title={character.name}>{character.name}</p></div> : null}
|
||||
{character ? <div className="w-16 min-w-0"><ItemImage item={character} size="large" kind="character" /><p className="mt-1 truncate text-center text-xs" title={character.name}>{character.name}</p></div> : null}
|
||||
{set.customStats !== undefined && <div className="col-span-2 flex flex-col gap-1">
|
||||
<Badge variant="secondary">Custom stats ของ {character?.name ?? set.characterKey}</Badge>
|
||||
<p className="whitespace-pre-wrap wrap-break-word text-sm">{set.customStats}</p>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ArrowUpRightIcon } from "lucide-react";
|
||||
import { Fragment, type ReactNode } from "react";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { CatalogItemImage } from "@/components/catalog-item-image";
|
||||
import { ComparisonContext } from "@/components/public/comparison-context";
|
||||
import {
|
||||
Card,
|
||||
@@ -417,16 +418,15 @@ function WeaponSectionContent({
|
||||
rarityGradientClass(row.weapon.rarity),
|
||||
)}
|
||||
>
|
||||
<div className="relative size-20 shrink-0">
|
||||
<Image
|
||||
src={row.weapon.imageUrl}
|
||||
alt={row.weapon.name}
|
||||
fill
|
||||
loading={exporting ? "eager" : undefined}
|
||||
sizes="(min-width: 768px) 5rem, 4rem"
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
<CatalogItemImage
|
||||
item={row.weapon}
|
||||
kind="weapon"
|
||||
alt={row.weapon.name}
|
||||
rarityBackground={false}
|
||||
className="size-20 shrink-0"
|
||||
loading={exporting ? "eager" : undefined}
|
||||
sizes="(min-width: 768px) 5rem, 4rem"
|
||||
/>
|
||||
<div className="flex min-w-0 flex-col gap-1">
|
||||
<p
|
||||
className={cn(
|
||||
@@ -1273,9 +1273,7 @@ function ExtraSectionContent({
|
||||
className="flex min-w-0 items-center gap-2 rounded-lg border bg-background/80 p-2 outline-none hover:bg-background focus-visible:ring-2 focus-visible:ring-ring"
|
||||
>
|
||||
{item.imageUrl ? (
|
||||
<div className={cn("relative size-12 shrink-0 overflow-hidden rounded-md", rarityGradientClass(item.rarity))}>
|
||||
<Image src={item.imageUrl} alt="" fill className="object-contain" />
|
||||
</div>
|
||||
<CatalogItemImage item={item} kind={item.kind} alt="" className="size-12 shrink-0 rounded-md" />
|
||||
) : null}
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-medium">{item.name}</p>
|
||||
|
||||
@@ -4,6 +4,7 @@ import Link from "next/link";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { CatalogItemImage } from "@/components/catalog-item-image";
|
||||
import { TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { StygianBossTabs, StygianNavigation } from "@/components/stygian/stygian-navigation";
|
||||
import {
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
StygianBossTab,
|
||||
} from "@/components/stygian/stygian-boss-display";
|
||||
import { elementIconFilter } from "@/lib/catalog/element";
|
||||
import { characterRarityGradientClass, rarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import type { getCatalogOptions } from "@/lib/guides/queries";
|
||||
import { stygianBossPositionLabels, stygianBossPositions, stygianLevels } from "@/lib/stygian/constants";
|
||||
import type { getStygianSchedule } from "@/lib/stygian/repository";
|
||||
@@ -52,12 +52,16 @@ function RecommendationIcon({ item, catalog, teamLayout = false }: { item: Item;
|
||||
return (
|
||||
<div className={cn("flex min-w-0 flex-col gap-1 rounded-lg border bg-card p-1", teamLayout && "w-full gap-2 sm:p-2")}>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className={cn("relative aspect-square overflow-hidden rounded-lg", characterRarityGradientClass(item.catalogKey, value.rarity))}>
|
||||
{value.imageUrl ? <Image src={value.imageUrl} alt={value.name} fill sizes={teamLayout ? "(min-width: 1280px) 128px, (min-width: 640px) 25vw, 50vw" : "128px"} className="object-contain" /> : null}
|
||||
</div>
|
||||
<CatalogItemImage
|
||||
item={{ key: item.catalogKey ?? "", imageUrl: value.imageUrl, rarity: value.rarity }}
|
||||
kind="character"
|
||||
alt={value.name}
|
||||
className="aspect-square rounded-lg"
|
||||
sizes={teamLayout ? "(min-width: 1280px) 128px, (min-width: 640px) 25vw, 50vw" : "128px"}
|
||||
/>
|
||||
{hasEquipment ? <div className="grid grid-cols-2 gap-1">
|
||||
{value.weapon ? <a href={`https://lunaris.moe/weapon/${encodeURIComponent(value.weapon.key)}`} target="_blank" rel="noreferrer" aria-label={`View ${value.weapon.name} on Lunaris`} title={value.weapon.name} className={cn("relative aspect-square overflow-hidden rounded-md border transition-opacity hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", rarityGradientClass(value.weapon.rarity))}><Image src={value.weapon.imageUrl} alt={value.weapon.name} fill sizes={teamLayout ? "(min-width: 1280px) 64px, (min-width: 640px) 12vw, 25vw" : "64px"} className="object-contain p-1" /></a> : null}
|
||||
{value.artifact ? <a href={`https://lunaris.moe/artifact/${encodeURIComponent(value.artifact.key)}`} target="_blank" rel="noreferrer" aria-label={`View ${value.artifact.name} on Lunaris`} title={value.artifact.name} className={cn("relative aspect-square overflow-hidden rounded-md border transition-opacity hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", rarityGradientClass(value.artifact.rarity))}><Image src={value.artifact.imageUrl} alt={value.artifact.name} fill sizes={teamLayout ? "(min-width: 1280px) 64px, (min-width: 640px) 12vw, 25vw" : "64px"} className="object-contain p-1" /></a> : null}
|
||||
{value.weapon ? <a href={`https://lunaris.moe/weapon/${encodeURIComponent(value.weapon.key)}`} target="_blank" rel="noreferrer" aria-label={`View ${value.weapon.name} on Lunaris`} title={value.weapon.name} className="relative aspect-square overflow-hidden rounded-md border transition-opacity hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"><CatalogItemImage item={value.weapon} kind="weapon" alt={value.weapon.name} className="absolute inset-0" imageClassName="p-1" sizes={teamLayout ? "(min-width: 1280px) 64px, (min-width: 640px) 12vw, 25vw" : "64px"} /></a> : null}
|
||||
{value.artifact ? <a href={`https://lunaris.moe/artifact/${encodeURIComponent(value.artifact.key)}`} target="_blank" rel="noreferrer" aria-label={`View ${value.artifact.name} on Lunaris`} title={value.artifact.name} className="relative aspect-square overflow-hidden rounded-md border transition-opacity hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"><CatalogItemImage item={value.artifact} kind="artifact" alt={value.artifact.name} className="absolute inset-0" imageClassName="p-1" sizes={teamLayout ? "(min-width: 1280px) 64px, (min-width: 640px) 12vw, 25vw" : "64px"} /></a> : null}
|
||||
</div> : null}
|
||||
</div>
|
||||
{teamLayout ? item.constellationLevel !== null || value.weapon && value.refinement ? <div className="flex justify-center gap-1">
|
||||
@@ -70,8 +74,8 @@ function RecommendationIcon({ item, catalog, teamLayout = false }: { item: Item;
|
||||
return (
|
||||
<div className="flex min-w-0 flex-col items-center gap-1 text-center">
|
||||
{item.kind === "weapon" || item.kind === "artifact" ? (
|
||||
<a href={`https://lunaris.moe/${item.kind}/${encodeURIComponent(item.catalogKey ?? "")}`} target="_blank" rel="noreferrer" aria-label={`View ${value.name} on Lunaris`} title={value.name} className={cn("relative aspect-square w-full overflow-hidden rounded-lg border transition-opacity hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", rarityGradientClass(value.rarity))}>
|
||||
{value.imageUrl ? <Image src={value.imageUrl} alt={value.name} fill sizes="100px" className="object-contain" /> : null}
|
||||
<a href={`https://lunaris.moe/${item.kind}/${encodeURIComponent(item.catalogKey ?? "")}`} target="_blank" rel="noreferrer" aria-label={`View ${value.name} on Lunaris`} title={value.name} className="relative aspect-square w-full overflow-hidden rounded-lg border transition-opacity hover:opacity-80 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring">
|
||||
<CatalogItemImage item={{ key: item.catalogKey ?? "", imageUrl: value.imageUrl, rarity: value.rarity }} kind={item.kind} alt={value.name} className="absolute inset-0" sizes="100px" />
|
||||
</a>
|
||||
) : (
|
||||
<div className="relative aspect-square w-full overflow-hidden rounded-lg border">
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
|
||||
import Image from "@/components/ui/resilient-image";
|
||||
import Link from "next/link";
|
||||
import { ArrowUpRightIcon, SearchIcon, XIcon } from "lucide-react";
|
||||
import { Fragment, useMemo, useState, type ReactNode } from "react";
|
||||
@@ -9,6 +8,7 @@ import { Fragment, useMemo, useState, type ReactNode } from "react";
|
||||
import { GuideImageExport } from "@/components/public/guide-image-export";
|
||||
import { ConditionTabIcon } from "@/components/public/condition-switcher";
|
||||
import { ExportText } from "@/components/public/export-language";
|
||||
import { CatalogItemImage } from "@/components/catalog-item-image";
|
||||
import { RotationHelp } from "@/components/public/rotation-help";
|
||||
import { ScrollableNav } from "@/components/public/scrollable-nav";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -28,7 +28,7 @@ import { Separator } from "@/components/ui/separator";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { elementBackgroundColor } from "@/lib/catalog/element";
|
||||
import { characterRarityGradientClass, rarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import { rarityGradientClass } from "@/lib/catalog/rarity";
|
||||
import { isCustomTravelerKey } from "@/lib/catalog/travelers";
|
||||
import type { PublicGuide } from "@/lib/guides/queries";
|
||||
import { teamConstellationLabel, teamRefinementLabel } from "@/lib/guides/team-tools";
|
||||
@@ -97,16 +97,16 @@ function ArtifactHalf({
|
||||
sizes: string;
|
||||
}) {
|
||||
const content = (
|
||||
<span className="relative block size-full">
|
||||
<Image
|
||||
src={artifact.imageUrl}
|
||||
alt={linked ? artifact.name : ""}
|
||||
fill
|
||||
loading={eager ? "eager" : undefined}
|
||||
sizes={sizes}
|
||||
className="object-contain p-1"
|
||||
/>
|
||||
</span>
|
||||
<CatalogItemImage
|
||||
item={artifact}
|
||||
kind="artifact"
|
||||
alt={linked ? artifact.name : ""}
|
||||
rarityBackground={false}
|
||||
className="size-full"
|
||||
imageClassName="p-1"
|
||||
loading={eager ? "eager" : undefined}
|
||||
sizes={sizes}
|
||||
/>
|
||||
);
|
||||
const className = cn(
|
||||
"absolute inset-0 overflow-hidden",
|
||||
@@ -156,13 +156,14 @@ export function TeamArtifactIcon({
|
||||
if (!secondary) {
|
||||
const content = (
|
||||
<>
|
||||
<Image
|
||||
src={primary.imageUrl}
|
||||
<CatalogItemImage
|
||||
item={primary}
|
||||
kind="artifact"
|
||||
alt={linked ? primary.name : ""}
|
||||
fill
|
||||
className="absolute inset-0"
|
||||
imageClassName="p-1"
|
||||
loading={eager ? "eager" : undefined}
|
||||
sizes={sizes}
|
||||
className="object-contain p-1"
|
||||
/>
|
||||
{linked ? (
|
||||
<Kbd className="absolute top-0 right-0 h-3 min-w-3 bg-transparent p-0 text-white">
|
||||
@@ -173,7 +174,6 @@ export function TeamArtifactIcon({
|
||||
);
|
||||
const artifactClassName = cn(
|
||||
"relative aspect-square scale-110 overflow-hidden rounded-md bg-muted",
|
||||
rarityGradientClass(primary.rarity),
|
||||
className,
|
||||
);
|
||||
|
||||
@@ -229,11 +229,9 @@ export function TeamExportImage({ team }: { team: TeamExportData }) {
|
||||
const percent = total > 0 ? damage / total * 100 : 0;
|
||||
return (
|
||||
<div key={member.position} className="flex h-43.5 items-center gap-6 rounded-3xl p-3.5" style={{ backgroundColor: elementBackgroundColor(member.character.element) }}>
|
||||
<div className={cn("relative size-35 shrink-0 overflow-hidden rounded-3xl border-[3px] border-white/20", characterRarityGradientClass(member.character.key, isCustomTravelerKey(member.character.key) ? 5 : member.character.rarity))}>
|
||||
<Image src={member.character.imageUrl} alt="" fill loading="eager" sizes="140px" className="object-contain" />
|
||||
</div>
|
||||
<CatalogItemImage item={{ ...member.character, rarity: isCustomTravelerKey(member.character.key) ? 5 : member.character.rarity }} kind="character" alt="" className="size-35 shrink-0 rounded-3xl border-[3px] border-white/20" loading="eager" sizes="140px" />
|
||||
<div className="grid w-44 shrink-0 grid-cols-2 gap-3">
|
||||
{member.weapon ? <div className={cn("relative aspect-square min-w-0 scale-110 overflow-hidden rounded-2xl", rarityGradientClass(member.weapon.rarity))}><Image src={member.weapon.imageUrl} alt="" fill loading="eager" sizes="82px" className="object-contain p-1" /></div> : null}
|
||||
{member.weapon ? <CatalogItemImage item={member.weapon} kind="weapon" alt="" className="aspect-square min-w-0 scale-110 rounded-2xl" imageClassName="p-1" loading="eager" sizes="82px" /> : null}
|
||||
<TeamArtifactIcon
|
||||
first={member.artifact}
|
||||
second={member.secondArtifact}
|
||||
@@ -313,29 +311,17 @@ function TeamCard({
|
||||
: "flex flex-col sm:grid sm:grid-cols-3",
|
||||
)}
|
||||
>
|
||||
<div
|
||||
<CatalogItemImage
|
||||
item={{ ...member.character, rarity: isCustomTravelerKey(member.character.key) ? 5 : member.character.rarity }}
|
||||
kind="character"
|
||||
alt={member.character.name}
|
||||
className={cn(
|
||||
"relative aspect-square min-h-0 overflow-hidden rounded-lg bg-muted",
|
||||
exporting
|
||||
? "col-span-2 row-span-2"
|
||||
: "sm:col-span-2 sm:row-span-2",
|
||||
characterRarityGradientClass(
|
||||
member.character.key,
|
||||
isCustomTravelerKey(member.character.key)
|
||||
? 5
|
||||
: member.character.rarity,
|
||||
),
|
||||
"aspect-square min-h-0 rounded-lg bg-muted",
|
||||
exporting ? "col-span-2 row-span-2" : "sm:col-span-2 sm:row-span-2",
|
||||
)}
|
||||
>
|
||||
<Image
|
||||
src={member.character.imageUrl}
|
||||
alt={member.character.name}
|
||||
fill
|
||||
loading={exporting ? "eager" : undefined}
|
||||
sizes="(min-width: 1280px) 184px, (min-width: 640px) 16vw, 25vw"
|
||||
className="object-contain"
|
||||
/>
|
||||
</div>
|
||||
loading={exporting ? "eager" : undefined}
|
||||
sizes="(min-width: 1280px) 184px, (min-width: 640px) 16vw, 25vw"
|
||||
/>
|
||||
<div
|
||||
className={cn(
|
||||
"min-w-0 grid grid-cols-2 gap-2",
|
||||
@@ -351,18 +337,16 @@ function TeamCard({
|
||||
rel="noreferrer"
|
||||
tabIndex={exporting ? -1 : undefined}
|
||||
aria-label={`เปิดข้อมูลอาวุธ ${member.weapon.name} บน Lunaris`}
|
||||
className={cn(
|
||||
"relative aspect-square scale-110 overflow-hidden rounded-md bg-muted outline-none sm:col-span-2",
|
||||
rarityGradientClass(member.weapon.rarity),
|
||||
)}
|
||||
className="relative aspect-square scale-110 overflow-hidden rounded-md bg-muted outline-none sm:col-span-2"
|
||||
>
|
||||
<Image
|
||||
src={member.weapon.imageUrl}
|
||||
<CatalogItemImage
|
||||
item={member.weapon}
|
||||
kind="weapon"
|
||||
alt={member.weapon.name}
|
||||
fill
|
||||
className="absolute inset-0"
|
||||
imageClassName="p-1"
|
||||
loading={exporting ? "eager" : undefined}
|
||||
sizes="(min-width: 1280px) 86px, (min-width: 640px) 8vw, 12.5vw"
|
||||
className="object-contain p-1"
|
||||
/>
|
||||
<Kbd className="absolute top-0 right-0 h-3 min-w-3 bg-transparent p-0 text-white">
|
||||
<ArrowUpRightIcon aria-hidden="true" />
|
||||
|
||||
Reference in New Issue
Block a user