tomodachi-share/src/components/mii/instructions.tsx
2026-03-26 17:12:45 +00:00

238 lines
8.4 KiB
TypeScript

import React from "react";
import DatingPreferencesViewer from "./dating-preferences";
import VoiceViewer from "./voice-viewer";
import PersonalityViewer from "./personality-viewer";
import { SwitchMiiInstructions } from "@/types";
import { Icon } from "@iconify/react";
import { COLORS } from "@/lib/switch";
interface Props {
instructions: Partial<SwitchMiiInstructions>;
}
interface SectionProps {
name: string;
instructions: Partial<SwitchMiiInstructions[keyof SwitchMiiInstructions]>;
children?: React.ReactNode;
isSubSection?: boolean;
}
const ORDINAL_SUFFIXES: Record<string, string> = {
one: "st",
two: "nd",
few: "rd",
other: "th",
};
const ordinalRules = new Intl.PluralRules("en-US", { type: "ordinal" });
function GridPosition({ index, cols = 5 }: { index: number; cols?: number }) {
const row = Math.floor(index / cols) + 1;
const col = (index % cols) + 1;
const rowSuffix = ORDINAL_SUFFIXES[ordinalRules.select(row)];
const colSuffix = ORDINAL_SUFFIXES[ordinalRules.select(col)];
return `${row}${rowSuffix} row, ${col}${colSuffix} column`;
}
function ColorPosition({ color }: { color: number }) {
if (!color) return null;
if (color <= 7) {
return (
<span className="flex items-center">
<div className="size-5 rounded mr-1.5" style={{ backgroundColor: `#${COLORS[color]}` }}></div>
Color menu on left, <GridPosition index={color} cols={1} />
</span>
);
}
if (color >= 108) {
return (
<span className="flex items-center">
<div className="size-5 rounded mr-1.5" style={{ backgroundColor: `#${COLORS[color]}` }}></div>
Outside color menu, <GridPosition index={color - 108} cols={2} />
</span>
);
}
return (
<span className="flex items-center">
<div className="size-5 rounded mr-1.5" style={{ backgroundColor: `#${COLORS[color]}` }}></div>
Color menu on right, <GridPosition index={color - 8} cols={10} />
</span>
);
}
interface TableCellProps {
label: string;
children: React.ReactNode;
}
function TableCell({ label, children }: TableCellProps) {
return (
<tr className={"border-b border-orange-300/50 last:border-0"}>
<td className={"py-0.5 pr-6 text-amber-700 font-semibold w-30 text-sm"}>{label}</td>
<td className={"py-0.5 text-amber-950"}>{children}</td>
</tr>
);
}
function Section({ name, instructions, children, isSubSection }: SectionProps) {
if (typeof instructions !== "object" || !instructions) return null;
const color = "color" in instructions ? instructions.color : undefined;
const height = "height" in instructions ? instructions.height : undefined;
const distance = "distance" in instructions ? instructions.distance : undefined;
const rotation = "rotation" in instructions ? instructions.rotation : undefined;
const size = "size" in instructions ? instructions.size : undefined;
const stretch = "stretch" in instructions ? instructions.stretch : undefined;
return (
<div className={`p-3 ${isSubSection ? "not-first:mt-2 pt-0!" : "border-l-4 border-amber-400 bg-amber-100/50 rounded-r-lg py-2.5"}`}>
<h3 className="font-semibold text-xl text-amber-800 mb-1">{name}</h3>
<table className="w-full">
<tbody>
{color && (
<TableCell label="Color">
<ColorPosition color={color} />
</TableCell>
)}
{height && <TableCell label="Height">{height}</TableCell>}
{distance && <TableCell label="Distance">{distance}</TableCell>}
{rotation && <TableCell label="Rotation">{rotation}</TableCell>}
{size && <TableCell label="Size">{size}</TableCell>}
{stretch && <TableCell label="Stretch">{stretch}</TableCell>}
{children}
</tbody>
</table>
</div>
);
}
export default function MiiInstructions({ instructions }: Props) {
if (Object.keys(instructions).length === 0) return null;
const { head, hair, eyebrows, eyes, nose, lips, ears, glasses, other, height, weight, datingPreferences, voice, personality } = instructions;
return (
<div className="bg-amber-50 border-2 border-amber-500 rounded-2xl shadow-lg p-4 flex flex-col gap-3 max-h-96 overflow-y-auto">
<h2 className="text-xl font-semibold text-amber-700 flex items-center gap-2">
<Icon icon="fa7-solid:list" />
Instructions
</h2>
{head && <Section name="Head" instructions={head}></Section>}
{hair && (
<Section name="Hair" instructions={hair}>
{hair.subColor && (
<TableCell label="Sub Color">
<ColorPosition color={hair.subColor} />
</TableCell>
)}
{hair.subColor2 && (
<TableCell label="Sub Color (Back)">
<ColorPosition color={hair.subColor2} />
</TableCell>
)}
{hair.style && <TableCell label="Tying Style">{hair.style}</TableCell>}
{hair.isFlipped && <TableCell label="Flipped">{hair.isFlipped ? "Yes" : "No"}</TableCell>}
</Section>
)}
{eyebrows && <Section name="Eyebrows" instructions={eyebrows}></Section>}
{eyes && (
<Section name="Eyes" instructions={eyes}>
<Section isSubSection name="Main" instructions={eyes.main} />
<Section isSubSection name="Eyelashes Top" instructions={eyes.eyelashesTop} />
<Section isSubSection name="Eyelashes Bottom" instructions={eyes.eyelashesBottom} />
<Section isSubSection name="Eyelid Top" instructions={eyes.eyelidTop} />
<Section isSubSection name="Eyelid Bottom" instructions={eyes.eyelidBottom} />
<Section isSubSection name="Eyeliner" instructions={eyes.eyeliner} />
<Section isSubSection name="Pupil" instructions={eyes.pupil} />
</Section>
)}
{nose && <Section name="Nose" instructions={nose}></Section>}
{lips && (
<Section name="Lips" instructions={lips}>
{lips.hasLipstick && <TableCell label="Lipstick">{lips.hasLipstick ? "Yes" : "No"}</TableCell>}
</Section>
)}
{ears && <Section name="Ears" instructions={ears}></Section>}
{glasses && (
<Section name="Glasses" instructions={glasses}>
{glasses.ringColor && (
<TableCell label="Ring Color">
<ColorPosition color={glasses.ringColor} />
</TableCell>
)}
{glasses.shadesColor && (
<TableCell label="Shades Color">
<ColorPosition color={glasses.shadesColor} />
</TableCell>
)}
</Section>
)}
{other && (
<Section name="Other" instructions={other}>
<Section isSubSection name="Wrinkles 1" instructions={other.wrinkles1} />
<Section isSubSection name="Wrinkles 2" instructions={other.wrinkles2} />
<Section isSubSection name="Beard" instructions={other.beard} />
<Section isSubSection name="Moustache" instructions={other.moustache}>
{other.moustache && other.moustache.isFlipped && <TableCell label="Flipped">{other.moustache.isFlipped ? "Yes" : "No"}</TableCell>}
</Section>
<Section isSubSection name="Goatee" instructions={other.goatee} />
<Section isSubSection name="Mole" instructions={other.mole} />
<Section isSubSection name="Eye Shadow" instructions={other.eyeShadow} />
<Section isSubSection name="Blush" instructions={other.blush} />
</Section>
)}
{(height || weight || datingPreferences || voice || personality) && (
<div className="pl-3 text-sm border-l-4 border-amber-400 bg-amber-100/50 rounded-r-lg py-2.5 text-amber-950">
<h3 className="font-semibold text-xl text-amber-800 mb-1">Misc</h3>
{height && (
<div className="flex mb-1">
<label htmlFor="height" className="w-16">
Height
</label>
<input id="height" type="range" min={0} max={100} step={1} disabled value={height} />
</div>
)}
{weight && (
<div className="flex">
<label htmlFor="weight" className="w-16">
Weight
</label>
<input id="weight" type="range" min={0} max={100} step={1} disabled value={weight} />
</div>
)}
{datingPreferences && (
<div className="pl-2 not-nth-2:mt-4">
<h4 className="font-semibold text-xl text-amber-800 mb-1">Dating Preferences</h4>
<div className="w-min">
<DatingPreferencesViewer data={datingPreferences} />
</div>
</div>
)}
{voice && (
<div className="pl-2 not-nth-2:mt-4">
<h4 className="font-semibold text-xl text-amber-800 mb-1">Voice</h4>
<div className="w-min">
<VoiceViewer data={voice} />
</div>
</div>
)}
{personality && (
<div className="pl-2 not-nth-2:mt-4">
<h4 className="font-semibold text-xl text-amber-800 mb-1">Personality</h4>
<div className="w-min">
<PersonalityViewer data={personality} />
</div>
</div>
)}
</div>
)}
</div>
);
}