mirror of
https://github.com/trafficlunar/tomodachi-share.git
synced 2026-05-13 13:17:45 +00:00
feat: improve instructions formatting
This commit is contained in:
parent
a9dcb21c20
commit
cbbe7887b8
4 changed files with 50 additions and 120 deletions
|
|
@ -119,12 +119,9 @@ input[type="range"]::-moz-range-track {
|
|||
}
|
||||
|
||||
/* Thumb */
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
@apply appearance-none size-4 bg-orange-300 border-2 border-orange-400 rounded-full shadow-md transition -mt-1.5;
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-thumb,
|
||||
input[type="range"]::-moz-range-thumb {
|
||||
@apply size-3.5 bg-orange-300 border-2 border-orange-400 rounded-full shadow-md transition;
|
||||
@apply appearance-none size-4.5 bg-orange-400 border-2 border-orange-600 rounded-full shadow-md transition;
|
||||
}
|
||||
|
||||
/* Hover */
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ 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 {
|
||||
|
|
@ -198,31 +197,35 @@ export default function MiiInstructions({ instructions }: Props) {
|
|||
<div className="p-3 text-sm border-l-4 border-amber-400 bg-amber-100/50 rounded-r-lg py-2.5 text-amber-950 w-max">
|
||||
<h3 className="font-semibold text-xl text-amber-800 mb-1">Misc</h3>
|
||||
|
||||
{height && (
|
||||
<div className="flex mb-1">
|
||||
<span className="w-16">Height</span>
|
||||
<span className="font-semibold text-orange-600">
|
||||
{height === 64 ? "0" : height > 64 ? `+${height - 64}` : `${height - 64}`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{weight && (
|
||||
<div className="flex">
|
||||
<span className="w-16">Weight</span>
|
||||
<span className="font-semibold text-orange-600">
|
||||
{weight === 64 ? "0" : weight > 64 ? `+${weight - 64}` : `${weight - 64}`}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<table className="w-full">
|
||||
<tbody>
|
||||
{not(height) && <TableCell label="Height">{height === 64 ? "0" : height! > 64 ? `+${height! - 64}` : `${height! - 64}`}</TableCell>}
|
||||
{not(weight) && <TableCell label="Weight">{weight === 64 ? "0" : weight! > 64 ? `+${weight! - 64}` : `${weight! - 64}`}</TableCell>}
|
||||
</tbody>
|
||||
</table>
|
||||
{birthday && (
|
||||
<div className="pl-2 not-nth-2:mt-4">
|
||||
<h4 className="font-semibold text-xl text-amber-800 mb-1">Birthday</h4>
|
||||
<table className="w-full">
|
||||
<tbody>
|
||||
{birthday.day && <TableCell label="Day">{birthday.day}</TableCell>}
|
||||
{birthday.month && <TableCell label="Month">{birthday.month}</TableCell>}
|
||||
{birthday.age && <TableCell label="Age">{birthday.age}</TableCell>}
|
||||
{birthday.dontAge && <TableCell label="Don't Age">{birthday.dontAge ? "Yes" : "No"}</TableCell>}
|
||||
{not(birthday.day) && <TableCell label="Day">{birthday.day}</TableCell>}
|
||||
{not(birthday.month) && <TableCell label="Month">{birthday.month}</TableCell>}
|
||||
{not(birthday.age) && <TableCell label="Age">{birthday.age}</TableCell>}
|
||||
{not(birthday.dontAge) && <TableCell label="Don't Age">{birthday.dontAge ? "Yes" : "No"}</TableCell>}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
{voice && (
|
||||
<div className="pl-2 not-nth-2:mt-4">
|
||||
<h4 className="font-semibold text-xl text-amber-800 mb-1">Voice</h4>
|
||||
<table className="w-full">
|
||||
<tbody>
|
||||
{not(voice.speed) && <TableCell label="Speed">{voice.speed}</TableCell>}
|
||||
{not(voice.pitch) && <TableCell label="Pitch">{voice.pitch}</TableCell>}
|
||||
{not(voice.depth) && <TableCell label="Depth">{voice.depth}</TableCell>}
|
||||
{not(voice.delivery) && <TableCell label="Delivery">{voice.delivery}</TableCell>}
|
||||
{not(voice.tone) && <TableCell label="Tone">{voice.tone}</TableCell>}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
@ -235,14 +238,6 @@ export default function MiiInstructions({ instructions }: Props) {
|
|||
</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>
|
||||
|
|
|
|||
|
|
@ -4,42 +4,25 @@ import EnhancedSlider from "@/components/submit-form/mii-editor/enhanced-slider"
|
|||
|
||||
interface Props {
|
||||
data: SwitchMiiInstructions["voice"];
|
||||
onChange?: (value: number, label: string) => void;
|
||||
onClickTone?: (i: number) => void;
|
||||
onChange: (value: number, label: string) => void;
|
||||
onClickTone: (i: number) => void;
|
||||
}
|
||||
|
||||
const VOICE_SETTINGS = ["Speed", "Pitch", "Depth", "Delivery"];
|
||||
|
||||
export default function VoiceViewer({ data, onChange, onClickTone }: Props) {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col">
|
||||
{VOICE_SETTINGS.map((label) => {
|
||||
const value = data[label.toLowerCase() as keyof typeof data] ?? 25;
|
||||
return onChange ? (
|
||||
<EnhancedSlider
|
||||
key={label}
|
||||
label={label}
|
||||
value={value}
|
||||
onChange={(v) => onChange?.(v, label.toLowerCase())}
|
||||
min={0}
|
||||
max={50}
|
||||
mid={25}
|
||||
/>
|
||||
) : (
|
||||
<div key={label} className="flex">
|
||||
<span className="w-14">{label}</span>
|
||||
<span className="font-semibold text-orange-600">
|
||||
{value === 25 ? "0" : value > 25 ? `+${value - 25}` : `${value - 25}`}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
return <EnhancedSlider key={label} label={label} value={value} onChange={(v) => onChange?.(v, label.toLowerCase())} min={0} max={50} mid={25} />;
|
||||
})}
|
||||
|
||||
<div className="flex gap-3">
|
||||
<div className="flex gap-3 mt-2">
|
||||
<label htmlFor="delivery" className="text-sm w-14">
|
||||
Tone
|
||||
</label>
|
||||
<div className="grid grid-cols-6 gap-1 min-w-[200px]">
|
||||
<div className="grid grid-cols-6 gap-1 min-w-50">
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -47,7 +30,7 @@ export default function VoiceViewer({ data, onChange, onClickTone }: Props) {
|
|||
onClick={() => {
|
||||
if (onClickTone) onClickTone(i);
|
||||
}}
|
||||
className={`transition-colors duration-100 rounded-xl ${data.tone === i ? "bg-orange-400!" : ""} ${onClickTone ? "hover:bg-orange-300 cursor-pointer" : ""}`}
|
||||
className={`transition-colors duration-100 rounded-xl hover:bg-orange-300 cursor-pointer ${data.tone === i ? "bg-orange-400!" : ""}`}
|
||||
>
|
||||
{i + 1}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { Icon } from "@iconify/react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
interface SliderProps {
|
||||
label: string;
|
||||
|
|
@ -12,42 +11,26 @@ interface SliderProps {
|
|||
className?: string;
|
||||
}
|
||||
|
||||
export default function EnhancedSlider({
|
||||
label,
|
||||
value,
|
||||
onChange,
|
||||
min = 0,
|
||||
max = 128,
|
||||
mid = 64,
|
||||
step = 1,
|
||||
className = "",
|
||||
}: SliderProps) {
|
||||
const [internalValue, setInternalValue] = useState(value);
|
||||
|
||||
// Sync with external value
|
||||
React.useEffect(() => {
|
||||
setInternalValue(value);
|
||||
}, [value]);
|
||||
|
||||
export default function EnhancedSlider({ label, value, onChange, min = 0, max = 128, mid = 64, step = 1, className = "" }: SliderProps) {
|
||||
const handleChange = (newValue: number) => {
|
||||
const clampedValue = Math.min(max, Math.max(min, newValue));
|
||||
setInternalValue(clampedValue);
|
||||
onChange(clampedValue);
|
||||
};
|
||||
|
||||
const nudge = (direction: number) => {
|
||||
const newValue = internalValue + (direction * step);
|
||||
const newValue = value + direction * step;
|
||||
handleChange(newValue);
|
||||
};
|
||||
|
||||
const displayValue = internalValue - mid;
|
||||
const displayValue = value - mid;
|
||||
const displayText = displayValue > 0 ? `+${displayValue}` : displayValue.toString();
|
||||
const percentage = ((value - min) / (max - min)) * 100;
|
||||
|
||||
return (
|
||||
<div className={`w-full ${className}`}>
|
||||
<div className="flex justify-between items-center mb-2 relative">
|
||||
<div className="flex justify-between items-center my-1 relative">
|
||||
<h3 className="text-sm font-semibold">{label}</h3>
|
||||
<span className="absolute left-1/2 transform -translate-x-1/2 text-xs font-bold text-orange-600 bg-white px-2 py-1 rounded-full shadow-sm">
|
||||
<span className="absolute left-1/2 transform -translate-x-1/2 text-xs font-bold text-orange-600 bg-orange-50 border-2 border-orange-400 px-2 py-1 rounded-full shadow-sm">
|
||||
{displayText}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -55,69 +38,41 @@ export default function EnhancedSlider({
|
|||
<button
|
||||
type="button"
|
||||
onClick={() => nudge(-1)}
|
||||
disabled={internalValue <= min}
|
||||
className="bg-white border-2 border-orange-400 text-orange-400 font-bold w-8 h-8 rounded-lg cursor-pointer flex items-center justify-center flex-shrink-0 transition-transform active:scale-95 disabled:opacity-30 disabled:cursor-not-allowed hover:bg-orange-50"
|
||||
disabled={value <= min}
|
||||
className="bg-orange-50 border-2 border-orange-400 text-orange-400 font-bold size-7 rounded-lg cursor-pointer flex items-center justify-center shrink-0 transition-transform not-disabled:active:scale-95 disabled:opacity-30 disabled:cursor-not-allowed hover:bg-orange-50"
|
||||
aria-label={`Decrease ${label}`}
|
||||
>
|
||||
<Icon icon="mdi:chevron-left" width="16" height="16" />
|
||||
</button>
|
||||
|
||||
|
||||
<div className="relative flex-1 h-8 flex items-center">
|
||||
{/* Tick mark at center */}
|
||||
<div className="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 w-0.5 h-3 bg-orange-400 rounded z-10 opacity-60"></div>
|
||||
|
||||
|
||||
<input
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={internalValue}
|
||||
onChange={(e) => handleChange(Number(e.target.value))}
|
||||
className="w-full h-2 bg-orange-200 rounded-lg appearance-none cursor-pointer slider-thumb"
|
||||
value={value}
|
||||
onChange={(e) => handleChange(e.target.valueAsNumber)}
|
||||
className="w-full px-0.5 h-2 bg-orange-200 rounded-lg appearance-none cursor-pointer focus:outline-0"
|
||||
style={{
|
||||
background: `linear-gradient(to right, #fb923c 0%, #fb923c ${((internalValue - min) / (max - min)) * 100}%, #fed7aa ${((internalValue - min) / (max - min)) * 100}%, #fed7aa 100%)`
|
||||
background: `linear-gradient(to right, #fb923c 0%, #fb923c ${percentage}%, #fed7aa ${percentage}%, #fed7aa 100%)`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => nudge(1)}
|
||||
disabled={internalValue >= max}
|
||||
className="bg-white border-2 border-orange-400 text-orange-400 font-bold w-8 h-8 rounded-lg cursor-pointer flex items-center justify-center flex-shrink-0 transition-transform active:scale-95 disabled:opacity-30 disabled:cursor-not-allowed hover:bg-orange-50"
|
||||
disabled={value >= max}
|
||||
className="bg-orange-50 border-2 border-orange-400 text-orange-400 font-bold size-7 rounded-lg cursor-pointer flex items-center justify-center shrink-0 transition-transform not-disabled:active:scale-95 disabled:opacity-30 disabled:cursor-not-allowed hover:bg-orange-50"
|
||||
aria-label={`Increase ${label}`}
|
||||
>
|
||||
<Icon icon="mdi:chevron-right" width="16" height="16" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style jsx>{`
|
||||
.slider-thumb::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: #fb923c;
|
||||
border: 2px solid #ea580c;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.slider-thumb::-moz-range-thumb {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
background: #fb923c;
|
||||
border: 2px solid #ea580c;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.slider-thumb:focus {
|
||||
outline: none;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue