[feat] number-ball #1

Merged
gtxykn0504 merged 1 commits from v1 into main 2026-06-15 19:33:30 +08:00
4 changed files with 26 additions and 15 deletions
+1
View File
@@ -69,6 +69,7 @@ export default function Page() {
number={n}
state={actions.getBallState(n)}
colorIndex={actions.getBallColorIndex(n)}
dimmed={actions.getBallDimmed(n)}
size="large"
/>
))}
+15 -4
View File
@@ -88,6 +88,7 @@ export interface RollCallActions {
clearResult: () => void
getBallState: (n: number) => "idle" | "highlighted" | "used"
getBallColorIndex: (n: number) => number | undefined
getBallDimmed: (n: number) => boolean
}
export const COLORS = [
@@ -112,7 +113,7 @@ export const COLORS = [
export function useRollCallLogic(): [RollCallState, RollCallActions] {
const [mode, setMode] = useState<Mode>("single")
const [totalTasks, setTotalTasks] = useState(20)
const [totalTasks, setTotalTasks] = useState(35)
const [pickCount, setPickCount] = useState(1)
const [rounds, setRounds] = useState(1)
const [allowRepeat, setAllowRepeat] = useState(false)
@@ -251,14 +252,15 @@ export function useRollCallLogic(): [RollCallState, RollCallActions] {
const getBallState = useCallback((n: number): "idle" | "highlighted" | "used" => {
if (mode === "single") {
const isInRecords = selectedRecords.some(r => r.number === n)
if (isInRecords) return "highlighted"
// Only highlight current round's selections
const isCurrentRound = singleHighlighted.includes(n)
if (isCurrentRound) return "highlighted"
return "idle"
}
if (highlighted.has(n)) return "highlighted"
if (hasResult && usedAll.size > 0 && !usedAll.has(n)) return "used"
return "idle"
}, [mode, selectedRecords, highlighted, hasResult, usedAll])
}, [mode, singleHighlighted, highlighted, hasResult, usedAll])
const getBallColorIndex = useCallback((n: number): number | undefined => {
if (mode === "single") {
@@ -275,6 +277,14 @@ export function useRollCallLogic(): [RollCallState, RollCallActions] {
return undefined
}, [mode, selectedRecords, groups])
const getBallDimmed = useCallback((n: number): boolean => {
// Only dim in single mode when there are highlighted numbers
if (mode === "single" && singleHighlighted.length > 0) {
return !singleHighlighted.includes(n)
}
return false
}, [mode, singleHighlighted])
const state: RollCallState = {
mode,
totalTasks,
@@ -308,6 +318,7 @@ export function useRollCallLogic(): [RollCallState, RollCallActions] {
clearResult,
getBallState,
getBallColorIndex,
getBallDimmed,
}
return [state, actions]
+9 -4
View File
@@ -8,19 +8,23 @@ interface NumberBallProps {
state: "idle" | "highlighted" | "used"
colorIndex?: number
size?: "normal" | "large"
dimmed?: boolean
}
export function NumberBall({ number, state, colorIndex, size = "normal" }: NumberBallProps) {
export function NumberBall({ number, state, colorIndex, size = "normal", dimmed = false }: NumberBallProps) {
const isHighlighted = state === "highlighted"
const borderColor = isHighlighted && colorIndex !== undefined
const hasColor = colorIndex !== undefined
const borderColor = hasColor
? COLORS[colorIndex]
: state === "idle" ? "#e2e6ea"
: "#eef0f2"
const bgColor = isHighlighted && colorIndex !== undefined
const bgColor = isHighlighted && hasColor
? COLORS[colorIndex]
: "transparent"
const textColor = isHighlighted && colorIndex !== undefined
const textColor = isHighlighted && hasColor
? "#ffffff"
: hasColor ? COLORS[colorIndex]
: state === "idle" ? "#8a95a1"
: "#cdd2d8"
@@ -30,6 +34,7 @@ export function NumberBall({ number, state, colorIndex, size = "normal" }: Numbe
"flex items-center justify-center font-mono select-none transition-all duration-150",
size === "normal" && "w-8 h-8 text-sm",
size === "large" && "w-14 h-14 text-lg",
dimmed && !isHighlighted && "opacity-30",
)}
style={{
border: `1px solid ${borderColor}`,
+1 -7
View File
@@ -8,13 +8,7 @@ interface SingleModePanelProps {
export function SingleModePanel({ singleBatches }: SingleModePanelProps) {
if (singleBatches.length === 0) {
return (
<div className="flex flex-col items-center justify-center h-12 gap-3">
<div className="text-[10px] tracking-[0.2em] text-[#cdd2d8] font-mono uppercase">
AWAITING INPUT
</div>
</div>
)
return null
}
// Reverse to show newest first