website refactor
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { TrendingDown, Check, HelpCircle, X, Zap } from 'lucide-react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import type { LeagueConfigFormModel } from '@/lib/types/LeagueConfigFormModel';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Check, HelpCircle, TrendingDown, X, Zap } from 'lucide-react';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
// ============================================================================
|
||||
// INFO FLYOUT (duplicated for self-contained component)
|
||||
@@ -78,42 +83,79 @@ function InfoFlyout({ isOpen, onClose, title, children, anchorRef }: InfoFlyoutP
|
||||
if (!isOpen) return null;
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
<Box
|
||||
ref={flyoutRef}
|
||||
className="fixed z-50 w-[380px] max-h-[80vh] overflow-y-auto bg-iron-gray border border-charcoal-outline rounded-xl shadow-2xl animate-fade-in"
|
||||
style={{ top: position.top, left: position.left }}
|
||||
position="fixed"
|
||||
zIndex={50}
|
||||
w="380px"
|
||||
bg="bg-iron-gray"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
rounded="xl"
|
||||
shadow="2xl"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ top: position.top, left: position.left, maxHeight: '80vh', overflowY: 'auto' }}
|
||||
>
|
||||
<div className="flex items-center justify-between p-4 border-b border-charcoal-outline/50 sticky top-0 bg-iron-gray z-10">
|
||||
<div className="flex items-center gap-2">
|
||||
<HelpCircle className="w-4 h-4 text-primary-blue" />
|
||||
<span className="text-sm font-semibold text-white">{title}</span>
|
||||
</div>
|
||||
<button
|
||||
<Box
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="between"
|
||||
p={4}
|
||||
borderBottom
|
||||
borderColor="border-charcoal-outline/50"
|
||||
position="sticky"
|
||||
top="0"
|
||||
bg="bg-iron-gray"
|
||||
zIndex={10}
|
||||
>
|
||||
<Stack direction="row" align="center" gap={2}>
|
||||
<Icon icon={HelpCircle} size={4} color="text-primary-blue" />
|
||||
<Text size="sm" weight="semibold" color="text-white">{title}</Text>
|
||||
</Stack>
|
||||
<Box
|
||||
as="button"
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md hover:bg-charcoal-outline transition-colors"
|
||||
display="flex"
|
||||
h="6"
|
||||
w="6"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
rounded="md"
|
||||
transition
|
||||
hoverBg="bg-charcoal-outline"
|
||||
>
|
||||
<X className="w-4 h-4 text-gray-400" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<Icon icon={X} size={4} color="text-gray-400" />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box p={4}>
|
||||
{children}
|
||||
</div>
|
||||
</div>,
|
||||
</Box>
|
||||
</Box>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
|
||||
function InfoButton({ onClick, buttonRef }: { onClick: () => void; buttonRef: React.Ref<HTMLButtonElement> }) {
|
||||
return (
|
||||
<button
|
||||
<Box
|
||||
as="button"
|
||||
ref={buttonRef}
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
className="flex h-5 w-5 items-center justify-center rounded-full text-gray-500 hover:text-primary-blue hover:bg-primary-blue/10 transition-colors"
|
||||
display="flex"
|
||||
h="5"
|
||||
w="5"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
rounded="full"
|
||||
transition
|
||||
color="text-gray-500"
|
||||
hoverTextColor="text-primary-blue"
|
||||
hoverBg="bg-primary-blue/10"
|
||||
>
|
||||
<HelpCircle className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<Icon icon={HelpCircle} size={3.5} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -132,36 +174,65 @@ function DropRulesMockup() {
|
||||
const wouldBe = results.reduce((sum, r) => sum + r.pts, 0);
|
||||
|
||||
return (
|
||||
<div className="bg-deep-graphite rounded-lg p-4">
|
||||
<div className="flex items-center gap-2 mb-3 pb-2 border-b border-charcoal-outline/50">
|
||||
<span className="text-xs font-semibold text-white">Best 4 of 6 Results</span>
|
||||
</div>
|
||||
<div className="flex gap-1 mb-3">
|
||||
<Box bg="bg-deep-graphite" rounded="lg" p={4}>
|
||||
<Box display="flex" alignItems="center" gap={2} mb={3} pb={2} borderBottom borderColor="border-charcoal-outline/50" opacity={0.5}>
|
||||
<Text size="xs" weight="semibold" color="text-white">Best 4 of 6 Results</Text>
|
||||
</Box>
|
||||
<Box display="flex" gap={1} mb={3}>
|
||||
{results.map((r, i) => (
|
||||
<div
|
||||
<Box
|
||||
key={i}
|
||||
className={`flex-1 p-2 rounded-lg text-center border transition-all ${
|
||||
r.dropped
|
||||
? 'bg-charcoal-outline/20 border-dashed border-charcoal-outline/50 opacity-50'
|
||||
: 'bg-performance-green/10 border-performance-green/30'
|
||||
}`}
|
||||
flexGrow={1}
|
||||
p={2}
|
||||
rounded="lg"
|
||||
textAlign="center"
|
||||
border
|
||||
transition
|
||||
bg={r.dropped ? 'bg-charcoal-outline/20' : 'bg-performance-green/10'}
|
||||
borderColor={r.dropped ? 'border-charcoal-outline/50' : 'border-performance-green/30'}
|
||||
opacity={r.dropped ? 0.5 : 1}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className={r.dropped ? 'border-dashed' : ''}
|
||||
>
|
||||
<div className="text-[9px] text-gray-500">{r.round}</div>
|
||||
<div className={`text-xs font-mono font-semibold ${r.dropped ? 'text-gray-500 line-through' : 'text-white'}`}>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '9px' }}
|
||||
color="text-gray-500"
|
||||
block
|
||||
>
|
||||
{r.round}
|
||||
</Text>
|
||||
<Text font="mono" weight="semibold" size="xs" color={r.dropped ? 'text-gray-500' : 'text-white'}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className={r.dropped ? 'line-through' : ''}
|
||||
block
|
||||
>
|
||||
{r.pts}
|
||||
</div>
|
||||
</div>
|
||||
</Text>
|
||||
</Box>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-between items-center text-xs">
|
||||
<span className="text-gray-500">Total counted:</span>
|
||||
<span className="font-mono font-semibold text-performance-green">{total} pts</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center text-[10px] text-gray-500 mt-1">
|
||||
<span>Without drops:</span>
|
||||
<span className="font-mono">{wouldBe} pts</span>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent="between" alignItems="center">
|
||||
<Text size="xs" color="text-gray-500">Total counted:</Text>
|
||||
<Text font="mono" weight="semibold" color="text-performance-green" size="xs">{total} pts</Text>
|
||||
</Box>
|
||||
<Box display="flex" justifyContent="between" alignItems="center" mt={1}>
|
||||
<Text
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-gray-500"
|
||||
>
|
||||
Without drops:
|
||||
</Text>
|
||||
<Text font="mono"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-gray-500"
|
||||
>
|
||||
{wouldBe} pts
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -290,20 +361,20 @@ export function LeagueDropSection({
|
||||
const needsN = dropPolicy.strategy !== 'none';
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Stack gap={4}>
|
||||
{/* Section header */}
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-primary-blue/10">
|
||||
<TrendingDown className="w-5 h-5 text-primary-blue" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-sm font-semibold text-white">Drop Rules</h3>
|
||||
<Box display="flex" alignItems="center" gap={3}>
|
||||
<Box display="flex" h="10" w="10" alignItems="center" justifyContent="center" rounded="xl" bg="bg-primary-blue/10">
|
||||
<Icon icon={TrendingDown} size={5} color="text-primary-blue" />
|
||||
</Box>
|
||||
<Box flexGrow={1}>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Heading level={3}>Drop Rules</Heading>
|
||||
<InfoButton buttonRef={dropInfoRef} onClick={() => setShowDropFlyout(true)} />
|
||||
</div>
|
||||
<p className="text-xs text-gray-500">Protect from bad races</p>
|
||||
</div>
|
||||
</div>
|
||||
</Box>
|
||||
<Text size="xs" color="text-gray-500">Protect from bad races</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Drop Rules Flyout */}
|
||||
<InfoFlyout
|
||||
@@ -312,180 +383,306 @@ export function LeagueDropSection({
|
||||
title="Drop Rules Explained"
|
||||
anchorRef={dropInfoRef}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-xs text-gray-400">
|
||||
<Stack gap={4}>
|
||||
<Text size="xs" color="text-gray-400" block>
|
||||
Drop rules allow drivers to exclude their worst results from championship calculations.
|
||||
This protects against mechanical failures, bad luck, or occasional poor performances.
|
||||
</p>
|
||||
</Text>
|
||||
|
||||
<div>
|
||||
<div className="text-[10px] text-gray-500 uppercase tracking-wide mb-2">Visual Example</div>
|
||||
<Box>
|
||||
<Text size="xs" weight="bold" color="text-gray-500" transform="uppercase"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="tracking-wide"
|
||||
block
|
||||
mb={2}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
>
|
||||
Visual Example
|
||||
</Text>
|
||||
<DropRulesMockup />
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-[10px] text-gray-500 uppercase tracking-wide">Drop Strategies</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-start gap-2 p-2 rounded-lg bg-deep-graphite border border-charcoal-outline/30">
|
||||
<span className="text-base">✓</span>
|
||||
<div>
|
||||
<div className="text-[10px] font-medium text-white">All Count</div>
|
||||
<div className="text-[9px] text-gray-500">Every race affects standings. Best for short seasons.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-2 p-2 rounded-lg bg-deep-graphite border border-charcoal-outline/30">
|
||||
<span className="text-base">🏆</span>
|
||||
<div>
|
||||
<div className="text-[10px] font-medium text-white">Best N Results</div>
|
||||
<div className="text-[9px] text-gray-500">Only your top N races count. Extra races are optional.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-2 p-2 rounded-lg bg-deep-graphite border border-charcoal-outline/30">
|
||||
<span className="text-base">🗑️</span>
|
||||
<div>
|
||||
<div className="text-[10px] font-medium text-white">Drop Worst N</div>
|
||||
<div className="text-[9px] text-gray-500">Exclude your N worst results. Forgives bad days.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Stack gap={2}>
|
||||
<Text size="xs" weight="bold" color="text-gray-500" transform="uppercase"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="tracking-wide"
|
||||
block
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
>
|
||||
Drop Strategies
|
||||
</Text>
|
||||
<Stack gap={2}>
|
||||
<Box display="flex" alignItems="start" gap={2} p={2} rounded="lg" bg="bg-deep-graphite" border borderColor="border-charcoal-outline/30">
|
||||
<Text size="base">✓</Text>
|
||||
<Box>
|
||||
<Text size="xs" weight="medium" color="text-white" block
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
>
|
||||
All Count
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500" block
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '9px' }}
|
||||
>
|
||||
Every race affects standings. Best for short seasons.
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="start" gap={2} p={2} rounded="lg" bg="bg-deep-graphite" border borderColor="border-charcoal-outline/30">
|
||||
<Text size="base">🏆</Text>
|
||||
<Box>
|
||||
<Text size="xs" weight="medium" color="text-white" block
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
>
|
||||
Best N Results
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500" block
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '9px' }}
|
||||
>
|
||||
Only your top N races count. Extra races are optional.
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="start" gap={2} p={2} rounded="lg" bg="bg-deep-graphite" border borderColor="border-charcoal-outline/30">
|
||||
<Text size="base">🗑️</Text>
|
||||
<Box>
|
||||
<Text size="xs" weight="medium" color="text-white" block
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
>
|
||||
Drop Worst N
|
||||
</Text>
|
||||
<Text size="xs" color="text-gray-500" block
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '9px' }}
|
||||
>
|
||||
Exclude your N worst results. Forgives bad days.
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<div className="rounded-lg bg-primary-blue/5 border border-primary-blue/20 p-3">
|
||||
<div className="flex items-start gap-2">
|
||||
<Zap className="w-3.5 h-3.5 text-primary-blue shrink-0 mt-0.5" />
|
||||
<div className="text-[11px] text-gray-400">
|
||||
<span className="font-medium text-primary-blue">Pro tip:</span> For an 8-round season,
|
||||
"Best 6" or "Drop 2" are popular choices.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Box rounded="lg" bg="bg-primary-blue/5" border borderColor="border-primary-blue/20" p={3}>
|
||||
<Box display="flex" alignItems="start" gap={2}>
|
||||
<Icon icon={Zap} size={3.5} color="text-primary-blue" flexShrink={0} mt={0.5} />
|
||||
<Box>
|
||||
<Text size="xs" color="text-gray-400"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '11px' }}
|
||||
>
|
||||
<Text weight="medium" color="text-primary-blue">Pro tip:</Text> For an 8-round season,
|
||||
"Best 6" or "Drop 2" are popular choices.
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</InfoFlyout>
|
||||
|
||||
{/* Strategy buttons + N stepper inline */}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Box display="flex" flexWrap="wrap" alignItems="center" gap={2}>
|
||||
{DROP_OPTIONS.map((option) => {
|
||||
const isSelected = dropPolicy.strategy === option.value;
|
||||
const ruleInfo = DROP_RULE_INFO[option.value];
|
||||
return (
|
||||
<div key={option.value} className="relative flex items-center">
|
||||
<button
|
||||
<Box key={option.value} display="flex" alignItems="center" position="relative">
|
||||
<Box
|
||||
as="button"
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => handleStrategyChange(option.value)}
|
||||
className={`
|
||||
flex items-center gap-2 px-3 py-2 rounded-l-lg border-2 border-r-0 transition-all duration-200
|
||||
${isSelected
|
||||
? 'border-primary-blue bg-primary-blue/10'
|
||||
: 'border-charcoal-outline/40 bg-iron-gray/20 hover:border-charcoal-outline hover:bg-iron-gray/30'
|
||||
}
|
||||
${disabled ? 'cursor-default opacity-60' : 'cursor-pointer'}
|
||||
`}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
gap={2}
|
||||
px={3}
|
||||
py={2}
|
||||
rounded="lg"
|
||||
border
|
||||
borderWidth="2px"
|
||||
transition
|
||||
borderColor={isSelected ? 'border-primary-blue' : 'border-charcoal-outline/40'}
|
||||
bg={isSelected ? 'bg-primary-blue/10' : 'bg-iron-gray/20'}
|
||||
hoverBorderColor={!isSelected && !disabled ? 'border-charcoal-outline' : undefined}
|
||||
hoverBg={!isSelected && !disabled ? 'bg-iron-gray/30' : undefined}
|
||||
cursor={disabled ? 'default' : 'pointer'}
|
||||
opacity={disabled ? 0.6 : 1}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ borderRightWidth: 0, borderTopRightRadius: 0, borderBottomRightRadius: 0 }}
|
||||
>
|
||||
{/* Radio indicator */}
|
||||
<div className={`
|
||||
flex h-4 w-4 items-center justify-center rounded-full border-2 shrink-0 transition-colors
|
||||
${isSelected ? 'border-primary-blue bg-primary-blue' : 'border-gray-500'}
|
||||
`}>
|
||||
{isSelected && <Check className="w-2.5 h-2.5 text-white" />}
|
||||
</div>
|
||||
<Box
|
||||
display="flex"
|
||||
h="4"
|
||||
w="4"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
rounded="full"
|
||||
border
|
||||
borderColor={isSelected ? 'border-primary-blue' : 'border-gray-500'}
|
||||
bg={isSelected ? 'bg-primary-blue' : ''}
|
||||
flexShrink={0}
|
||||
transition
|
||||
>
|
||||
{isSelected && <Icon icon={Check} size={2.5} color="text-white" />}
|
||||
</Box>
|
||||
|
||||
<span className="text-sm">{option.emoji}</span>
|
||||
<span className={`text-sm font-medium ${isSelected ? 'text-white' : 'text-gray-400'}`}>
|
||||
<Text size="sm">{option.emoji}</Text>
|
||||
<Text size="sm" weight="medium" color={isSelected ? 'text-white' : 'text-gray-400'}>
|
||||
{option.label}
|
||||
</span>
|
||||
</button>
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
{/* Info button - separate from main button */}
|
||||
<button
|
||||
ref={(el) => { dropRuleRefs.current[option.value] = el; }}
|
||||
<Box
|
||||
as="button"
|
||||
ref={(el: HTMLButtonElement | null) => { dropRuleRefs.current[option.value] = el; }}
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
setActiveDropRuleFlyout(activeDropRuleFlyout === option.value ? null : option.value);
|
||||
}}
|
||||
className={`
|
||||
flex h-full items-center justify-center px-2 py-2 rounded-r-lg border-2 border-l-0 transition-all duration-200
|
||||
${isSelected
|
||||
? 'border-primary-blue bg-primary-blue/10'
|
||||
: 'border-charcoal-outline/40 bg-iron-gray/20 hover:border-charcoal-outline hover:bg-iron-gray/30'
|
||||
}
|
||||
${disabled ? 'cursor-default opacity-60' : 'cursor-pointer'}
|
||||
`}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
px={2}
|
||||
py={2}
|
||||
rounded="lg"
|
||||
border
|
||||
borderWidth="2px"
|
||||
transition
|
||||
borderColor={isSelected ? 'border-primary-blue' : 'border-charcoal-outline/40'}
|
||||
bg={isSelected ? 'bg-primary-blue/10' : 'bg-iron-gray/20'}
|
||||
hoverBorderColor={!isSelected && !disabled ? 'border-charcoal-outline' : undefined}
|
||||
hoverBg={!isSelected && !disabled ? 'bg-iron-gray/30' : undefined}
|
||||
cursor={disabled ? 'default' : 'pointer'}
|
||||
opacity={disabled ? 0.6 : 1}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ borderLeftWidth: 0, borderTopLeftRadius: 0, borderBottomLeftRadius: 0, height: '100%' }}
|
||||
>
|
||||
<HelpCircle className="w-3.5 h-3.5 text-gray-500 hover:text-primary-blue transition-colors" />
|
||||
</button>
|
||||
<Icon icon={HelpCircle} size={3.5} color="text-gray-500"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="hover:text-primary-blue transition-colors"
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Drop Rule Info Flyout */}
|
||||
<InfoFlyout
|
||||
isOpen={activeDropRuleFlyout === option.value}
|
||||
onClose={() => setActiveDropRuleFlyout(null)}
|
||||
title={ruleInfo.title}
|
||||
anchorRef={{ current: dropRuleRefs.current[option.value] ?? dropInfoRef.current }}
|
||||
anchorRef={{ current: (dropRuleRefs.current[option.value] as HTMLElement | null) ?? dropInfoRef.current }}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-xs text-gray-400">{ruleInfo.description}</p>
|
||||
<Stack gap={4}>
|
||||
<Text size="xs" color="text-gray-400" block>{ruleInfo.description}</Text>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="text-[10px] text-gray-500 uppercase tracking-wide">How It Works</div>
|
||||
<ul className="space-y-1.5">
|
||||
<Stack gap={2}>
|
||||
<Text size="xs" weight="bold" color="text-gray-500" transform="uppercase"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="tracking-wide"
|
||||
block
|
||||
mb={2}
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
>
|
||||
How It Works
|
||||
</Text>
|
||||
<Stack gap={1.5}>
|
||||
{ruleInfo.details.map((detail, idx) => (
|
||||
<li key={idx} className="flex items-start gap-2 text-xs text-gray-400">
|
||||
<Check className="w-3 h-3 text-performance-green shrink-0 mt-0.5" />
|
||||
<span>{detail}</span>
|
||||
</li>
|
||||
<Box key={idx} display="flex" alignItems="start" gap={2}>
|
||||
<Icon icon={Check} size={3} color="text-performance-green" flexShrink={0} mt={0.5} />
|
||||
<Text size="xs" color="text-gray-400">{detail}</Text>
|
||||
</Box>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<div className="rounded-lg bg-deep-graphite border border-charcoal-outline/30 p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-base">{option.emoji}</span>
|
||||
<div>
|
||||
<div className="text-[10px] text-gray-500">Example</div>
|
||||
<div className="text-xs font-medium text-white">{ruleInfo.example}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Box rounded="lg" bg="bg-deep-graphite" border borderColor="border-charcoal-outline/30" p={3}>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Text size="base">{option.emoji}</Text>
|
||||
<Box>
|
||||
<Text size="xs" color="text-gray-400" block
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
>
|
||||
Example
|
||||
</Text>
|
||||
<Text size="xs" weight="medium" color="text-white" block>{ruleInfo.example}</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</InfoFlyout>
|
||||
</div>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* N Stepper - only show when needed */}
|
||||
{needsN && (
|
||||
<div className="flex items-center gap-1 ml-2">
|
||||
<span className="text-xs text-gray-500 mr-1">N =</span>
|
||||
<button
|
||||
<Box display="flex" alignItems="center" gap={1} ml={2}>
|
||||
<Text size="xs" color="text-gray-500" mr={1}>N =</Text>
|
||||
<Box
|
||||
as="button"
|
||||
type="button"
|
||||
disabled={disabled || (dropPolicy.n ?? 1) <= 1}
|
||||
onClick={() => handleNChange(-1)}
|
||||
className="flex h-7 w-7 items-center justify-center rounded-md bg-iron-gray border border-charcoal-outline text-gray-400 hover:text-white hover:border-primary-blue disabled:opacity-40 transition-colors"
|
||||
display="flex"
|
||||
h="7"
|
||||
w="7"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
rounded="md"
|
||||
bg="bg-iron-gray"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
color="text-gray-400"
|
||||
transition
|
||||
hoverTextColor={!disabled ? 'text-white' : undefined}
|
||||
hoverBorderColor={!disabled ? 'border-primary-blue' : undefined}
|
||||
opacity={disabled || (dropPolicy.n ?? 1) <= 1 ? 0.4 : 1}
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<div className="flex h-7 w-10 items-center justify-center rounded-md bg-iron-gray/50 border border-charcoal-outline/50">
|
||||
<span className="text-sm font-semibold text-white">{dropPolicy.n ?? 1}</span>
|
||||
</div>
|
||||
<button
|
||||
</Box>
|
||||
<Box display="flex" h="7" w="10" alignItems="center" justifyContent="center" rounded="md" bg="bg-iron-gray/50" border borderColor="border-charcoal-outline/50">
|
||||
<Text size="sm" weight="semibold" color="text-white">{dropPolicy.n ?? 1}</Text>
|
||||
</Box>
|
||||
<Box
|
||||
as="button"
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
onClick={() => handleNChange(1)}
|
||||
className="flex h-7 w-7 items-center justify-center rounded-md bg-iron-gray border border-charcoal-outline text-gray-400 hover:text-white hover:border-primary-blue disabled:opacity-40 transition-colors"
|
||||
display="flex"
|
||||
h="7"
|
||||
w="7"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
rounded="md"
|
||||
bg="bg-iron-gray"
|
||||
border
|
||||
borderColor="border-charcoal-outline"
|
||||
color="text-gray-400"
|
||||
transition
|
||||
hoverTextColor={!disabled ? 'text-white' : undefined}
|
||||
hoverBorderColor={!disabled ? 'border-primary-blue' : undefined}
|
||||
opacity={disabled ? 0.4 : 1}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
{/* Explanation text */}
|
||||
<p className="text-xs text-gray-500">
|
||||
<Text size="xs" color="text-gray-500" block>
|
||||
{dropPolicy.strategy === 'none' && 'Every race result affects the championship standings.'}
|
||||
{dropPolicy.strategy === 'bestNResults' && `Only your best ${dropPolicy.n ?? 1} results will count.`}
|
||||
{dropPolicy.strategy === 'dropWorstN' && `Your worst ${dropPolicy.n ?? 1} results will be excluded.`}
|
||||
</p>
|
||||
</div>
|
||||
</Text>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user