feat: profile cards on mentions
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Kieran 2023-10-17 23:01:53 +01:00
parent d50979b8ae
commit 7a1df4a178
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
6 changed files with 116 additions and 69 deletions

View File

@ -1,17 +1,25 @@
import { NostrLink, NostrPrefix } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import { useHover } from "@uidotdev/usehooks";
import DisplayName from "Element/User/DisplayName";
import { ProfileCard } from "Element/User/ProfileCard";
import { ProfileLink } from "Element/User/ProfileLink";
export default function Mention({ link }: { link: NostrLink }) {
const [ref, hovering] = useHover<HTMLAnchorElement>();
const profile = useUserProfile(link.id);
if (link.type !== NostrPrefix.Profile && link.type !== NostrPrefix.PublicKey) return;
return (
<ProfileLink pubkey={link.id} user={profile} onClick={e => e.stopPropagation()}>
@<DisplayName user={profile} pubkey={link.id} />
</ProfileLink>
<>
<ProfileLink pubkey={link.id} user={profile} onClick={e => e.stopPropagation()}>
<span ref={ref}>
@<DisplayName user={profile} pubkey={link.id} />
</span>
</ProfileLink>
<ProfileCard pubkey={link.id} user={profile} show={hovering} ref={ref} />
</>
);
}

View File

@ -33,7 +33,10 @@ export function getDisplayNameOrPlaceHolder(user: UserMetadata | undefined, pubk
const DisplayName = ({ pubkey, user }: DisplayNameProps) => {
const [name, isPlaceHolder] = useMemo(() => getDisplayNameOrPlaceHolder(user, pubkey), [user, pubkey]);
return <span className={isPlaceHolder ? "placeholder" : ""}>{name}</span>;
if (isPlaceHolder) {
return <span className="placeholder">{name}</span>;
}
return name;
};
export default DisplayName;

View File

@ -0,0 +1,11 @@
.profile-card {
width: 360px;
border-radius: 16px;
background: var(--gray-superdark);
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.05);
}
.profile-card > div {
color: white;
padding: 8px 12px;
}

View File

@ -0,0 +1,86 @@
import "./ProfileCard.css";
import { ControlledMenu } from "@szhsin/react-menu";
import { UserMetadata } from "@snort/system";
import FollowButton from "./FollowButton";
import ProfileImage from "./ProfileImage";
import { UserWebsiteLink } from "./UserWebsiteLink";
import Text from "Element/Text";
import { useEffect, useState } from "react";
interface RectElement {
getBoundingClientRect(): {
left: number;
right: number;
top: number;
bottom: number;
width: number;
height: number;
};
}
export function ProfileCard({
pubkey,
user,
show,
ref,
delay,
}: {
pubkey: string;
user?: UserMetadata;
show: boolean;
ref: React.RefObject<Element | RectElement>;
delay?: number;
}) {
const [showProfileMenu, setShowProfileMenu] = useState(false);
const [t, setT] = useState<ReturnType<typeof setTimeout>>();
useEffect(() => {
if (show) {
const tn = setTimeout(() => {
setShowProfileMenu(true);
}, delay ?? 1000);
setT(tn);
} else {
if (t) {
clearTimeout(t);
setT(undefined);
}
}
}, [show]);
if (!show && !showProfileMenu) return;
return (
<ControlledMenu
state={showProfileMenu ? "open" : "closed"}
anchorRef={ref}
menuClassName="profile-card"
onClose={() => setShowProfileMenu(false)}
align="end">
<div className="flex flex-col g8">
<div className="flex justify-between">
<ProfileImage pubkey={""} profile={user} showProfileCard={false} link="" />
<div className="flex g8">
{/*<button type="button" onClick={() => {
LoginStore.loginWithPubkey(pubkey, LoginSessionType.PublicKey, undefined, undefined, undefined, true);
}}>
<FormattedMessage defaultMessage="Stalk" />
</button>*/}
<FollowButton pubkey={pubkey} />
</div>
</div>
<Text
id={`profile-card-${pubkey}`}
content={user?.about ?? ""}
creator={pubkey}
tags={[]}
disableMedia={true}
disableLinkPreview={true}
truncate={250}
/>
<UserWebsiteLink user={user} />
</div>
</ControlledMenu>
);
}

View File

@ -43,15 +43,3 @@ a.pfp {
background-color: var(--gray-superdark);
transform: rotate(135deg);
}
.profile-card {
width: 360px;
border-radius: 16px;
background: var(--gray-superdark);
box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.05);
}
.profile-card > div {
color: white;
padding: 8px 12px;
}

View File

@ -1,10 +1,9 @@
import "./ProfileImage.css";
import React, { ReactNode, useEffect, useState } from "react";
import React, { ReactNode } from "react";
import { HexKey, UserMetadata } from "@snort/system";
import { useUserProfile } from "@snort/system-react";
import { useHover } from "@uidotdev/usehooks";
import { ControlledMenu } from "@szhsin/react-menu";
import classNames from "classnames";
import Avatar from "Element/User/Avatar";
@ -12,10 +11,8 @@ import Nip05 from "Element/User/Nip05";
import useLogin from "Hooks/useLogin";
import Icon from "Icons/Icon";
import DisplayName from "./DisplayName";
import Text from "Element/Text";
import FollowButton from "Element/User/FollowButton";
import { UserWebsiteLink } from "Element/User/UserWebsiteLink";
import { ProfileLink } from "./ProfileLink";
import { ProfileCard } from "./ProfileCard";
export interface ProfileImageProps {
pubkey: HexKey;
@ -57,22 +54,6 @@ export default function ProfileImage({
const { follows } = useLogin();
const doesFollow = follows.item.includes(pubkey);
const [ref, hovering] = useHover<HTMLDivElement>();
const [showProfileMenu, setShowProfileMenu] = useState(false);
const [t, setT] = useState<ReturnType<typeof setTimeout>>();
useEffect(() => {
if (hovering) {
const tn = setTimeout(() => {
setShowProfileMenu(true);
}, 1000);
setT(tn);
} else {
if (t) {
clearTimeout(t);
setT(undefined);
}
}
}, [hovering]);
function handleClick(e: React.MouseEvent) {
if (link === "") {
@ -118,38 +99,8 @@ export default function ProfileImage({
}
function profileCard() {
if (showProfileCard ?? true) {
return (
<ControlledMenu
state={showProfileMenu ? "open" : "closed"}
anchorRef={ref}
menuClassName="profile-card"
onClose={() => setShowProfileMenu(false)}>
<div className="flex flex-col g8">
<div className="flex justify-between">
<ProfileImage pubkey={""} profile={user} showProfileCard={false} link="" />
<div className="flex g8">
{/*<button type="button" onClick={() => {
LoginStore.loginWithPubkey(pubkey, LoginSessionType.PublicKey, undefined, undefined, undefined, true);
}}>
<FormattedMessage defaultMessage="Stalk" />
</button>*/}
<FollowButton pubkey={pubkey} />
</div>
</div>
<Text
id={`profile-card-${pubkey}`}
content={user?.about ?? ""}
creator={pubkey}
tags={[]}
disableMedia={true}
disableLinkPreview={true}
truncate={250}
/>
<UserWebsiteLink user={user} />
</div>
</ControlledMenu>
);
if ((showProfileCard ?? true) && user) {
return <ProfileCard pubkey={pubkey} user={user} show={hovering} ref={ref} />;
}
return null;
}