feat: profile cards on mentions
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
d50979b8ae
commit
7a1df4a178
@ -1,17 +1,25 @@
|
|||||||
import { NostrLink, NostrPrefix } from "@snort/system";
|
import { NostrLink, NostrPrefix } from "@snort/system";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
|
import { useHover } from "@uidotdev/usehooks";
|
||||||
|
|
||||||
import DisplayName from "Element/User/DisplayName";
|
import DisplayName from "Element/User/DisplayName";
|
||||||
|
import { ProfileCard } from "Element/User/ProfileCard";
|
||||||
import { ProfileLink } from "Element/User/ProfileLink";
|
import { ProfileLink } from "Element/User/ProfileLink";
|
||||||
|
|
||||||
export default function Mention({ link }: { link: NostrLink }) {
|
export default function Mention({ link }: { link: NostrLink }) {
|
||||||
|
const [ref, hovering] = useHover<HTMLAnchorElement>();
|
||||||
const profile = useUserProfile(link.id);
|
const profile = useUserProfile(link.id);
|
||||||
|
|
||||||
if (link.type !== NostrPrefix.Profile && link.type !== NostrPrefix.PublicKey) return;
|
if (link.type !== NostrPrefix.Profile && link.type !== NostrPrefix.PublicKey) return;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProfileLink pubkey={link.id} user={profile} onClick={e => e.stopPropagation()}>
|
<>
|
||||||
@<DisplayName user={profile} pubkey={link.id} />
|
<ProfileLink pubkey={link.id} user={profile} onClick={e => e.stopPropagation()}>
|
||||||
</ProfileLink>
|
<span ref={ref}>
|
||||||
|
@<DisplayName user={profile} pubkey={link.id} />
|
||||||
|
</span>
|
||||||
|
</ProfileLink>
|
||||||
|
<ProfileCard pubkey={link.id} user={profile} show={hovering} ref={ref} />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,10 @@ export function getDisplayNameOrPlaceHolder(user: UserMetadata | undefined, pubk
|
|||||||
const DisplayName = ({ pubkey, user }: DisplayNameProps) => {
|
const DisplayName = ({ pubkey, user }: DisplayNameProps) => {
|
||||||
const [name, isPlaceHolder] = useMemo(() => getDisplayNameOrPlaceHolder(user, pubkey), [user, pubkey]);
|
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;
|
export default DisplayName;
|
||||||
|
11
packages/app/src/Element/User/ProfileCard.css
Normal file
11
packages/app/src/Element/User/ProfileCard.css
Normal 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;
|
||||||
|
}
|
86
packages/app/src/Element/User/ProfileCard.tsx
Normal file
86
packages/app/src/Element/User/ProfileCard.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -43,15 +43,3 @@ a.pfp {
|
|||||||
background-color: var(--gray-superdark);
|
background-color: var(--gray-superdark);
|
||||||
transform: rotate(135deg);
|
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;
|
|
||||||
}
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import "./ProfileImage.css";
|
import "./ProfileImage.css";
|
||||||
|
|
||||||
import React, { ReactNode, useEffect, useState } from "react";
|
import React, { ReactNode } from "react";
|
||||||
import { HexKey, UserMetadata } from "@snort/system";
|
import { HexKey, UserMetadata } from "@snort/system";
|
||||||
import { useUserProfile } from "@snort/system-react";
|
import { useUserProfile } from "@snort/system-react";
|
||||||
import { useHover } from "@uidotdev/usehooks";
|
import { useHover } from "@uidotdev/usehooks";
|
||||||
import { ControlledMenu } from "@szhsin/react-menu";
|
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
|
|
||||||
import Avatar from "Element/User/Avatar";
|
import Avatar from "Element/User/Avatar";
|
||||||
@ -12,10 +11,8 @@ import Nip05 from "Element/User/Nip05";
|
|||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import Icon from "Icons/Icon";
|
import Icon from "Icons/Icon";
|
||||||
import DisplayName from "./DisplayName";
|
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 { ProfileLink } from "./ProfileLink";
|
||||||
|
import { ProfileCard } from "./ProfileCard";
|
||||||
|
|
||||||
export interface ProfileImageProps {
|
export interface ProfileImageProps {
|
||||||
pubkey: HexKey;
|
pubkey: HexKey;
|
||||||
@ -57,22 +54,6 @@ export default function ProfileImage({
|
|||||||
const { follows } = useLogin();
|
const { follows } = useLogin();
|
||||||
const doesFollow = follows.item.includes(pubkey);
|
const doesFollow = follows.item.includes(pubkey);
|
||||||
const [ref, hovering] = useHover<HTMLDivElement>();
|
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) {
|
function handleClick(e: React.MouseEvent) {
|
||||||
if (link === "") {
|
if (link === "") {
|
||||||
@ -118,38 +99,8 @@ export default function ProfileImage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function profileCard() {
|
function profileCard() {
|
||||||
if (showProfileCard ?? true) {
|
if ((showProfileCard ?? true) && user) {
|
||||||
return (
|
return <ProfileCard pubkey={pubkey} user={user} show={hovering} ref={ref} />;
|
||||||
<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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user