Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions pegasus/components/ui/ProfileAchievements.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,21 @@ export default function ProfileAchievements({ achievements = [], isBanned = fals
{visibleAchievements.map((achievement) => {
const title = getAchievementTitle(achievement);
const iconUrl = achievement.icon_url || "/badges/creator.webp";
const achievementIcon = (
<img src={iconUrl} alt={title} width="56" height="56" />
);

return (
<Tooltip key={achievement.id || achievement.code} content={getAchievementTooltip(achievement)}>
<span className="subsite-achievements__item" tabIndex={0}>
<img src={iconUrl} alt={title} width="56" height="56" />
</span>
{achievement.code === "hytalemodjam_2026" ? (
<a className="subsite-achievements__item subsite-achievements__item--link" href="https://hytalemodjam.com/submissions?modjam=hm-26.2" target="_blank" rel="noopener noreferrer" aria-label={title}>
{achievementIcon}
</a>
) : (
<span className="subsite-achievements__item" tabIndex={0}>
{achievementIcon}
</span>
)}
</Tooltip>
);
})}
Expand Down
76 changes: 70 additions & 6 deletions pegasus/components/ui/Tooltip.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,45 @@
"use client";

import { useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";

const TOOLTIP_GAP = 10;
const VIEWPORT_PADDING = 8;

export default function Tooltip({ content, children, position = "top", delay = 500 }) {
const [isVisible, setIsVisible] = useState(false);
const [tooltipStyle, setTooltipStyle] = useState(null);
const [isMounted, setIsMounted] = useState(false);
const triggerRef = useRef(null);
const bubbleRef = useRef(null);
const timeoutRef = useRef(null);

const updateTooltipPosition = useCallback(() => {
const trigger = triggerRef.current;
const bubble = bubbleRef.current;

if(!trigger || !bubble) {
return;
}

const triggerRect = trigger.getBoundingClientRect();
const bubbleRect = bubble.getBoundingClientRect();
const normalizedPosition = ["top", "bottom"].includes(position) ? position : "top";
const maxLeft = Math.max(VIEWPORT_PADDING, window.innerWidth - bubbleRect.width - VIEWPORT_PADDING);
const left = Math.min(
Math.max(triggerRect.left + triggerRect.width / 2 - bubbleRect.width / 2, VIEWPORT_PADDING),
maxLeft
);
const top = normalizedPosition === "bottom"
? triggerRect.bottom + TOOLTIP_GAP
: triggerRect.top - bubbleRect.height - TOOLTIP_GAP;

setTooltipStyle({
left: `${left}px`,
top: `${Math.max(VIEWPORT_PADDING, top)}px`,
});
}, [position]);

const clearShowTimeout = () => {
if(timeoutRef.current !== null) {
clearTimeout(timeoutRef.current);
Expand All @@ -24,21 +58,51 @@ export default function Tooltip({ content, children, position = "top", delay = 5
const hideTooltip = () => {
clearShowTimeout();
setIsVisible(false);
setTooltipStyle(null);
};

useEffect(() => {
setIsMounted(true);
}, []);

useEffect(() => () => clearShowTimeout(), []);

useLayoutEffect(() => {
if(!isVisible) {
return;
}

updateTooltipPosition();
}, [isVisible, content, updateTooltipPosition]);

useEffect(() => {
if(!isVisible) {
return;
}

window.addEventListener("resize", updateTooltipPosition);
window.addEventListener("scroll", updateTooltipPosition, true);

return () => {
window.removeEventListener("resize", updateTooltipPosition);
window.removeEventListener("scroll", updateTooltipPosition, true);
};
}, [isVisible, updateTooltipPosition]);

if(!content) {
return children;
}

return (
<span className={`mf-tooltip${isVisible ? " mf-tooltip--visible" : ""}`} data-position={position} onMouseEnter={scheduleShow} onMouseLeave={hideTooltip} onFocus={scheduleShow} onBlur={hideTooltip}>
<span ref={triggerRef} className={`mf-tooltip${isVisible ? " mf-tooltip--visible" : ""}`} data-position={position} onMouseEnter={scheduleShow} onMouseLeave={hideTooltip} onFocus={scheduleShow} onBlur={hideTooltip}>
{children}

<span className="mf-tooltip__bubble" role="tooltip">
{content}
</span>

{isMounted && isVisible ? createPortal(
<span ref={bubbleRef} className={`mf-tooltip__bubble mf-tooltip__bubble--portal${tooltipStyle ? " mf-tooltip__bubble--visible" : ""}`} data-position={position} role="tooltip" style={tooltipStyle || undefined}>
{content}
</span>,
document.body
) : null}
</span>
);
}
2 changes: 1 addition & 1 deletion pegasus/i18n/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,7 @@
"description": "For 10,000 downloads on one project."
},
"hytalemodjam_2026": {
"title": "Hytale x HytaleModding ModJam 2026",
"title": "Hytale x HytaleModding ModJam",
"description": "Participant of Hytale x HytaleModding ModJam 2026."
}
}
Expand Down
2 changes: 1 addition & 1 deletion pegasus/i18n/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,7 @@
"description": "Por 10.000 descargas en un proyecto."
},
"hytalemodjam_2026": {
"title": "Hytale x HytaleModding ModJam 2026",
"title": "Hytale x HytaleModding ModJam",
"description": "Participante de Hytale x HytaleModding ModJam 2026."
}
}
Expand Down
2 changes: 1 addition & 1 deletion pegasus/i18n/messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,7 @@
"description": "Por 10.000 downloads em um projeto."
},
"hytalemodjam_2026": {
"title": "Hytale x HytaleModding ModJam 2026",
"title": "Hytale x HytaleModding ModJam",
"description": "Participante do Hytale x HytaleModding ModJam 2026."
}
}
Expand Down
2 changes: 1 addition & 1 deletion pegasus/i18n/messages/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,7 @@
"description": "За 10 000 скачиваний на одном проекте."
},
"hytalemodjam_2026": {
"title": "Hytale x HytaleModding ModJam 2026",
"title": "Hytale x HytaleModding ModJam",
"description": "Участник Hytale x HytaleModding ModJam 2026."
}
}
Expand Down
2 changes: 1 addition & 1 deletion pegasus/i18n/messages/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,7 @@
"description": "Tek bir projede 10.000 indirme için."
},
"hytalemodjam_2026": {
"title": "Hytale x HytaleModding ModJam 2026",
"title": "Hytale x HytaleModding ModJam",
"description": "Hytale x HytaleModding ModJam 2026 katılımcısı."
}
}
Expand Down
2 changes: 1 addition & 1 deletion pegasus/i18n/messages/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -1047,7 +1047,7 @@
"description": "За 10 000 завантажень одного проєкту."
},
"hytalemodjam_2026": {
"title": "Hytale x HytaleModding ModJam 2026",
"title": "Hytale x HytaleModding ModJam",
"description": "Учасник Hytale x HytaleModding ModJam 2026."
}
}
Expand Down
10 changes: 5 additions & 5 deletions pegasus/styles/Index.css
Original file line number Diff line number Diff line change
Expand Up @@ -4524,11 +4524,11 @@ body.light div .image-container::before {
background: #ffa347;
border-radius: 999px;
}
.badge--developer {
background: var(--theme-color-background);
font-size: 14px;
line-height: 22px;
padding: 2px 10px;
.badge--developer {
background: var(--theme-color-background);
font-size: 14px;
line-height: 22px;
padding: 2px 10px;
border-radius: 999px;
display: flex;
align-items: center;
Expand Down
18 changes: 17 additions & 1 deletion pegasus/styles/Settings.css
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@
left: 50%;
bottom: calc(100% + 10px);
transform: translateX(-50%);
z-index: 20;
z-index: 40;
box-shadow: var(--theme-shadow-popover);
background: var(--theme-color-background-content);
color: var(--theme-color-text-primary);
Expand Down Expand Up @@ -489,6 +489,22 @@
opacity: 1;
visibility: visible;
}
.mf-tooltip__bubble--portal {
position: fixed;
left: auto;
bottom: auto;
transform: none;
}
.mf-tooltip__bubble--portal[data-position="bottom"]::after {
top: auto;
bottom: 100%;
border-top: 0;
border-bottom: 7px solid var(--theme-color-background-content);
}
.mf-tooltip__bubble--visible {
opacity: 1;
visibility: visible;
}
.gallery-modal-preview {
border-radius: 16px;
overflow: hidden;
Expand Down
6 changes: 6 additions & 0 deletions pegasus/styles/Subsite.css
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@
overflow: hidden;
outline: none;
}
.subsite-achievements__item--link {
cursor: pointer;
}
.subsite-achievements__item--link img:hover {
opacity: 0.9;
}
.subsite-achievements__item img {
display: block;
width: 56px;
Expand Down
Loading