Files
2026-06-16 19:15:56 +08:00

297 lines
10 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { cn } from "@/lib/utils"
import type { Mode } from "@/components/logic"
interface SettingsPanelProps {
mode: Mode
totalTasks: number
pickCount: number
rounds: number
allowRepeat: boolean
singleAllowRepeat: boolean
selectedRecords: { number: number; colorIndex: number }[]
onModeChange: (v: Mode) => void
onTotalTasksChange: (v: number) => void
onPickCountChange: (v: number) => void
onRoundsChange: (v: number) => void
onAllowRepeatChange: (v: boolean) => void
onSingleAllowRepeatChange: (v: boolean) => void
onRoll: () => void
onReset: () => void
isRolling: boolean
errorMsg: string
}
function FieldRow({
label,
hint,
value,
onChange,
min = 1,
}: {
label: string
hint: string
value: number
onChange: (v: number) => void
min?: number
}) {
return (
<div className="flex items-center justify-between gap-4">
<div className="min-w-0">
<div className="text-xs text-[#16191f] leading-none mb-0.5">{label}</div>
<div className="text-[10px] text-[#8a95a1] leading-none">{hint}</div>
</div>
<div className="flex items-center gap-0 border border-[#e2e6ea] flex-shrink-0">
<button
onClick={() => onChange(Math.max(min, value - 1))}
className="w-7 h-7 flex items-center justify-center text-[#8a95a1] hover:text-[#16191f] hover:bg-[#f1f3f5] transition-colors text-base leading-none"
aria-label={`减少${label}`}
>
</button>
<input
type="number"
value={value}
min={min}
onChange={(e) => {
const v = parseInt(e.target.value, 10)
if (!isNaN(v)) onChange(v)
}}
className={cn(
"w-12 h-7 text-center text-sm font-mono bg-[#f7f8fa]",
"border-x border-[#e2e6ea]",
"focus:outline-none focus:bg-[#ffffff]",
"[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none"
)}
/>
<button
onClick={() => onChange(value + 1)}
className="w-7 h-7 flex items-center justify-center text-[#8a95a1] hover:text-[#16191f] hover:bg-[#f1f3f5] transition-colors text-base leading-none"
aria-label={`增加${label}`}
>
+
</button>
</div>
</div>
)
}
export function SettingsPanel({
mode,
totalTasks,
pickCount,
rounds,
allowRepeat,
singleAllowRepeat,
selectedRecords,
onModeChange,
onTotalTasksChange,
onPickCountChange,
onRoundsChange,
onAllowRepeatChange,
onSingleAllowRepeatChange,
onRoll,
onReset,
isRolling,
errorMsg,
}: SettingsPanelProps) {
// 只有当 rounds 是数字且小于 2 时才禁用重复选项
const repeatDisabled = typeof rounds === 'number' && rounds < 2
const isSingleMode = mode === "single"
return (
<div className="flex flex-col gap-0">
{/* Mode selector */}
<div className="flex gap-2 mb-6">
<button
onClick={() => onModeChange("single")}
className={cn(
"flex-1 h-9 border text-xs tracking-wider uppercase transition-colors",
isSingleMode
? "border-[#0f141a] bg-[#0f141a] text-[#ffffff]"
: "border-[#e2e6ea] text-[#8a95a1] hover:border-[#cdd2d8] hover:text-[#16191f]"
)}
>
</button>
<button
onClick={() => onModeChange("multi")}
className={cn(
"flex-1 h-9 border text-xs tracking-wider uppercase transition-colors",
!isSingleMode
? "border-[#0f141a] bg-[#0f141a] text-[#ffffff]"
: "border-[#e2e6ea] text-[#8a95a1] hover:border-[#cdd2d8] hover:text-[#16191f]"
)}
>
</button>
</div>
{/* Section label */}
<div className="flex items-center gap-2 mb-4">
<span className="text-[10px] tracking-[0.2em] uppercase text-[#8a95a1] font-mono">CONFIG</span>
<span className="flex-1 h-px bg-[#e2e6ea]" />
</div>
{/* Fields */}
<div className="flex flex-col gap-4 mb-6">
<FieldRow label="总人数" hint="参与点名的序号上限" value={totalTasks} onChange={onTotalTasksChange} />
<div className="h-px bg-[#e2e6ea]" />
<FieldRow
label="每次人数"
hint={isSingleMode ? "每次抽取的人数" : "每组抽取的人数"}
value={pickCount}
onChange={onPickCountChange}
/>
{!isSingleMode && (
<>
<div className="h-px bg-[#e2e6ea]" />
<FieldRow label="抽取组数" hint="生成分组的数量" value={rounds} onChange={onRoundsChange} min={1} />
</>
)}
</div>
{/* Single mode: Allow repeat toggle */}
{isSingleMode && (
<button
type="button"
onClick={() => onSingleAllowRepeatChange(!singleAllowRepeat)}
className={cn(
"flex items-center justify-between w-full px-3 py-2.5 border mb-6 transition-colors",
singleAllowRepeat && "border-[#0f141a] bg-[#f1f3f5]",
!singleAllowRepeat && "border-[#e2e6ea] hover:border-[#cdd2d8]"
)}
>
<div className="text-left">
<div className={cn("text-xs", singleAllowRepeat ? "text-[#0f141a]" : "text-[#4a5563]")}>
</div>
<div className="text-[10px] text-[#8a95a1] mt-0.5"></div>
</div>
{/* Toggle pill */}
<div className={cn(
"w-9 h-5 border relative flex-shrink-0 transition-colors",
singleAllowRepeat ? "border-[#0f141a] bg-[#0f141a]/10" : "border-[#cdd2d8]"
)}>
<span className={cn(
"absolute top-0.5 w-3 h-3 transition-all duration-150 rounded-full",
singleAllowRepeat
? "left-[calc(100%-15px)] bg-[#0f141a]"
: "left-0.5 bg-[#cdd2d8]"
)} />
</div>
</button>
)}
{/* Group mode only: Toggle */}
{!isSingleMode && (
<>
{/* Toggle */}
<button
type="button"
disabled={repeatDisabled}
onClick={() => !repeatDisabled && onAllowRepeatChange(!allowRepeat)}
className={cn(
"flex items-center justify-between w-full px-3 py-2.5 border mb-6 transition-colors",
repeatDisabled && "opacity-40 cursor-not-allowed border-[#e2e6ea]",
!repeatDisabled && allowRepeat && "border-[#0f141a] bg-[#f1f3f5]",
!repeatDisabled && !allowRepeat && "border-[#e2e6ea] hover:border-[#cdd2d8]"
)}
>
<div className="text-left">
<div className={cn("text-xs", allowRepeat && !repeatDisabled ? "text-[#0f141a]" : "text-[#4a5563]")}>
</div>
<div className="text-[10px] text-[#8a95a1] mt-0.5"></div>
</div>
{/* Toggle pill */}
<div className={cn(
"w-9 h-5 border relative flex-shrink-0 transition-colors",
allowRepeat && !repeatDisabled ? "border-[#0f141a] bg-[#0f141a]/10" : "border-[#cdd2d8]"
)}>
<span className={cn(
"absolute top-0.5 w-3 h-3 transition-all duration-150 rounded-full",
allowRepeat && !repeatDisabled
? "left-[calc(100%-15px)] bg-[#0f141a]"
: "left-0.5 bg-[#cdd2d8]"
)} />
</div>
</button>
</>
)}
{/* Error */}
{errorMsg && (
<div className="border border-[#d92d20]/40 bg-[#d92d20]/5 px-3 py-2 mb-4 text-xs text-[#d92d20] leading-relaxed fui-fadein">
ERR: {errorMsg}
</div>
)}
{/* Action buttons */}
<div className="flex gap-2">
{/* Single mode: Roll button */}
{isSingleMode && (
<button
onClick={onRoll}
disabled={isRolling}
className={cn(
"flex-1 h-10 border text-xs tracking-[0.15em] uppercase font-mono transition-all",
"disabled:opacity-40 disabled:cursor-not-allowed",
isRolling
? "border-[#e2e6ea] text-[#8a95a1]"
: "border-[#0f141a] bg-[#0f141a] text-[#ffffff] hover:bg-[#ffffff] hover:text-[#0f141a]"
)}
>
{isRolling ? (
<span className="flex items-center justify-center gap-2">
<span className="inline-block w-3 h-3 border border-[#cdd2d8] border-t-[#16191f] rounded-full animate-spin" />
PROCESSING
</span>
) : (
"SELECT"
)}
</button>
)}
{/* Single mode: Reset button */}
{isSingleMode && selectedRecords.length > 0 && (
<button
onClick={onReset}
className={cn(
"h-10 px-4 border border-[#e2e6ea] text-xs tracking-wider uppercase font-mono transition-colors",
"text-[#8a95a1] hover:border-[#d92d20] hover:text-[#d92d20]"
)}
>
RESET
</button>
)}
{/* Multi mode: Execute button */}
{!isSingleMode && (
<button
onClick={onRoll}
disabled={isRolling}
className={cn(
"w-full h-10 border text-xs tracking-[0.15em] uppercase font-mono transition-all",
"disabled:opacity-40 disabled:cursor-not-allowed",
isRolling
? "border-[#e2e6ea] text-[#8a95a1]"
: "border-[#0f141a] bg-[#0f141a] text-[#ffffff] hover:bg-[#ffffff] hover:text-[#0f141a]"
)}
>
{isRolling ? (
<span className="flex items-center justify-center gap-2">
<span className="inline-block w-3 h-3 border border-[#cdd2d8] border-t-[#16191f] rounded-full animate-spin" />
PROCESSING
</span>
) : (
"EXECUTE"
)}
</button>
)}
</div>
</div>
)
}