mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-10 05:16:19 +00:00
feat: add fullscreen media modal to profile hero for avatar display
This commit is contained in:
2
proto
2
proto
Submodule proto updated: 7a23620f93...6f11c99c57
@@ -0,0 +1,61 @@
|
|||||||
|
@use "../../scss/globals.scss";
|
||||||
|
|
||||||
|
.fullscreen-media-modal {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
|
||||||
|
&__close-button {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(globals.$spacing-unit * 4);
|
||||||
|
right: calc(globals.$spacing-unit * 3);
|
||||||
|
cursor: pointer;
|
||||||
|
color: globals.$body-color;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid globals.$border-color;
|
||||||
|
padding: globals.$spacing-unit;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all ease 0.2s;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image-container {
|
||||||
|
max-width: 90%;
|
||||||
|
max-height: 90%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__image {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 60vh;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||||
|
animation: image-appear 0.3s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes image-appear {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.85);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { useCallback, useEffect, useRef } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import { XIcon } from "@primer/octicons-react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { Backdrop } from "../backdrop/backdrop";
|
||||||
|
import "./fullscreen-media-modal.scss";
|
||||||
|
|
||||||
|
export interface FullscreenMediaModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
src: string | null | undefined;
|
||||||
|
alt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FullscreenMediaModal({
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
src,
|
||||||
|
alt,
|
||||||
|
}: FullscreenMediaModalProps) {
|
||||||
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
|
const { t } = useTranslation("modal");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
const onKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("keydown", onKeyDown);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", onKeyDown);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {};
|
||||||
|
}, [onClose, visible]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onMouseDown = (e: MouseEvent) => {
|
||||||
|
if (containerRef.current) {
|
||||||
|
const clickedOnImage = containerRef.current.contains(e.target as Node);
|
||||||
|
|
||||||
|
if (!clickedOnImage) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (visible) {
|
||||||
|
window.addEventListener("mousedown", onMouseDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("mousedown", onMouseDown);
|
||||||
|
};
|
||||||
|
}, [onClose, visible]);
|
||||||
|
|
||||||
|
if (!visible || !src) return null;
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<Backdrop>
|
||||||
|
<div className="fullscreen-media-modal" role="dialog" aria-label={alt}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onClose}
|
||||||
|
className="fullscreen-media-modal__close-button"
|
||||||
|
aria-label={t("close")}
|
||||||
|
>
|
||||||
|
<XIcon size={24} />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div
|
||||||
|
ref={containerRef}
|
||||||
|
className="fullscreen-media-modal__image-container"
|
||||||
|
>
|
||||||
|
<img src={src} alt={alt} className="fullscreen-media-modal__image" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Backdrop>,
|
||||||
|
document.body
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -20,3 +20,4 @@ export * from "./game-context-menu/game-context-menu";
|
|||||||
export * from "./game-context-menu/use-game-actions";
|
export * from "./game-context-menu/use-game-actions";
|
||||||
export * from "./star-rating/star-rating";
|
export * from "./star-rating/star-rating";
|
||||||
export * from "./search-dropdown/search-dropdown";
|
export * from "./search-dropdown/search-dropdown";
|
||||||
|
export * from "./fullscreen-media-modal/fullscreen-media-modal";
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ import {
|
|||||||
XCircleFillIcon,
|
XCircleFillIcon,
|
||||||
} from "@primer/octicons-react";
|
} from "@primer/octicons-react";
|
||||||
import { buildGameDetailsPath } from "@renderer/helpers";
|
import { buildGameDetailsPath } from "@renderer/helpers";
|
||||||
import { Avatar, Button, Link } from "@renderer/components";
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
FullscreenMediaModal,
|
||||||
|
Link,
|
||||||
|
} from "@renderer/components";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
useAppSelector,
|
useAppSelector,
|
||||||
@@ -33,6 +38,7 @@ type FriendAction =
|
|||||||
|
|
||||||
export function ProfileHero() {
|
export function ProfileHero() {
|
||||||
const [showEditProfileModal, setShowEditProfileModal] = useState(false);
|
const [showEditProfileModal, setShowEditProfileModal] = useState(false);
|
||||||
|
const [showFullscreenAvatar, setShowFullscreenAvatar] = useState(false);
|
||||||
const [isPerformingAction, setIsPerformingAction] = useState(false);
|
const [isPerformingAction, setIsPerformingAction] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -246,10 +252,12 @@ export function ProfileHero() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const handleAvatarClick = useCallback(() => {
|
const handleAvatarClick = useCallback(() => {
|
||||||
if (isMe) {
|
if (userProfile?.profileImageUrl) {
|
||||||
|
setShowFullscreenAvatar(true);
|
||||||
|
} else if (isMe) {
|
||||||
setShowEditProfileModal(true);
|
setShowEditProfileModal(true);
|
||||||
}
|
}
|
||||||
}, [isMe]);
|
}, [isMe, userProfile?.profileImageUrl]);
|
||||||
|
|
||||||
const currentGame = useMemo(() => {
|
const currentGame = useMemo(() => {
|
||||||
if (isMe) {
|
if (isMe) {
|
||||||
@@ -272,6 +280,13 @@ export function ProfileHero() {
|
|||||||
onClose={() => setShowEditProfileModal(false)}
|
onClose={() => setShowEditProfileModal(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FullscreenMediaModal
|
||||||
|
visible={showFullscreenAvatar}
|
||||||
|
onClose={() => setShowFullscreenAvatar(false)}
|
||||||
|
src={userProfile?.profileImageUrl}
|
||||||
|
alt={userProfile?.displayName}
|
||||||
|
/>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
className="profile-hero__content-box"
|
className="profile-hero__content-box"
|
||||||
style={{ background: !backgroundImage ? heroBackground : undefined }}
|
style={{ background: !backgroundImage ? heroBackground : undefined }}
|
||||||
|
|||||||
Reference in New Issue
Block a user