"use client"; import { useState } from "react"; import { CharacterColorPresets } from "@/components/character/character-color-presets"; import { CharacterColorSliders } from "@/components/character/character-color-sliders"; import { CharacterLayerPreview } from "@/components/character/character-layer-preview"; import { CharacterLayerTabs } from "@/components/character/character-layer-tabs"; import { CharacterPieceSelector } from "@/components/character/character-piece-selector"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import type { AvatarCustomization } from "@/lib/avatar-customization-storage"; import { clampHsl } from "@/lib/character/color-utils"; import { CHARACTER_LAYERS, COLOR_PRESETS, DEFAULT_GRAY_COLOR, LAYER_DEFAULT_COLORS, NONE_VARIANT_ID, buildDefaultVariants, getLayerById, type ColorPreset, } from "@/lib/character/presets"; import type { LayerColorState, LayerVariantState } from "@/lib/character/types"; function SectionLabel({ children }: { children: React.ReactNode }) { return (

{children}

); } type CharacterCreatorProps = { initialCustomization?: AvatarCustomization; name?: string; onNameChange?: (name: string) => void; showNameField?: boolean; saveLabel?: string; isSaving?: boolean; ownedVariantIds?: string[]; onSave?: (customization: AvatarCustomization) => Promise; }; export function CharacterCreator({ initialCustomization, name: controlledName, onNameChange, ownedVariantIds = [], showNameField = false, saveLabel = "Save avatar", isSaving = false, onSave, }: CharacterCreatorProps) { const [internalName, setInternalName] = useState( initialCustomization?.name ?? "Pixel Me", ); const name = controlledName ?? internalName; const setName = (nextName: string) => { if (onNameChange) { onNameChange(nextName); return; } setInternalName(nextName); }; const [colors, setColors] = useState( initialCustomization?.colors ?? { ...LAYER_DEFAULT_COLORS }, ); const [variants, setVariants] = useState( initialCustomization?.variants ?? buildDefaultVariants(), ); const [activePresetId, setActivePresetId] = useState( DEFAULT_GRAY_COLOR.id, ); const [activeLayerId, setActiveLayerId] = useState< (typeof CHARACTER_LAYERS)[number]["id"] >("skin"); const [saveError, setSaveError] = useState(null); const activeLayer = getLayerById(activeLayerId); const activeColor = colors[activeLayerId]; const ownedSet = new Set(ownedVariantIds); const pieceVariants = activeLayer.allowVariants ? activeLayer.variants.filter( (variant) => variant.id === NONE_VARIANT_ID || ownedSet.has(variant.id), ) : []; const applyPreset = (preset: ColorPreset) => { setActivePresetId(preset.id); setColors((current) => ({ ...current, [activeLayerId]: { ...preset.hsl }, })); }; const updateActiveColor = (nextColor: typeof activeColor) => { setActivePresetId(""); setColors((current) => ({ ...current, [activeLayerId]: clampHsl(nextColor), })); }; const handleLayerChange = (layerId: (typeof CHARACTER_LAYERS)[number]["id"]) => { setActiveLayerId(layerId); const layerColor = colors[layerId]; const matchingPreset = COLOR_PRESETS.find( (preset) => preset.hsl.h === layerColor.h && preset.hsl.s === layerColor.s && preset.hsl.l === layerColor.l, ); setActivePresetId(matchingPreset?.id ?? ""); }; const handleVariantChange = (variantId: string) => { setVariants((current) => ({ ...current, [activeLayerId]: variantId, })); }; const buildCustomization = (): AvatarCustomization => ({ name, colors, variants, customized: true, equippedItems: initialCustomization?.equippedItems ?? [], }); const handleSave = async () => { if (!onSave) { return; } setSaveError(null); try { await onSave(buildCustomization()); } catch (error) { setSaveError( error instanceof Error ? error.message : "Could not save avatar.", ); } }; return (
{showNameField ? (
setName(event.target.value)} />
) : null}
Style
Color
Adjust
{onSave ? (
{saveError ? (

{saveError}

) : null}
) : null}
); }