diff --git a/src/pages/Appearance.jsx b/src/pages/Appearance.jsx index d1ec659da..14be40c6c 100644 --- a/src/pages/Appearance.jsx +++ b/src/pages/Appearance.jsx @@ -71,6 +71,7 @@ function Appearance() { const [loadedAnimationName, setLoadedAnimationName] = React.useState(""); const [isPickingColor, setIsPickingColor] = React.useState(false) const [colorPicked, setColorPicked] = React.useState({ background: '#ffffff' }) + const [hoveredItem, setHoveredItem] = React.useState(null) const next = () => { !isMute && playSound('backNextButton'); @@ -263,6 +264,41 @@ function Appearance() { } } + const showTraitGroupTooltip = (traitGroup) => { + setHoveredItem({ + title: traitGroup.name || traitGroup.trait, + image: traitGroup.fullIconSvg, + rows: [ + ["Type", "Trait group"], + ["Trait", traitGroup.trait], + ["Options", `${traitGroup.collection?.length || 0}`], + ["Required", traitGroup.isRequired ? "Yes" : "No"], + ], + }) + } + + const showTraitTooltip = (trait, active) => { + setHoveredItem({ + title: trait.name || trait.id, + image: trait.fullThumbnail, + rows: [ + ["Trait", trait.traitGroup?.trait || ""], + ["ID", trait.id], + ["Collection", trait.collectionID], + ["Status", trait.locked ? "Locked" : "Available"], + ["Selected", active ? "Yes" : "No"], + ], + }) + } + + const showActionTooltip = (title, image, rows = []) => { + setHoveredItem({ title, image, rows }) + } + + const hideTooltip = () => { + setHoveredItem(null) + } + const uploadTrait = () =>{ setIsPickingColor(false); @@ -304,6 +340,12 @@ function Appearance() { characterManager.getGroupTraits().map((traitGroup, index) => (
showTraitGroupTooltip(traitGroup)} + onMouseLeave={hideTooltip} + onFocus={() => showTraitGroupTooltip(traitGroup)} + onBlur={hideTooltip} + tabIndex={0} + aria-label={`${traitGroup.name || traitGroup.trait} trait group`} onClick={() => { selectTraitGroup(traitGroup) }}> @@ -362,6 +404,12 @@ function Appearance() {
showActionTooltip("Randomize", randomizeIcon, [["Trait", selectedTraitGroup.trait]])} + onMouseLeave={hideTooltip} + onFocus={() => showActionTooltip("Randomize", randomizeIcon, [["Trait", selectedTraitGroup.trait]])} + onBlur={hideTooltip} + tabIndex={0} + aria-label={`Randomize ${selectedTraitGroup.trait}`} onClick={() => {randomTrait(selectedTraitGroup.trait)}} > showActionTooltip("None", cancel, [["Trait", selectedTraitGroup.trait], ["Selected", selectedTrait == null ? "Yes" : "No"]])} + onMouseLeave={hideTooltip} + onFocus={() => showActionTooltip("None", cancel, [["Trait", selectedTraitGroup.trait], ["Selected", selectedTrait == null ? "Yes" : "No"]])} + onBlur={hideTooltip} + tabIndex={0} + aria-label={`Remove ${selectedTraitGroup.trait}`} onClick={() => {removeTrait(selectedTraitGroup.trait)}} > showTraitTooltip(trait, active)} + onMouseLeave={hideTooltip} + onFocus={() => showTraitTooltip(trait, active)} + onBlur={hideTooltip} + tabIndex={0} + aria-label={trait.name || trait.id} onClick={()=>{selectTrait(trait); console.log(trait)}} > +
@@ -474,6 +535,27 @@ function Appearance() { export default Appearance +const HoverPreview = ({item}) => { + if (!item) return null + + return ( +
+ {item.image && } +
{item.title}
+ {item.rows?.length > 0 && ( +
+ {item.rows.map(([label, value]) => ( +
+ {label} + {value} +
+ ))} +
+ )} +
+ ) +} + /** * @param {{selectedTrait:ModelTrait|null,selectedBlendShapeTrait:Record,onBack:()=>void,setSelectedBlendshapeTrait:(x:Record)=>void}} param0 */ @@ -558,4 +640,4 @@ const BlendShapeItem = ({active,blendshapeID,src,select})=>{ />
) -} \ No newline at end of file +} diff --git a/src/pages/Appearance.module.css b/src/pages/Appearance.module.css index 374a079b9..34b877e8f 100644 --- a/src/pages/Appearance.module.css +++ b/src/pages/Appearance.module.css @@ -214,4 +214,64 @@ flex-direction: column; display: flex; color: white; -} \ No newline at end of file +} + +.hoverPreview { + position: fixed; + left: 454px; + top: 98px; + width: 184px; + padding: 12px; + box-sizing: border-box; + background: rgba(5, 11, 14, 0.92); + border: 1px solid rgba(8, 234, 160, 0.7); + color: #ffffff; + z-index: 1300; + pointer-events: none; + backdrop-filter: blur(22.5px); + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4); +} + +.hoverPreviewImage { + display: block; + width: 160px; + height: 160px; + object-fit: contain; + margin: 0 auto 10px; + background: rgba(255, 255, 255, 0.06); +} + +.hoverPreviewTitle { + font-family: "TTSC-Bold"; + font-size: 13px; + line-height: 1.2; + text-transform: capitalize; + margin-bottom: 8px; + overflow-wrap: anywhere; +} + +.hoverPreviewRows { + display: flex; + flex-direction: column; + gap: 5px; +} + +.hoverPreviewRow { + display: flex; + justify-content: space-between; + gap: 10px; + font-size: 10px; + line-height: 1.2; +} + +.hoverPreviewRow span { + color: #9eabb8; + text-transform: uppercase; +} + +.hoverPreviewRow strong { + color: #d1d7df; + font-weight: 700; + text-align: right; + overflow-wrap: anywhere; +}