Files
nuzlocke-tracker/frontend/src/components/RulesConfiguration.tsx

187 lines
5.9 KiB
TypeScript
Raw Normal View History

2026-02-04 17:13:58 +01:00
import type { NuzlockeRules } from '../types/rules'
import { RULE_DEFINITIONS, DEFAULT_RULES } from '../types/rules'
import { RuleToggle } from './RuleToggle'
import { TypeBadge } from './TypeBadge'
const POKEMON_TYPES = [
'bug',
'dark',
'dragon',
'electric',
'fairy',
'fighting',
'fire',
'flying',
'ghost',
'grass',
'ground',
'ice',
'normal',
'poison',
'psychic',
'rock',
'steel',
'water',
] as const
2026-02-04 17:13:58 +01:00
interface RulesConfigurationProps {
rules: NuzlockeRules
onChange: (rules: NuzlockeRules) => void
onReset?: (() => void) | undefined
hiddenRules?: Set<keyof NuzlockeRules> | undefined
2026-02-04 17:13:58 +01:00
}
export function RulesConfiguration({
rules,
onChange,
onReset,
hiddenRules,
2026-02-04 17:13:58 +01:00
}: RulesConfigurationProps) {
const visibleRules = hiddenRules
? RULE_DEFINITIONS.filter((r) => !hiddenRules.has(r.key))
: RULE_DEFINITIONS
const coreRules = visibleRules.filter((r) => r.category === 'core')
const playstyleRules = visibleRules.filter((r) => r.category === 'playstyle')
const variantRules = visibleRules.filter((r) => r.category === 'variant')
2026-02-04 17:13:58 +01:00
const handleRuleChange = (key: keyof NuzlockeRules, value: boolean) => {
onChange({ ...rules, [key]: value })
}
const handleResetToDefault = () => {
onChange(DEFAULT_RULES)
onReset?.()
}
const allowedTypes = rules.allowedTypes ?? []
const toggleType = (type: string) => {
const next = allowedTypes.includes(type)
? allowedTypes.filter((t) => t !== type)
: [...allowedTypes, type]
onChange({ ...rules, allowedTypes: next })
}
const enabledCount =
visibleRules.filter((r) => rules[r.key]).length + (allowedTypes.length > 0 ? 1 : 0)
const totalCount = visibleRules.length + 1 // +1 for type restriction
2026-02-04 17:13:58 +01:00
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<h2 className="text-xl font-semibold text-text-primary">Rules Configuration</h2>
<p className="text-sm text-text-tertiary">
2026-02-04 17:13:58 +01:00
{enabledCount} of {totalCount} rules enabled
</p>
</div>
<button
type="button"
onClick={handleResetToDefault}
className="text-sm text-text-link hover:text-accent-300"
2026-02-04 17:13:58 +01:00
>
Reset to Default
</button>
</div>
<div className="bg-surface-1 rounded-lg shadow">
<div className="px-4 py-3 border-b border-border-default">
<h3 className="text-lg font-medium text-text-primary">Core Rules</h3>
<p className="text-sm text-text-tertiary">
2026-02-04 17:13:58 +01:00
The fundamental rules of a Nuzlocke challenge
</p>
</div>
<div className="px-4">
{coreRules.map((rule) => (
<RuleToggle
key={rule.key}
name={rule.name}
description={rule.description}
enabled={rules[rule.key]}
onChange={(value) => handleRuleChange(rule.key, value)}
/>
))}
</div>
</div>
<div className="bg-surface-1 rounded-lg shadow">
<div className="px-4 py-3 border-b border-border-default">
<h3 className="text-lg font-medium text-text-primary">Playstyle</h3>
<p className="text-sm text-text-tertiary">
Describe how you're playing — doesn't affect tracker behavior
</p>
2026-02-04 17:13:58 +01:00
</div>
<div className="px-4">
{playstyleRules.map((rule) => (
2026-02-04 17:13:58 +01:00
<RuleToggle
key={rule.key}
name={rule.name}
description={rule.description}
enabled={rules[rule.key]}
onChange={(value) => handleRuleChange(rule.key, value)}
/>
))}
</div>
</div>
<div className="bg-surface-1 rounded-lg shadow">
<div className="px-4 py-3 border-b border-border-default">
<h3 className="text-lg font-medium text-text-primary">Run Variant</h3>
<p className="text-sm text-text-tertiary">
Changes which Pokémon can appear affects the encounter selector
</p>
</div>
<div className="px-4">
{variantRules.map((rule) => (
<RuleToggle
key={rule.key}
name={rule.name}
description={rule.description}
enabled={rules[rule.key]}
onChange={(value) => handleRuleChange(rule.key, value)}
/>
))}
</div>
</div>
<div className="bg-surface-1 rounded-lg shadow">
<div className="px-4 py-3 border-b border-border-default">
<h3 className="text-lg font-medium text-text-primary">Type Restriction</h3>
<p className="text-sm text-text-tertiary">
Monolocke and variants. Select allowed types a Pokémon qualifies if it shares at least
one type. Leave all deselected to disable.
</p>
</div>
<div className="px-4 py-4">
<div className="flex flex-wrap gap-2">
{POKEMON_TYPES.map((type) => (
<button
key={type}
type="button"
onClick={() => toggleType(type)}
title={type.charAt(0).toUpperCase() + type.slice(1)}
className={`p-1.5 rounded-lg border-2 transition-colors ${
allowedTypes.includes(type)
? 'border-accent-400 bg-accent-900/20'
: 'border-transparent opacity-40 hover:opacity-70'
}`}
>
<TypeBadge type={type} size="md" />
</button>
))}
</div>
{allowedTypes.length > 0 && (
<button
type="button"
onClick={() => onChange({ ...rules, allowedTypes: [] })}
className="mt-3 text-xs text-text-tertiary hover:text-text-secondary"
>
Clear selection
</button>
)}
</div>
</div>
2026-02-04 17:13:58 +01:00
</div>
)
}