mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-03-28 19:23:15 +00:00
feat: view instructions on mii page
This commit is contained in:
parent
5995afe3db
commit
13941e849c
23 changed files with 625 additions and 311 deletions
|
|
@ -121,7 +121,7 @@ body {
|
||||||
|
|
||||||
/* Range input */
|
/* Range input */
|
||||||
input[type="range"] {
|
input[type="range"] {
|
||||||
@apply appearance-none bg-transparent cursor-pointer;
|
@apply appearance-none bg-transparent not-disabled:cursor-pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Track */
|
/* Track */
|
||||||
|
|
@ -135,8 +135,7 @@ input[type="range"]::-moz-range-track {
|
||||||
|
|
||||||
/* Thumb */
|
/* Thumb */
|
||||||
input[type="range"]::-webkit-slider-thumb {
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
@apply appearance-none size-4 bg-orange-400 border-2 border-orange-500 rounded-full shadow-md transition;
|
@apply appearance-none size-4 bg-orange-400 border-2 border-orange-500 rounded-full shadow-md transition -mt-1.5;
|
||||||
margin-top: -6px; /* center thumb vertically */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="range"]::-moz-range-thumb {
|
input[type="range"]::-moz-range-thumb {
|
||||||
|
|
@ -145,9 +144,9 @@ input[type="range"]::-moz-range-thumb {
|
||||||
|
|
||||||
/* Hover */
|
/* Hover */
|
||||||
input[type="range"]:hover::-webkit-slider-thumb {
|
input[type="range"]:hover::-webkit-slider-thumb {
|
||||||
@apply bg-orange-500;
|
@apply not-disabled:bg-orange-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="range"]:hover::-moz-range-thumb {
|
input[type="range"]:hover::-moz-range-thumb {
|
||||||
@apply bg-orange-500;
|
@apply not-disabled:bg-orange-500;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,18 @@ import { Icon } from "@iconify/react";
|
||||||
|
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { MiiPlatform } from "@prisma/client";
|
||||||
|
|
||||||
import LikeButton from "@/components/like-button";
|
import LikeButton from "@/components/like-button";
|
||||||
import ImageViewer from "@/components/image-viewer";
|
import ImageViewer from "@/components/image-viewer";
|
||||||
import DeleteMiiButton from "@/components/delete-mii";
|
import DeleteMiiButton from "@/components/mii/delete-mii-button";
|
||||||
import ShareMiiButton from "@/components/share-mii-button";
|
import ShareMiiButton from "@/components/mii/share-mii-button";
|
||||||
import ThreeDsScanTutorialButton from "@/components/tutorial/3ds-scan";
|
import ThreeDsScanTutorialButton from "@/components/tutorial/3ds-scan";
|
||||||
import SwitchScanTutorialButton from "@/components/tutorial/switch-scan";
|
import SwitchScanTutorialButton from "@/components/tutorial/switch-scan";
|
||||||
import Description from "@/components/description";
|
import Description from "@/components/description";
|
||||||
import { MiiPlatform } from "@prisma/client";
|
import MiiInstructions from "@/components/mii/instructions";
|
||||||
|
|
||||||
|
import { SwitchMiiInstructions } from "@/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
params: Promise<{ id: string }>;
|
params: Promise<{ id: string }>;
|
||||||
|
|
@ -120,7 +123,7 @@ export default async function MiiPage({ params }: Props) {
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="max-w-5xl w-full flex flex-col gap-4">
|
<div className="max-w-5xl w-full flex flex-col gap-4">
|
||||||
<div className="relative grid grid-cols-3 gap-4 max-md:grid-cols-1">
|
<div className="relative grid grid-cols-3 gap-4 max-md:grid-cols-1">
|
||||||
<div className="bg-amber-50 rounded-3xl border-2 border-amber-500 shadow-lg p-4 flex flex-col items-center max-w-md w-full max-md:place-self-center max-md:row-start-2">
|
<div className="bg-amber-50 rounded-3xl border-2 border-amber-500 shadow-lg p-4 h-min flex flex-col items-center max-w-md w-full max-md:place-self-center max-md:row-start-2">
|
||||||
{/* Mii Image */}
|
{/* Mii Image */}
|
||||||
<div className="bg-linear-to-b from-amber-100 to-amber-200 overflow-hidden rounded-xl w-full mb-4 flex justify-center">
|
<div className="bg-linear-to-b from-amber-100 to-amber-200 overflow-hidden rounded-xl w-full mb-4 flex justify-center">
|
||||||
<ImageViewer
|
<ImageViewer
|
||||||
|
|
@ -232,6 +235,7 @@ export default async function MiiPage({ params }: Props) {
|
||||||
<Icon icon="foundation:female" className="text-pink-400" />
|
<Icon icon="foundation:female" className="text-pink-400" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{mii.platform !== "THREE_DS" && (
|
||||||
<div
|
<div
|
||||||
className={`rounded-xl flex justify-center items-center size-13 text-5xl border-2 shadow-sm ${
|
className={`rounded-xl flex justify-center items-center size-13 text-5xl border-2 shadow-sm ${
|
||||||
mii.gender === "NONBINARY" ? "bg-purple-100 border-purple-400" : "bg-white border-gray-300"
|
mii.gender === "NONBINARY" ? "bg-purple-100 border-purple-400" : "bg-white border-gray-300"
|
||||||
|
|
@ -239,6 +243,7 @@ export default async function MiiPage({ params }: Props) {
|
||||||
>
|
>
|
||||||
<Icon icon="mdi:gender-non-binary" className="text-purple-400" />
|
<Icon icon="mdi:gender-non-binary" className="text-purple-400" />
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -305,7 +310,7 @@ export default async function MiiPage({ params }: Props) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Instructions */}
|
{/* Instructions */}
|
||||||
<div className="bg-amber-50 border-2 border-amber-500 rounded-2xl shadow-lg p-4">{JSON.stringify(mii.instructions)}</div>
|
{mii.platform === "SWITCH" && <MiiInstructions instructions={mii.instructions as Partial<SwitchMiiInstructions>} />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -343,7 +348,7 @@ export default async function MiiPage({ params }: Props) {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="indent-8 text-black/50">There is nothing here...</p>
|
<p className="indent-7.5 text-black/50">There is nothing here...</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import { auth } from "@/lib/auth";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
import Countdown from "@/components/countdown";
|
import Countdown from "@/components/countdown";
|
||||||
import MiiList from "@/components/mii-list";
|
import MiiList from "@/components/mii/list";
|
||||||
import Skeleton from "@/components/mii-list/skeleton";
|
import Skeleton from "@/components/mii/list/skeleton";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import { Suspense } from "react";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
import ProfileInformation from "@/components/profile-information";
|
import ProfileInformation from "@/components/profile-information";
|
||||||
import MiiList from "@/components/mii-list";
|
import MiiList from "@/components/mii/list";
|
||||||
import Skeleton from "@/components/mii-list/skeleton";
|
import Skeleton from "@/components/mii/list/skeleton";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import { Suspense } from "react";
|
||||||
import { auth } from "@/lib/auth";
|
import { auth } from "@/lib/auth";
|
||||||
|
|
||||||
import ProfileInformation from "@/components/profile-information";
|
import ProfileInformation from "@/components/profile-information";
|
||||||
import Skeleton from "@/components/mii-list/skeleton";
|
import Skeleton from "@/components/mii/list/skeleton";
|
||||||
import MiiList from "@/components/mii-list";
|
import MiiList from "@/components/mii/list";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||||
|
|
|
||||||
38
src/components/mii/dating-preferences.tsx
Normal file
38
src/components/mii/dating-preferences.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import { ChangeEvent } from "react";
|
||||||
|
import { MiiGender } from "@prisma/client";
|
||||||
|
import { SwitchMiiInstructions } from "@/types";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: SwitchMiiInstructions["datingPreferences"];
|
||||||
|
onChecked?: (e: ChangeEvent<HTMLInputElement, HTMLInputElement>, gender: MiiGender) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DATING_PREFERENCES = ["Male", "Female", "Nonbinary"];
|
||||||
|
|
||||||
|
export default function DatingPreferencesViewer({ data, onChecked }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1.5">
|
||||||
|
{DATING_PREFERENCES.map((gender) => {
|
||||||
|
const genderEnum = gender.toUpperCase() as MiiGender;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-1.5">
|
||||||
|
<input
|
||||||
|
key={gender}
|
||||||
|
type="checkbox"
|
||||||
|
id={gender}
|
||||||
|
className="checkbox"
|
||||||
|
checked={data.includes(genderEnum)}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (onChecked) onChecked(e, genderEnum);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<label htmlFor={gender} className="text-sm select-none">
|
||||||
|
{gender}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -6,8 +6,8 @@ import { useEffect, useState } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { Icon } from "@iconify/react";
|
import { Icon } from "@iconify/react";
|
||||||
|
|
||||||
import LikeButton from "./like-button";
|
import LikeButton from "../like-button";
|
||||||
import SubmitButton from "./submit-button";
|
import SubmitButton from "../submit-button";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
miiId: number;
|
miiId: number;
|
||||||
272
src/components/mii/instructions.tsx
Normal file
272
src/components/mii/instructions.tsx
Normal file
|
|
@ -0,0 +1,272 @@
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
Color menu on left, <GridPosition index={color} cols={1} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (color >= 108) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
Outside color menu, <GridPosition index={color - 108} cols={2} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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") return null;
|
||||||
|
|
||||||
|
const type = "type" in instructions ? instructions.type : undefined;
|
||||||
|
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 ? "mt-2" : "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>
|
||||||
|
{type && (
|
||||||
|
<TableCell label="Type">
|
||||||
|
<GridPosition index={type} />
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
{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.setType && (
|
||||||
|
<TableCell label="Set Type">
|
||||||
|
<GridPosition index={hair.setType} />
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
{hair.bangsType && (
|
||||||
|
<TableCell label="Bangs Type">
|
||||||
|
<GridPosition index={hair.bangsType} />
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
{hair.backType && (
|
||||||
|
<TableCell label="Back Type">
|
||||||
|
<GridPosition index={hair.backType} />
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
{hair.subColor && (
|
||||||
|
<TableCell label="Sub Color">
|
||||||
|
<ColorPosition color={hair.subColor} />
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
{eyebrows && <Section name="Eyebrows" instructions={eyebrows}></Section>}
|
||||||
|
{eyes && (
|
||||||
|
<Section name="Eyes" instructions={eyes}>
|
||||||
|
{eyes.eyesType && (
|
||||||
|
<TableCell label="Eyes Type">
|
||||||
|
<GridPosition index={eyes.eyesType} />
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
{eyes.eyelashesTop && (
|
||||||
|
<TableCell label="Eyelashes Top Type">
|
||||||
|
<GridPosition index={eyes.eyelashesTop} />
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
{eyes.eyelashesBottom && (
|
||||||
|
<TableCell label="Eyelashes Bottom Type">
|
||||||
|
<GridPosition index={eyes.eyelashesBottom} />
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
{eyes.eyelidTop && (
|
||||||
|
<TableCell label="Eyelid Top Type">
|
||||||
|
<GridPosition index={eyes.eyelidTop} />
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
{eyes.eyelidBottom && (
|
||||||
|
<TableCell label="Eyelid Bottom Type">
|
||||||
|
<GridPosition index={eyes.eyelidBottom} />
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
{eyes.eyeliner && (
|
||||||
|
<TableCell label="Eyeliner Type">
|
||||||
|
<GridPosition index={eyes.eyeliner} />
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
{eyes.pupil && (
|
||||||
|
<TableCell label="Pupil Type">
|
||||||
|
<GridPosition index={eyes.pupil} />
|
||||||
|
</TableCell>
|
||||||
|
)}
|
||||||
|
</Section>
|
||||||
|
)}
|
||||||
|
{nose && <Section name="Nose" instructions={nose}></Section>}
|
||||||
|
{lips && <Section name="Lips" instructions={lips}></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} />
|
||||||
|
<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">
|
||||||
|
<h4 className="text-lg font-semibold mt-4">Dating Preferences</h4>
|
||||||
|
<div className="w-min">
|
||||||
|
<DatingPreferencesViewer data={datingPreferences} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{voice && (
|
||||||
|
<div className="pl-2">
|
||||||
|
<h4 className="font-semibold text-xl text-amber-800 mb-1 mt-4">Voice</h4>
|
||||||
|
<div className="w-min">
|
||||||
|
<VoiceViewer data={voice} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{personality && (
|
||||||
|
<div className="pl-2">
|
||||||
|
<h4 className="font-semibold text-xl text-amber-800 mb-1 mt-4">Personality</h4>
|
||||||
|
<div className="w-min">
|
||||||
|
<PersonalityViewer data={personality} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -11,9 +11,9 @@ import { auth } from "@/lib/auth";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
import SortSelect from "./sort-select";
|
import SortSelect from "./sort-select";
|
||||||
import Carousel from "../carousel";
|
import Carousel from "../../carousel";
|
||||||
import LikeButton from "../like-button";
|
import LikeButton from "../../like-button";
|
||||||
import DeleteMiiButton from "../delete-mii";
|
import DeleteMiiButton from "../delete-mii-button";
|
||||||
import Pagination from "./pagination";
|
import Pagination from "./pagination";
|
||||||
import FilterMenu from "./filter-menu";
|
import FilterMenu from "./filter-menu";
|
||||||
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useEffect, useMemo, useState, useTransition } from "react";
|
import { useEffect, useMemo, useState, useTransition } from "react";
|
||||||
import TagSelector from "../tag-selector";
|
import TagSelector from "../../tag-selector";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
isExclude?: boolean;
|
isExclude?: boolean;
|
||||||
49
src/components/mii/personality-viewer.tsx
Normal file
49
src/components/mii/personality-viewer.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { SwitchMiiInstructions } from "@/types";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: SwitchMiiInstructions["personality"];
|
||||||
|
onClick?: (key: string, i: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PERSONALITY_SETTINGS: { label: string; left: string; right: string }[] = [
|
||||||
|
{ label: "Movement", left: "Slow", right: "Quick" },
|
||||||
|
{ label: "Speech", left: "Polite", right: "Honest" },
|
||||||
|
{ label: "Energy", left: "Flat", right: "Varied" },
|
||||||
|
{ label: "Thinking", left: "Serious", right: "Chill" },
|
||||||
|
{ label: "Overall", left: "Normal", right: "Quirky" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function PersonalityViewer({ data, onClick }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1.5 mb-3">
|
||||||
|
{PERSONALITY_SETTINGS.map(({ label, left, right }) => {
|
||||||
|
const key = label.toLowerCase() as keyof typeof data;
|
||||||
|
return (
|
||||||
|
<div key={label} className="flex justify-center items-center gap-2">
|
||||||
|
<span className="text-sm font-semibold w-24 shrink-0">{label}</span>
|
||||||
|
<span className="text-sm text-zinc-500 w-14 text-right">{left}</span>
|
||||||
|
<div className="flex gap-0.5">
|
||||||
|
{Array.from({ length: 6 }).map((_, i) => {
|
||||||
|
const colors = ["bg-green-400", "bg-green-300", "bg-teal-200", "bg-orange-200", "bg-orange-300", "bg-orange-400"];
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={i}
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
if (onClick) onClick(key, i);
|
||||||
|
}}
|
||||||
|
className={`size-7 rounded-lg transition-opacity duration-100 border-orange-500
|
||||||
|
${colors[i]} ${data[key] === i ? "border-2 opacity-100" : "opacity-70"} ${onClick ? "cursor-pointer" : ""}`}
|
||||||
|
></button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<span className="text-sm text-zinc-500 w-12 shrink-0">{right}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
59
src/components/mii/voice-viewer.tsx
Normal file
59
src/components/mii/voice-viewer.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { SwitchMiiInstructions } from "@/types";
|
||||||
|
import { ChangeEvent } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: SwitchMiiInstructions["voice"];
|
||||||
|
onClick?: (e: ChangeEvent<HTMLInputElement, HTMLInputElement>, label: string) => void;
|
||||||
|
onClickTone?: (i: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VOICE_SETTINGS: string[] = ["Speed", "Pitch", "Depth", "Delivery"];
|
||||||
|
|
||||||
|
export default function VoiceViewer({ data, onClick, onClickTone }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
{VOICE_SETTINGS.map((label) => (
|
||||||
|
<div key={label} className="flex gap-3">
|
||||||
|
<label htmlFor={label} className="text-sm w-14">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
name={label}
|
||||||
|
className="grow"
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
value={data[label as keyof typeof data]}
|
||||||
|
disabled={!onClick}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (onClick) onClick(e, label);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<label htmlFor="delivery" className="text-sm w-14">
|
||||||
|
Tone
|
||||||
|
</label>
|
||||||
|
<div className="grid grid-cols-6 gap-1 grow">
|
||||||
|
{Array.from({ length: 6 }).map((_, i) => (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
key={i}
|
||||||
|
onClick={() => {
|
||||||
|
if (onClickTone) onClickTone(i);
|
||||||
|
}}
|
||||||
|
className={`transition-colors duration-100 rounded-xl ${data.tone === i ? "bg-orange-400!" : ""} ${onClick ? "hover:bg-orange-300 cursor-pointer" : ""}`}
|
||||||
|
>
|
||||||
|
{i + 1}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { Icon } from "@iconify/react";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { Icon } from "@iconify/react";
|
||||||
|
import { COLORS } from "@/lib/switch";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
|
@ -58,10 +59,7 @@ export default function ColorPicker({ disabled, color, setColor }: Props) {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
key={i}
|
key={i}
|
||||||
onClick={() => {
|
onClick={() => setColor(i)}
|
||||||
setColor(i);
|
|
||||||
close();
|
|
||||||
}}
|
|
||||||
className={`size-7.5 cursor-pointer rounded-md ring-orange-500 ring-offset-2 ${color === i ? "ring-2 z-10" : ""}`}
|
className={`size-7.5 cursor-pointer rounded-md ring-orange-500 ring-offset-2 ${color === i ? "ring-2 z-10" : ""}`}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: `#${c}`,
|
backgroundColor: `#${c}`,
|
||||||
|
|
@ -80,10 +78,7 @@ export default function ColorPicker({ disabled, color, setColor }: Props) {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
key={i + 8}
|
key={i + 8}
|
||||||
onClick={() => {
|
onClick={() => setColor(i + 8)}
|
||||||
setColor(i + 8);
|
|
||||||
close();
|
|
||||||
}}
|
|
||||||
className={`size-7.5 cursor-pointer rounded-md ring-orange-500 ring-offset-2 ${color === i + 8 ? "ring-2 z-10" : ""}`}
|
className={`size-7.5 cursor-pointer rounded-md ring-orange-500 ring-offset-2 ${color === i + 8 ? "ring-2 z-10" : ""}`}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: `#${c}`,
|
backgroundColor: `#${c}`,
|
||||||
|
|
@ -108,140 +103,3 @@ export default function ColorPicker({ disabled, color, setColor }: Props) {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const COLORS: string[] = [
|
|
||||||
// Outside
|
|
||||||
"000000",
|
|
||||||
"8E8E93",
|
|
||||||
"6B4F0F",
|
|
||||||
"5A2A0A",
|
|
||||||
"7A1E0E",
|
|
||||||
"A0522D",
|
|
||||||
"A56B2A",
|
|
||||||
"D4A15A",
|
|
||||||
// Row 1
|
|
||||||
"F2F2F2",
|
|
||||||
"E6D5C3",
|
|
||||||
"F3E6A2",
|
|
||||||
"CDE6A1",
|
|
||||||
"A9DFA3",
|
|
||||||
"8ED8B0",
|
|
||||||
"8FD3E8",
|
|
||||||
"C9C2E6",
|
|
||||||
"F3C1CF",
|
|
||||||
"F0A8A8",
|
|
||||||
// Row 2
|
|
||||||
"D8D8D8",
|
|
||||||
"E8C07D",
|
|
||||||
"F0D97A",
|
|
||||||
"CDE07A",
|
|
||||||
"7BC96F",
|
|
||||||
"6BC4B2",
|
|
||||||
"5BBAD6",
|
|
||||||
"D9A7E0",
|
|
||||||
"F7B6C2",
|
|
||||||
"F47C6C",
|
|
||||||
// Row 3
|
|
||||||
"C0C0C0",
|
|
||||||
"D9A441",
|
|
||||||
"F4C542",
|
|
||||||
"D4C86A",
|
|
||||||
"8FD14F",
|
|
||||||
"58B88A",
|
|
||||||
"6FA8DC",
|
|
||||||
"B4A7D6",
|
|
||||||
"F06277",
|
|
||||||
"FF6F61",
|
|
||||||
// Row 4
|
|
||||||
"A8A8A8",
|
|
||||||
"D29B62",
|
|
||||||
"F2CF75",
|
|
||||||
"D8C47A",
|
|
||||||
"8DB600",
|
|
||||||
"66C2A5",
|
|
||||||
"4DA3D9",
|
|
||||||
"C27BA0",
|
|
||||||
"D35D6E",
|
|
||||||
"FF4C3B",
|
|
||||||
// Row 5
|
|
||||||
"9A9A9A",
|
|
||||||
"C77800",
|
|
||||||
"F4B183",
|
|
||||||
"D6BF3A",
|
|
||||||
"3FA34D",
|
|
||||||
"4CA3A3",
|
|
||||||
"7EA6E0",
|
|
||||||
"B56576",
|
|
||||||
"FF1744",
|
|
||||||
"FF2A00",
|
|
||||||
// Row 6
|
|
||||||
"8A817C",
|
|
||||||
"B85C1E",
|
|
||||||
"FF8C00",
|
|
||||||
"D2B48C",
|
|
||||||
"2E8B57",
|
|
||||||
"2F7E8C",
|
|
||||||
"2E86C1",
|
|
||||||
"7D5BA6",
|
|
||||||
"C2185B",
|
|
||||||
"E0193A",
|
|
||||||
// Row 7
|
|
||||||
"6E6E6E",
|
|
||||||
"95543A",
|
|
||||||
"F4A460",
|
|
||||||
"B7A369",
|
|
||||||
"3B7A0A",
|
|
||||||
"1F6F78",
|
|
||||||
"3F51B5",
|
|
||||||
"673AB7",
|
|
||||||
"B71C1C",
|
|
||||||
"C91F3A",
|
|
||||||
// Row 8
|
|
||||||
"3E3E3E",
|
|
||||||
"8B5A2B",
|
|
||||||
"F0986C",
|
|
||||||
"9E8F2A",
|
|
||||||
"0B5D3B",
|
|
||||||
"0E3A44",
|
|
||||||
"1F2A44",
|
|
||||||
"4B2E2E",
|
|
||||||
"9C1B1B",
|
|
||||||
"7A3B2E",
|
|
||||||
// Row 9
|
|
||||||
"2E2E2E",
|
|
||||||
"7A4A2A",
|
|
||||||
"A86A1D",
|
|
||||||
"6E6B2A",
|
|
||||||
"2F6F55",
|
|
||||||
"004E52",
|
|
||||||
"1C2F6E",
|
|
||||||
"3A1F4D",
|
|
||||||
"A52A2A",
|
|
||||||
"8B4513",
|
|
||||||
// Row 10
|
|
||||||
"000000",
|
|
||||||
"5A2E0C",
|
|
||||||
"7B3F00",
|
|
||||||
"5C4A00",
|
|
||||||
"004225",
|
|
||||||
"003B44",
|
|
||||||
"0A1F44",
|
|
||||||
"2B1B3F",
|
|
||||||
"7B2D2D",
|
|
||||||
"8B3A0E",
|
|
||||||
// Head tab extra colors
|
|
||||||
"FFD8BA",
|
|
||||||
"FFD5AC",
|
|
||||||
"FEC1A4",
|
|
||||||
"FEC68F",
|
|
||||||
"FEB089",
|
|
||||||
"FEBA6B",
|
|
||||||
"F39866",
|
|
||||||
"E89854",
|
|
||||||
"E37E3F",
|
|
||||||
"B45627",
|
|
||||||
"914220",
|
|
||||||
"59371F",
|
|
||||||
"662D16",
|
|
||||||
"392D1E",
|
|
||||||
];
|
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,16 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { SwitchMiiInstructions } from "@/types";
|
|
||||||
import { MiiGender } from "@prisma/client";
|
import { MiiGender } from "@prisma/client";
|
||||||
|
|
||||||
|
import DatingPreferencesViewer from "@/components/mii/dating-preferences";
|
||||||
|
import VoiceViewer from "@/components/mii/voice-viewer";
|
||||||
|
import PersonalityViewer from "@/components/mii/personality-viewer";
|
||||||
|
|
||||||
|
import { SwitchMiiInstructions } from "@/types";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
instructions: React.RefObject<SwitchMiiInstructions>;
|
instructions: React.RefObject<SwitchMiiInstructions>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VOICE_SETTINGS: string[] = ["Speed", "Pitch", "Depth", "Delivery"];
|
|
||||||
const PERSONALITY_SETTINGS: { label: string; left: string; right: string }[] = [
|
|
||||||
{ label: "Movement", left: "Slow", right: "Quick" },
|
|
||||||
{ label: "Speech", left: "Polite", right: "Honest" },
|
|
||||||
{ label: "Energy", left: "Flat", right: "Varied" },
|
|
||||||
{ label: "Thinking", left: "Serious", right: "Chill" },
|
|
||||||
{ label: "Overall", left: "Normal", right: "Quirky" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function HeadTab({ instructions }: Props) {
|
export default function HeadTab({ instructions }: Props) {
|
||||||
const [height, setHeight] = useState(50);
|
const [height, setHeight] = useState(50);
|
||||||
const [weight, setWeight] = useState(50);
|
const [weight, setWeight] = useState(50);
|
||||||
|
|
@ -94,59 +90,15 @@ export default function HeadTab({ instructions }: Props) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<div className="flex gap-1.5">
|
<DatingPreferencesViewer
|
||||||
<input
|
data={datingPreferences}
|
||||||
type="checkbox"
|
onChecked={(e, gender) => {
|
||||||
id="male"
|
|
||||||
className="checkbox"
|
|
||||||
checked={datingPreferences.includes("MALE")}
|
|
||||||
onChange={(e) => {
|
|
||||||
setDatingPreferences((prev) =>
|
setDatingPreferences((prev) =>
|
||||||
e.target.checked ? (prev.includes("MALE") ? prev : [...prev, "MALE"]) : prev.filter((p) => p !== "MALE"),
|
e.target.checked ? (prev.includes(gender) ? prev : [...prev, gender]) : prev.filter((p) => p !== gender),
|
||||||
);
|
);
|
||||||
instructions.current.datingPreferences = datingPreferences;
|
instructions.current.datingPreferences = datingPreferences;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<label htmlFor="male" className="text-sm">
|
|
||||||
Male
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-1.5">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="female"
|
|
||||||
className="checkbox"
|
|
||||||
checked={datingPreferences.includes("FEMALE")}
|
|
||||||
onChange={(e) => {
|
|
||||||
setDatingPreferences((prev) =>
|
|
||||||
e.target.checked ? (prev.includes("FEMALE") ? prev : [...prev, "FEMALE"]) : prev.filter((p) => p !== "FEMALE"),
|
|
||||||
);
|
|
||||||
instructions.current.datingPreferences = datingPreferences;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<label htmlFor="female" className="text-sm">
|
|
||||||
Female
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex gap-1.5">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
id="nonbinary"
|
|
||||||
className="checkbox"
|
|
||||||
checked={datingPreferences.includes("NONBINARY")}
|
|
||||||
onChange={(e) => {
|
|
||||||
setDatingPreferences((prev) =>
|
|
||||||
e.target.checked ? (prev.includes("NONBINARY") ? prev : [...prev, "NONBINARY"]) : prev.filter((p) => p !== "NONBINARY"),
|
|
||||||
);
|
|
||||||
instructions.current.datingPreferences = datingPreferences;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<label htmlFor="nonbinary" className="text-sm">
|
|
||||||
Nonbinary
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -157,49 +109,17 @@ export default function HeadTab({ instructions }: Props) {
|
||||||
<hr className="grow border-zinc-300" />
|
<hr className="grow border-zinc-300" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1">
|
<VoiceViewer
|
||||||
{VOICE_SETTINGS.map((label) => (
|
data={voice}
|
||||||
<div key={label} className="flex gap-3">
|
onClick={(e, label) => {
|
||||||
<label htmlFor={label} className="text-sm w-14">
|
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
name={label}
|
|
||||||
className="grow"
|
|
||||||
min={0}
|
|
||||||
max={100}
|
|
||||||
step={1}
|
|
||||||
value={voice[label as keyof typeof voice]}
|
|
||||||
onChange={(e) => {
|
|
||||||
setVoice((p) => ({ ...p, [label]: e.target.valueAsNumber }));
|
setVoice((p) => ({ ...p, [label]: e.target.valueAsNumber }));
|
||||||
instructions.current.voice[label as keyof typeof voice] = e.target.valueAsNumber;
|
instructions.current.voice[label as keyof typeof voice] = e.target.valueAsNumber;
|
||||||
}}
|
}}
|
||||||
/>
|
onClickTone={(i) => {
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<div className="flex gap-3">
|
|
||||||
<label htmlFor="delivery" className="text-sm w-14">
|
|
||||||
Tone
|
|
||||||
</label>
|
|
||||||
<div className="grid grid-cols-6 gap-1 grow">
|
|
||||||
{Array.from({ length: 6 }).map((_, i) => (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
key={i}
|
|
||||||
onClick={() => {
|
|
||||||
setVoice((p) => ({ ...p, tone: i }));
|
setVoice((p) => ({ ...p, tone: i }));
|
||||||
instructions.current.voice.tone = i;
|
instructions.current.voice.tone = i;
|
||||||
}}
|
}}
|
||||||
className={`cursor-pointer hover:bg-orange-300 transition-colors duration-100 rounded-xl ${voice.tone === i ? "bg-orange-400!" : ""}`}
|
/>
|
||||||
>
|
|
||||||
{i + 1}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -209,35 +129,13 @@ export default function HeadTab({ instructions }: Props) {
|
||||||
<hr className="grow border-zinc-300" />
|
<hr className="grow border-zinc-300" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1.5 mb-3">
|
<PersonalityViewer
|
||||||
{PERSONALITY_SETTINGS.map(({ label, left, right }) => {
|
data={personality}
|
||||||
const key = label.toLowerCase() as keyof typeof personality;
|
onClick={(key, i) => {
|
||||||
return (
|
|
||||||
<div key={label} className="flex justify-center items-center gap-2">
|
|
||||||
<span className="text-sm font-bold w-24 shrink-0">{label}</span>
|
|
||||||
<span className="text-sm text-zinc-500 w-14 text-right">{left}</span>
|
|
||||||
<div className="flex gap-0.5">
|
|
||||||
{Array.from({ length: 6 }).map((_, i) => {
|
|
||||||
const colors = ["bg-green-400", "bg-green-300", "bg-teal-200", "bg-orange-200", "bg-orange-300", "bg-orange-400"];
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={i}
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
setPersonality((p) => ({ ...p, [key]: i }));
|
setPersonality((p) => ({ ...p, [key]: i }));
|
||||||
instructions.current.personality = personality;
|
instructions.current.personality = personality;
|
||||||
}}
|
}}
|
||||||
className={`size-7 cursor-pointer rounded-lg transition-opacity duration-100 border-orange-500
|
/>
|
||||||
${colors[i]} ${personality[key] === i ? "border-2 opacity-100" : "opacity-70"}`}
|
|
||||||
></button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<span className="text-sm text-zinc-500 w-12 shrink-0">{right}</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
136
src/lib/switch.ts
Normal file
136
src/lib/switch.ts
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
export const COLORS: string[] = [
|
||||||
|
// Outside
|
||||||
|
"000000",
|
||||||
|
"8E8E93",
|
||||||
|
"6B4F0F",
|
||||||
|
"5A2A0A",
|
||||||
|
"7A1E0E",
|
||||||
|
"A0522D",
|
||||||
|
"A56B2A",
|
||||||
|
"D4A15A",
|
||||||
|
// Row 1
|
||||||
|
"F2F2F2",
|
||||||
|
"E6D5C3",
|
||||||
|
"F3E6A2",
|
||||||
|
"CDE6A1",
|
||||||
|
"A9DFA3",
|
||||||
|
"8ED8B0",
|
||||||
|
"8FD3E8",
|
||||||
|
"C9C2E6",
|
||||||
|
"F3C1CF",
|
||||||
|
"F0A8A8",
|
||||||
|
// Row 2
|
||||||
|
"D8D8D8",
|
||||||
|
"E8C07D",
|
||||||
|
"F0D97A",
|
||||||
|
"CDE07A",
|
||||||
|
"7BC96F",
|
||||||
|
"6BC4B2",
|
||||||
|
"5BBAD6",
|
||||||
|
"D9A7E0",
|
||||||
|
"F7B6C2",
|
||||||
|
"F47C6C",
|
||||||
|
// Row 3
|
||||||
|
"C0C0C0",
|
||||||
|
"D9A441",
|
||||||
|
"F4C542",
|
||||||
|
"D4C86A",
|
||||||
|
"8FD14F",
|
||||||
|
"58B88A",
|
||||||
|
"6FA8DC",
|
||||||
|
"B4A7D6",
|
||||||
|
"F06277",
|
||||||
|
"FF6F61",
|
||||||
|
// Row 4
|
||||||
|
"A8A8A8",
|
||||||
|
"D29B62",
|
||||||
|
"F2CF75",
|
||||||
|
"D8C47A",
|
||||||
|
"8DB600",
|
||||||
|
"66C2A5",
|
||||||
|
"4DA3D9",
|
||||||
|
"C27BA0",
|
||||||
|
"D35D6E",
|
||||||
|
"FF4C3B",
|
||||||
|
// Row 5
|
||||||
|
"9A9A9A",
|
||||||
|
"C77800",
|
||||||
|
"F4B183",
|
||||||
|
"D6BF3A",
|
||||||
|
"3FA34D",
|
||||||
|
"4CA3A3",
|
||||||
|
"7EA6E0",
|
||||||
|
"B56576",
|
||||||
|
"FF1744",
|
||||||
|
"FF2A00",
|
||||||
|
// Row 6
|
||||||
|
"8A817C",
|
||||||
|
"B85C1E",
|
||||||
|
"FF8C00",
|
||||||
|
"D2B48C",
|
||||||
|
"2E8B57",
|
||||||
|
"2F7E8C",
|
||||||
|
"2E86C1",
|
||||||
|
"7D5BA6",
|
||||||
|
"C2185B",
|
||||||
|
"E0193A",
|
||||||
|
// Row 7
|
||||||
|
"6E6E6E",
|
||||||
|
"95543A",
|
||||||
|
"F4A460",
|
||||||
|
"B7A369",
|
||||||
|
"3B7A0A",
|
||||||
|
"1F6F78",
|
||||||
|
"3F51B5",
|
||||||
|
"673AB7",
|
||||||
|
"B71C1C",
|
||||||
|
"C91F3A",
|
||||||
|
// Row 8
|
||||||
|
"3E3E3E",
|
||||||
|
"8B5A2B",
|
||||||
|
"F0986C",
|
||||||
|
"9E8F2A",
|
||||||
|
"0B5D3B",
|
||||||
|
"0E3A44",
|
||||||
|
"1F2A44",
|
||||||
|
"4B2E2E",
|
||||||
|
"9C1B1B",
|
||||||
|
"7A3B2E",
|
||||||
|
// Row 9
|
||||||
|
"2E2E2E",
|
||||||
|
"7A4A2A",
|
||||||
|
"A86A1D",
|
||||||
|
"6E6B2A",
|
||||||
|
"2F6F55",
|
||||||
|
"004E52",
|
||||||
|
"1C2F6E",
|
||||||
|
"3A1F4D",
|
||||||
|
"A52A2A",
|
||||||
|
"8B4513",
|
||||||
|
// Row 10
|
||||||
|
"000000",
|
||||||
|
"5A2E0C",
|
||||||
|
"7B3F00",
|
||||||
|
"5C4A00",
|
||||||
|
"004225",
|
||||||
|
"003B44",
|
||||||
|
"0A1F44",
|
||||||
|
"2B1B3F",
|
||||||
|
"7B2D2D",
|
||||||
|
"8B3A0E",
|
||||||
|
// Head tab extra colors
|
||||||
|
"FFD8BA",
|
||||||
|
"FFD5AC",
|
||||||
|
"FEC1A4",
|
||||||
|
"FEC68F",
|
||||||
|
"FEB089",
|
||||||
|
"FEBA6B",
|
||||||
|
"F39866",
|
||||||
|
"E89854",
|
||||||
|
"E37E3F",
|
||||||
|
"B45627",
|
||||||
|
"914220",
|
||||||
|
"59371F",
|
||||||
|
"662D16",
|
||||||
|
"392D1E",
|
||||||
|
];
|
||||||
Loading…
Reference in a new issue