[feat] Support empty numeric inputs & UI improvements for real app

This commit is contained in:
He
2026-06-16 19:00:03 +08:00
parent b926a6e30d
commit b86716e952
7 changed files with 183 additions and 87 deletions
+40 -16
View File
@@ -5,16 +5,16 @@ import type { Mode } from "@/components/logic"
interface SettingsPanelProps {
mode: Mode
totalTasks: number
pickCount: number
rounds: number
totalTasks: number | string
pickCount: number | string
rounds: number | string
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
onTotalTasksChange: (v: number | string) => void
onPickCountChange: (v: number | string) => void
onRoundsChange: (v: number | string) => void
onAllowRepeatChange: (v: boolean) => void
onSingleAllowRepeatChange: (v: boolean) => void
onRoll: () => void
@@ -29,13 +29,18 @@ function FieldRow({
value,
onChange,
min = 1,
error,
}: {
label: string
hint: string
value: number
onChange: (v: number) => void
value: number | string
onChange: (v: number | string) => void
min?: number
error?: string
}) {
const displayValue = value === "" ? "" : value
const hasError = error && value === ""
return (
<div className="flex items-center justify-between gap-4">
<div className="min-w-0">
@@ -44,7 +49,13 @@ function FieldRow({
</div>
<div className="flex items-center gap-0 border border-[#e2e6ea] flex-shrink-0">
<button
onClick={() => onChange(Math.max(min, value - 1))}
onClick={() => {
if (displayValue === "") {
onChange(min)
} else {
onChange(Math.max(min, (value as number) - 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}`}
>
@@ -52,21 +63,33 @@ function FieldRow({
</button>
<input
type="number"
value={value}
value={displayValue}
min={min}
onChange={(e) => {
const v = parseInt(e.target.value, 10)
if (!isNaN(v) && v >= min) onChange(v)
const inputValue = e.target.value
if (inputValue === "") {
onChange("")
} else {
const v = parseInt(inputValue, 10)
if (!isNaN(v)) onChange(v)
}
}}
className={cn(
"w-12 h-7 text-center text-sm font-mono text-[#16191f] bg-[#f7f8fa]",
"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"
"[appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none",
hasError && "text-[#d92d20] border-[#d92d20]"
)}
/>
<button
onClick={() => onChange(value + 1)}
onClick={() => {
if (displayValue === "") {
onChange(min)
} else {
onChange((value as number) + 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}`}
>
@@ -96,7 +119,8 @@ export function SettingsPanel({
isRolling,
errorMsg,
}: SettingsPanelProps) {
const repeatDisabled = rounds < 2
// 只有当 rounds 是数字且小于 2 时才禁用重复选项
const repeatDisabled = typeof rounds === 'number' && rounds < 2
const isSingleMode = mode === "single"
return (