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;
+}