website refactor
This commit is contained in:
@@ -3,9 +3,8 @@
|
||||
import { useState, useRef, useCallback } from 'react';
|
||||
import { Card } from '@/ui/Card';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { Box } from '@/ui/Box';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Stack } from '@/ui/Stack';
|
||||
import { Text } from '@/ui/Text';
|
||||
import { Heading } from '@/ui/Heading';
|
||||
import { Icon } from '@/ui/Icon';
|
||||
import {
|
||||
@@ -174,12 +173,12 @@ export function LeagueDecalPlacementEditor({
|
||||
return (
|
||||
<Stack gap={6}>
|
||||
{/* Header */}
|
||||
<Box display="flex" alignItems="center" justifyContent="between">
|
||||
<Box>
|
||||
<Stack display="flex" alignItems="center" justifyContent="between">
|
||||
<Stack>
|
||||
<Heading level={3} fontSize="lg" weight="semibold" color="text-white">{carName}</Heading>
|
||||
<Text size="sm" color="text-gray-400">Position sponsor decals on this car's template</Text>
|
||||
</Box>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
</Stack>
|
||||
<Stack display="flex" alignItems="center" gap={2}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => setZoom(z => Math.max(0.5, z - 0.25))}
|
||||
@@ -201,13 +200,13 @@ export function LeagueDecalPlacementEditor({
|
||||
>
|
||||
<Icon icon={ZoomIn} size={4} />
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
<Box display="grid" responsiveGridCols={{ base: 1, lg: 3 }} gap={6}>
|
||||
<Stack display="grid" responsiveGridCols={{ base: 1, lg: 3 }} gap={6}>
|
||||
{/* Canvas */}
|
||||
<Box responsiveColSpan={{ lg: 2 }}>
|
||||
<Box
|
||||
<Stack responsiveColSpan={{ lg: 2 }}>
|
||||
<Stack
|
||||
ref={canvasRef}
|
||||
position="relative"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
@@ -220,7 +219,7 @@ export function LeagueDecalPlacementEditor({
|
||||
>
|
||||
{/* Base Image or Placeholder */}
|
||||
{baseImageUrl ? (
|
||||
<Box
|
||||
<Stack
|
||||
as="img"
|
||||
src={baseImageUrl}
|
||||
alt="Livery template"
|
||||
@@ -231,21 +230,21 @@ export function LeagueDecalPlacementEditor({
|
||||
draggable={false}
|
||||
/>
|
||||
) : (
|
||||
<Box fullWidth fullHeight display="flex" flexDirection="col" alignItems="center" justifyContent="center">
|
||||
<Stack fullWidth fullHeight display="flex" flexDirection="col" alignItems="center" justifyContent="center">
|
||||
<Icon icon={ImageIcon} size={16} color="text-gray-600"
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="mb-2"
|
||||
/>
|
||||
<Text size="sm" color="text-gray-500">No base template uploaded</Text>
|
||||
<Text size="xs" color="text-gray-600">Upload a template image first</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{/* Decal Placeholders */}
|
||||
{placements.map((placement) => {
|
||||
const decalColors = getSponsorTypeColor(placement.sponsorType);
|
||||
return (
|
||||
<Box
|
||||
<Stack
|
||||
key={placement.id}
|
||||
onMouseDown={(e: React.MouseEvent) => handleMouseDown(e, placement.id)}
|
||||
onClick={() => handleDecalClick(placement.id)}
|
||||
@@ -272,8 +271,8 @@ export function LeagueDecalPlacementEditor({
|
||||
transform: `translate(-50%, -50%) rotate(${placement.rotation}deg)`,
|
||||
}}
|
||||
>
|
||||
<Box textAlign="center" truncate px={1}>
|
||||
<Box
|
||||
<Stack textAlign="center" truncate px={1}>
|
||||
<Stack
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
transform="uppercase"
|
||||
@@ -281,36 +280,36 @@ export function LeagueDecalPlacementEditor({
|
||||
className="tracking-wide opacity-70"
|
||||
>
|
||||
{placement.sponsorType === 'main' ? 'Main' : 'Secondary'}
|
||||
</Box>
|
||||
<Box truncate>{placement.sponsorName}</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Stack truncate>{placement.sponsorName}</Stack>
|
||||
</Stack>
|
||||
|
||||
{/* Drag handle indicator */}
|
||||
{selectedDecal === placement.id && (
|
||||
<Box position="absolute" top="-1" left="-1" w="3" h="3" bg="bg-white" rounded="full" border borderWidth="2px" borderColor="border-primary-blue" />
|
||||
<Stack position="absolute" top="-1" left="-1" w="3" h="3" bg="bg-white" rounded="full" border borderWidth="2px" borderColor="border-primary-blue" />
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Grid overlay when dragging */}
|
||||
{isDragging && (
|
||||
<Box position="absolute" inset="0" pointerEvents="none">
|
||||
<Box fullWidth fullHeight
|
||||
<Stack position="absolute" inset="0" pointerEvents="none">
|
||||
<Stack fullWidth fullHeight
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{
|
||||
backgroundImage: 'linear-gradient(to right, rgba(255,255,255,0.05) 1px, transparent 1px), linear-gradient(to bottom, rgba(255,255,255,0.05) 1px, transparent 1px)',
|
||||
backgroundSize: '10% 10%',
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<Text size="xs" color="text-gray-500" mt={2} block>
|
||||
Click a decal to select it, then drag to reposition. Use controls on the right to adjust size and rotation.
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
{/* Controls Panel */}
|
||||
<Stack gap={4}>
|
||||
@@ -321,7 +320,7 @@ export function LeagueDecalPlacementEditor({
|
||||
{placements.map((placement) => {
|
||||
const decalColors = getSponsorTypeColor(placement.sponsorType);
|
||||
return (
|
||||
<Box
|
||||
<Stack
|
||||
key={placement.id}
|
||||
as="button"
|
||||
onClick={() => setSelectedDecal(placement.id)}
|
||||
@@ -335,9 +334,9 @@ export function LeagueDecalPlacementEditor({
|
||||
bg={selectedDecal === placement.id ? decalColors.bg : 'bg-iron-gray/30'}
|
||||
hoverBg={selectedDecal !== placement.id ? 'bg-iron-gray/50' : undefined}
|
||||
>
|
||||
<Box display="flex" alignItems="center" justifyContent="between">
|
||||
<Box>
|
||||
<Box
|
||||
<Stack display="flex" alignItems="center" justifyContent="between">
|
||||
<Stack>
|
||||
<Stack
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
weight="medium"
|
||||
@@ -345,19 +344,19 @@ export function LeagueDecalPlacementEditor({
|
||||
color={decalColors.text}
|
||||
>
|
||||
{placement.sponsorType === 'main' ? 'Main Sponsor' : `Secondary ${placement.sponsorType.split('-')[1]}`}
|
||||
</Box>
|
||||
<Box
|
||||
</Stack>
|
||||
<Stack
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
style={{ fontSize: '10px' }}
|
||||
color="text-gray-500"
|
||||
mt={0.5}
|
||||
>
|
||||
{Math.round(placement.x * 100)}%, {Math.round(placement.y * 100)}% • {placement.rotation}°
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Icon icon={Target} size={4} color={selectedDecal === placement.id ? decalColors.text : 'text-gray-500'} />
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
@@ -369,12 +368,12 @@ export function LeagueDecalPlacementEditor({
|
||||
<Heading level={4} fontSize="sm" weight="semibold" color="text-white" mb={3}>Adjust Selected</Heading>
|
||||
|
||||
{/* Position */}
|
||||
<Box mb={4}>
|
||||
<Stack mb={4}>
|
||||
<Text as="label" size="xs" color="text-gray-400" block mb={2}>Position</Text>
|
||||
<Box display="grid" gridCols={2} gap={2}>
|
||||
<Box>
|
||||
<Stack display="grid" gridCols={2} gap={2}>
|
||||
<Stack>
|
||||
<Text as="label" size="xs" color="text-gray-500" block mb={1}>X</Text>
|
||||
<Box
|
||||
<Stack
|
||||
as="input"
|
||||
type="range"
|
||||
min="0"
|
||||
@@ -388,10 +387,10 @@ export function LeagueDecalPlacementEditor({
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="appearance-none cursor-pointer accent-primary-blue"
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<Text as="label" size="xs" color="text-gray-500" block mb={1}>Y</Text>
|
||||
<Box
|
||||
<Stack
|
||||
as="input"
|
||||
type="range"
|
||||
min="0"
|
||||
@@ -405,14 +404,14 @@ export function LeagueDecalPlacementEditor({
|
||||
// eslint-disable-next-line gridpilot-rules/component-classification
|
||||
className="appearance-none cursor-pointer accent-primary-blue"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{/* Size */}
|
||||
<Box mb={4}>
|
||||
<Stack mb={4}>
|
||||
<Text as="label" size="xs" color="text-gray-400" block mb={2}>Size</Text>
|
||||
<Box display="flex" gap={2}>
|
||||
<Stack display="flex" gap={2}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => handleResize(selectedPlacement.id, 0.9)}
|
||||
@@ -435,14 +434,14 @@ export function LeagueDecalPlacementEditor({
|
||||
/>
|
||||
Larger
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{/* Rotation */}
|
||||
<Box mb={4}>
|
||||
<Stack mb={4}>
|
||||
<Text as="label" size="xs" color="text-gray-400" block mb={2}>Rotation: {selectedPlacement.rotation}°</Text>
|
||||
<Box display="flex" alignItems="center" gap={2}>
|
||||
<Box
|
||||
<Stack display="flex" alignItems="center" gap={2}>
|
||||
<Stack
|
||||
as="input"
|
||||
type="range"
|
||||
min="0"
|
||||
@@ -464,8 +463,8 @@ export function LeagueDecalPlacementEditor({
|
||||
>
|
||||
<Icon icon={RotateCw} size={4} />
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -484,14 +483,14 @@ export function LeagueDecalPlacementEditor({
|
||||
</Button>
|
||||
|
||||
{/* Help Text */}
|
||||
<Box p={3} rounded="lg" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline/50">
|
||||
<Stack p={3} rounded="lg" bg="bg-iron-gray/30" border borderColor="border-charcoal-outline/50">
|
||||
<Text size="xs" color="text-gray-500" block>
|
||||
<Text weight="bold" color="text-gray-400">Tip:</Text> Main sponsor gets the largest, most prominent placement.
|
||||
Secondary sponsors get smaller positions. These decals will be burned onto all driver liveries.
|
||||
</Text>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user