Files
Firefly_Srtools/src/components/avatarBar/index.tsx
T
Kain344 2a36904b43
Gitea Auto Deploy / Deploy-Container (push) Failing after 13m19s
Refactor TypeScript interfaces and types for improved readability and consistency
- Reformatted interfaces in changelog.ts, enka.ts, extraData.ts, global.d.ts, index.ts, lightconeDetail.ts, metaData.ts, mics.ts, mocDetail.ts, modelConfig.ts, monsterDetail.ts, peakDetail.ts, pfDetail.ts, psConnect.ts, relicDetail.ts, showcase.ts, srtools.ts, and zod/index.ts for consistent indentation and spacing.
- Added missing imports and interfaces where necessary, such as Invasion in mocDetail.ts and peakDetail.ts.
- Ensured all interfaces are properly structured and maintain a consistent style across the codebase.
2026-08-27 00:38:15 +07:00

200 lines
9.1 KiB
TypeScript

"use client"
import Image from "next/image"
import { useMemo } from "react"
import CharacterCard from "../card/characterCard"
import useLocaleStore from "@/stores/localeStore"
import { useTranslations } from "next-intl"
import useDetailDataStore from "@/stores/detailDataStore"
import useCurrentDataStore from '@/stores/currentDataStore';
import { calcRarity, getNameChar } from "@/helper"
import { useShallow } from "zustand/react/shallow";
import { Search, X } from "lucide-react"
export default function AvatarBar({ onClose }: { onClose?: () => void }) {
const {
avatarSearch,
mapAvatarElementActive,
mapAvatarPathActive,
setAvatarSearch,
setAvatarSelected,
setMapAvatarElementActive,
setMapAvatarPathActive,
setSkillIDSelected,
} = useCurrentDataStore(
useShallow((state) => ({
avatarSearch: state.avatarSearch,
mapAvatarElementActive: state.mapAvatarElementActive,
mapAvatarPathActive: state.mapAvatarPathActive,
setAvatarSearch: state.setAvatarSearch,
setAvatarSelected: state.setAvatarSelected,
setMapAvatarElementActive: state.setMapAvatarElementActive,
setMapAvatarPathActive: state.setMapAvatarPathActive,
setSkillIDSelected: state.setSkillIDSelected,
}))
)
const { mapAvatar, baseType, damageType } = useDetailDataStore(
useShallow((state) => ({
mapAvatar: state.mapAvatar,
baseType: state.baseType,
damageType: state.damageType,
}))
)
const transI18n = useTranslations("DataPage")
const { locale } = useLocaleStore(
useShallow((state) => ({ locale: state.locale }))
)
const listAvatar = useMemo(() => {
if (!mapAvatar || !locale || !transI18n) return []
let list = Object.values(mapAvatar)
if (avatarSearch) {
list = list.filter(item =>
getNameChar(locale, transI18n, item)
.toLowerCase()
.includes(avatarSearch.toLowerCase())
)
}
const allElementFalse = !Object.values(mapAvatarElementActive).some(v => v)
const allPathFalse = !Object.values(mapAvatarPathActive).some(v => v)
list = list.filter(item =>
(allElementFalse || mapAvatarElementActive[item.DamageType]) &&
(allPathFalse || mapAvatarPathActive[item.BaseType])
)
list.sort((a, b) => {
const r = calcRarity(b.Rarity) - calcRarity(a.Rarity)
if (r !== 0) return r
return b.ID - a.ID
})
return list
}, [mapAvatar, mapAvatarElementActive, mapAvatarPathActive, avatarSearch, locale, transI18n])
return (
<div className="flex flex-col h-full gap-2 p-2">
{/* Compact Search Bar */}
<label className="input input-sm input-bordered flex items-center gap-2 bg-base-200/90 border-base-300 rounded-xl w-full h-8.5 px-3 shadow-xs focus-within:border-primary/60 transition-colors">
<Search className="w-4 h-4 text-base-content/70 shrink-0" />
<input
type="text"
placeholder={transI18n("placeholderCharacter")}
className="grow text-xs bg-transparent border-none outline-none focus:outline-none placeholder:text-base-content/50"
value={avatarSearch}
onChange={(e) => setAvatarSearch(e.target.value)}
/>
{avatarSearch && (
<button
type="button"
onClick={() => setAvatarSearch("")}
className="text-base-content/50 hover:text-base-content"
>
<X className="w-3.5 h-3.5" />
</button>
)}
</label>
{/* Compact Filter Section */}
<div className="p-1 rounded-xl bg-base-200/80 border border-base-content/15 space-y-1 shadow-xs">
{/* Element Filters - 7 Elements on 1 Row (gap-0.5) */}
<div className="grid grid-cols-7 gap-0.5">
{Object.entries(damageType).filter(([key]) => key !== "").map(([key, value]) => {
const isActive = !!mapAvatarElementActive[key];
return (
<button
key={key}
type="button"
onClick={() => {
setMapAvatarElementActive({ ...mapAvatarElementActive, [key]: !mapAvatarElementActive[key] })
}}
className={`h-8.5 w-full rounded-lg transition-all duration-150 active-press flex items-center justify-center p-0 ${
isActive
? "bg-primary/20 text-primary ring-1.5 ring-primary scale-105 z-10 shadow-xs"
: "hover:bg-base-100 opacity-90 hover:opacity-100"
}`}
title={value.DamageTypeName || key}
>
<Image
src={`${process.env.CDN_URL}/${value.Icon}`}
alt={key}
unoptimized
crossOrigin="anonymous"
className="w-6.5 h-6.5 object-contain drop-shadow-xs"
width={32}
height={32}
/>
</button>
);
})}
</div>
{/* Path Filters - 9 Paths on 1 Row (gap-0.5) */}
<div className="grid grid-cols-9 gap-0.5">
{Object.entries(baseType).filter(([key]) => key !== "").map(([key, value]) => {
const isActive = !!mapAvatarPathActive[key];
return (
<button
key={key}
type="button"
onClick={() => {
setMapAvatarPathActive({ ...mapAvatarPathActive, [key]: !mapAvatarPathActive[key] })
}}
className={`h-8 w-full rounded-lg transition-all duration-150 active-press flex items-center justify-center p-0 ${
isActive
? "bg-primary/20 ring-1.5 ring-primary scale-105 z-10 shadow-xs"
: "hover:bg-base-100 opacity-90 hover:opacity-100"
}`}
title={value.BaseTypeName || key}
>
<Image
src={`${process.env.CDN_URL}/${value.Icon}`}
unoptimized
crossOrigin="anonymous"
alt={key}
className={`w-5.5 h-5.5 object-contain ${
isActive ? "theme-path-icon-active" : "theme-path-icon"
}`}
width={28}
height={28}
/>
</button>
);
})}
</div>
</div>
{/* Character Count & Grid List */}
<div className="flex items-center justify-between text-xs font-semibold text-base-content/80 px-1 py-0.5">
<span className="flex items-center gap-1.5">
<span className="w-1.5 h-1.5 rounded-full bg-primary animate-pulse"></span>
<span>{transI18n("character")}</span>
</span>
<span className="font-mono text-xs font-bold px-2 py-0.5 rounded-md bg-primary/10 text-primary border border-primary/20">
{listAvatar.length}
</span>
</div>
<div className="flex-1 overflow-y-auto pr-0.5 scrollbar-thin">
<ul className="grid grid-cols-3 sm:grid-cols-2 lg:grid-cols-3 gap-1.5 auto-rows-fr">
{listAvatar.map((item) => (
<div
key={item.ID}
className="h-full flex flex-col"
onClick={() => {
setAvatarSelected(item);
setSkillIDSelected(null)
if (onClose) onClose()
}}
>
<CharacterCard data={item} />
</div>
))}
</ul>
</div>
</div>
)
}