feat : add elemental ickn

This commit is contained in:
2026-09-04 18:30:22 +07:00 Unverified
parent 9a7954fa36
commit 9d3717d0f3
7 changed files with 129 additions and 25 deletions
+57 -13
View File
@@ -116,11 +116,13 @@ import {
} from "@/lib/guides/schemas";
import {
normalizeTargetStat,
recommendedStatElement,
recommendedStatIconName,
targetStatDefaults,
targetStatIconName,
targetStatOptions,
} from "@/lib/guides/stats";
import { elementIconFilter } from "@/lib/catalog/element";
import { supportsWeaponType } from "@/lib/catalog/lunaris";
import { rarityGradientClass } from "@/lib/catalog/rarity";
import {
@@ -158,6 +160,8 @@ type CatalogItem = {
imageUrl: string;
rarity?: number | null;
weaponType?: string | null;
element?: string | null;
elementIconUrl?: string | null;
};
const teamMemberApplyOptions = [
["characterKey", "Character"],
@@ -391,12 +395,14 @@ function StatSelect({
value,
max,
onChange,
elementIconUrls,
}: {
label: string;
options: readonly string[];
value: string[];
max: number;
onChange: (value: string[]) => void;
elementIconUrls: Record<string, string>;
}) {
return (
<Field>
@@ -409,7 +415,7 @@ function StatSelect({
}}
>
<SelectTrigger className="w-full">
<StatIcons values={value} />
<StatIcons values={value} elementIconUrls={elementIconUrls} />
<SelectValue placeholder="Select recommended stats…" />
</SelectTrigger>
<SelectContent>
@@ -420,7 +426,7 @@ function StatSelect({
value={option}
disabled={value.length >= max && !value.includes(option)}
>
<StatIcons values={[option]} />
<StatIcons values={[option]} elementIconUrls={elementIconUrls} />
{option}
</SelectItem>
))}
@@ -432,28 +438,53 @@ function StatSelect({
);
}
function StatIcons({ values }: { values: readonly string[] }) {
const iconNames = values.flatMap((value) => {
function StatIcons({
values,
elementIconUrls = {},
}: {
values: readonly string[];
elementIconUrls?: Record<string, string>;
}) {
const icons = values.flatMap((value) => {
const element = recommendedStatElement(value);
if (element && elementIconUrls[element]) {
return [{ name: element, src: elementIconUrls[element], element }];
}
const targetIcon = targetStatIconName(value);
if (targetIcon) return [targetIcon];
if (targetIcon) {
return [{ name: targetIcon, src: `/icon/stats/${targetIcon}.webp` }];
}
const recommendedIcon = recommendedStatIconName(value);
return recommendedIcon ? [recommendedIcon] : [];
return recommendedIcon
? [{
name: recommendedIcon,
src: `/icon/stats/${recommendedIcon}.${recommendedIcon === "elemental-bonus" ? "svg" : "webp"}`,
}]
: [];
});
if (!iconNames.length) return null;
if (!icons.length) return null;
return (
<span className="flex shrink-0 items-center gap-0.5" aria-hidden="true">
{iconNames.map((iconName) => (
<StatIcon key={iconName} iconName={iconName} />
{icons.map((icon) => (
<StatIcon key={icon.name} {...icon} />
))}
</span>
);
}
function StatIcon({ iconName }: { iconName: string }) {
function StatIcon({
name,
src,
element,
}: {
name: string;
src: string;
element?: string;
}) {
const [hasError, setHasError] = useState(false);
if (hasError) {
const PlaceholderIcon =
iconName === "healing-bonus" ? HeartPulseIcon : SparklesIcon;
name === "healing-bonus" ? HeartPulseIcon : SparklesIcon;
return (
<PlaceholderIcon
className="size-5 text-muted-foreground"
@@ -463,11 +494,12 @@ function StatIcon({ iconName }: { iconName: string }) {
}
return (
<Image
src={`/icon/stats/${iconName}.webp`}
src={src}
alt=""
width={20}
height={20}
className="size-5 object-contain brightness-0 invert"
className={cn("size-5 object-contain", !element && "brightness-0 invert")}
style={element ? { filter: elementIconFilter(element) } : undefined}
onError={() => setHasError(true)}
/>
);
@@ -1130,8 +1162,16 @@ function ArtifactEditor({
artifacts,
initial,
catalogTalents,
characters,
}: EditorBase) {
const profile = initial.artifactProfile;
const elementIconUrls = Object.fromEntries(
characters.flatMap((character) =>
character.element && character.elementIconUrl
? [[character.element, character.elementIconUrl] as const]
: [],
),
);
const artifactSensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
@@ -1577,6 +1617,7 @@ function ArtifactEditor({
value={sands}
max={3}
onChange={setSands}
elementIconUrls={elementIconUrls}
/>
<StatSelect
label="Goblet / ถ้วย"
@@ -1584,6 +1625,7 @@ function ArtifactEditor({
value={goblet}
max={3}
onChange={setGoblet}
elementIconUrls={elementIconUrls}
/>
<StatSelect
label="Circlet / หมวก"
@@ -1591,6 +1633,7 @@ function ArtifactEditor({
value={circlet}
max={3}
onChange={setCirclet}
elementIconUrls={elementIconUrls}
/>
</div>
<StatSelect
@@ -1599,6 +1642,7 @@ function ArtifactEditor({
value={substats}
max={substatOptions.length}
onChange={setSubstats}
elementIconUrls={elementIconUrls}
/>
<FieldSet>
<FieldLegend>Target Stats</FieldLegend>