mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-22 02:12:38 +01:00
new tooltips
This commit is contained in:
parent
2c587af4a8
commit
71e47c96cd
@ -8,9 +8,7 @@ import Presence from "../stores/objects/Presence";
|
||||
import User from "../stores/objects/User";
|
||||
import Container from "./Container";
|
||||
import Floating from "./floating/Floating";
|
||||
import FloatingContent from "./floating/FloatingContent";
|
||||
import FloatingTrigger from "./floating/FloatingTrigger";
|
||||
import UserProfilePopout from "./floating/UserProfilePopout";
|
||||
|
||||
const Wrapper = styled(Container)<{ size: number; hasClick?: boolean }>`
|
||||
width: ${(props) => props.size}px;
|
||||
@ -52,6 +50,7 @@ interface Props {
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
showPresence?: boolean;
|
||||
}
|
||||
|
||||
function Avatar(props: Props) {
|
||||
@ -65,7 +64,13 @@ function Avatar(props: Props) {
|
||||
const Base = props.onClick ? Yes(props.onClick) : FloatingTrigger;
|
||||
|
||||
return (
|
||||
<Floating placement="right-start">
|
||||
<Floating
|
||||
placement="right-start"
|
||||
type="userPopout"
|
||||
props={{
|
||||
user: user as unknown as User,
|
||||
}}
|
||||
>
|
||||
<Base>
|
||||
<Wrapper size={props.size ?? 32} style={props.style} ref={ref} hasClick={props.onClick !== null}>
|
||||
<img
|
||||
@ -77,15 +82,14 @@ function Avatar(props: Props) {
|
||||
height={props.size ?? 32}
|
||||
loading="eager"
|
||||
/>
|
||||
{props.presence && props.presence.status !== PresenceUpdateStatus.Offline && (
|
||||
<StatusDot color={app.theme.getStatusColor(props.presence.status)} {...props.statusDotStyle} />
|
||||
{props.showPresence && (
|
||||
<StatusDot
|
||||
color={app.theme.getStatusColor(props.presence?.status ?? PresenceUpdateStatus.Offline)}
|
||||
{...props.statusDotStyle}
|
||||
/>
|
||||
)}
|
||||
</Wrapper>
|
||||
</Base>
|
||||
|
||||
<FloatingContent>
|
||||
<UserProfilePopout user={user as unknown as User} />
|
||||
</FloatingContent>
|
||||
</Floating>
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ import { useNavigate } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import Channel from "../../stores/objects/Channel";
|
||||
import Icon from "../Icon";
|
||||
import Tooltip from "../Tooltip";
|
||||
import Floating from "../floating/Floating";
|
||||
import FloatingTrigger from "../floating/FloatingTrigger";
|
||||
|
||||
const ListItem = styled.div<{ isCategory?: boolean }>`
|
||||
padding: ${(props) => (props.isCategory ? "16px 8px 0 0" : "1px 8px 0 0")};
|
||||
@ -93,7 +94,15 @@ function ChannelListItem({ channel, isCategory, active }: Props) {
|
||||
</Text>
|
||||
</div>
|
||||
{isCategory && (
|
||||
<Tooltip title="Create Channel" placement="top">
|
||||
<Floating
|
||||
placement="top"
|
||||
type="tooltip"
|
||||
offset={10}
|
||||
props={{
|
||||
content: <span>Create Channel</span>,
|
||||
}}
|
||||
>
|
||||
<FloatingTrigger>
|
||||
<span>
|
||||
<Icon
|
||||
icon="mdiPlus"
|
||||
@ -104,7 +113,8 @@ function ChannelListItem({ channel, isCategory, active }: Props) {
|
||||
color={hovered ? "var(--text)" : "var(--text-secondary)"}
|
||||
/>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</FloatingTrigger>
|
||||
</Floating>
|
||||
)}
|
||||
</Wrapper>
|
||||
</ListItem>
|
||||
|
@ -3,7 +3,8 @@
|
||||
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import Tooltip from "./Tooltip";
|
||||
import Floating from "./floating/Floating";
|
||||
import FloatingTrigger from "./floating/FloatingTrigger";
|
||||
|
||||
const Actions = styled.div`
|
||||
position: absolute;
|
||||
@ -57,9 +58,17 @@ function CodeBlock(props: Props) {
|
||||
}}
|
||||
>
|
||||
<Actions>
|
||||
<Tooltip title="Copy to Clipboard" placement="top">
|
||||
<Floating
|
||||
placement="top"
|
||||
type="tooltip"
|
||||
props={{
|
||||
content: <span>"Copy to Clipboard</span>,
|
||||
}}
|
||||
>
|
||||
<FloatingTrigger>
|
||||
<a onClick={onCopy}>{text}</a>
|
||||
</Tooltip>
|
||||
</FloatingTrigger>
|
||||
</Floating>
|
||||
</Actions>
|
||||
{props.children}
|
||||
</pre>
|
||||
|
@ -10,7 +10,8 @@ import { Permissions } from "../utils/Permissions";
|
||||
import REST from "../utils/REST";
|
||||
import Container from "./Container";
|
||||
import SidebarPill, { PillType } from "./SidebarPill";
|
||||
import Tooltip from "./Tooltip";
|
||||
import Floating from "./floating/Floating";
|
||||
import FloatingTrigger from "./floating/FloatingTrigger";
|
||||
|
||||
export const GuildSidebarListItem = styled.div`
|
||||
position: relative;
|
||||
@ -70,7 +71,15 @@ function GuildItem({ guild, active }: Props) {
|
||||
return (
|
||||
<GuildSidebarListItem>
|
||||
<SidebarPill type={pillType} />
|
||||
<Tooltip title={guild.name} placement="right">
|
||||
<Floating
|
||||
placement="right"
|
||||
type="tooltip"
|
||||
offset={20}
|
||||
props={{
|
||||
content: <span>{guild.name}</span>,
|
||||
}}
|
||||
>
|
||||
<FloatingTrigger>
|
||||
<Wrapper
|
||||
onClick={doNavigate}
|
||||
active={active}
|
||||
@ -97,7 +106,8 @@ function GuildItem({ guild, active }: Props) {
|
||||
</span>
|
||||
)}
|
||||
</Wrapper>
|
||||
</Tooltip>
|
||||
</FloatingTrigger>
|
||||
</Floating>
|
||||
</GuildSidebarListItem>
|
||||
);
|
||||
}
|
||||
|
@ -2,12 +2,9 @@ import { PresenceUpdateStatus } from "@spacebarchat/spacebar-api-types/v9";
|
||||
import styled from "styled-components";
|
||||
import { useAppStore } from "../../stores/AppStore";
|
||||
import GuildMember from "../../stores/objects/GuildMember";
|
||||
import User from "../../stores/objects/User";
|
||||
import Avatar from "../Avatar";
|
||||
import Floating from "../floating/Floating";
|
||||
import FloatingContent from "../floating/FloatingContent";
|
||||
import FloatingTrigger from "../floating/FloatingTrigger";
|
||||
import UserProfilePopout from "../floating/UserProfilePopout";
|
||||
|
||||
const ListItem = styled(FloatingTrigger)<{ isCategory?: boolean }>`
|
||||
padding: ${(props) => (props.isCategory ? "16px 8px 0 0" : "1px 8px 0 0")};
|
||||
@ -64,15 +61,23 @@ interface Props {
|
||||
|
||||
function MemberListItem({ item }: Props) {
|
||||
const app = useAppStore();
|
||||
const presence = app.presences.get(item.guild.id)?.get(item.user!.id);
|
||||
const presence = app.presences.get(item.user!.id);
|
||||
|
||||
return (
|
||||
<Floating placement="right-start">
|
||||
<Floating
|
||||
placement="right-start"
|
||||
type="userPopout"
|
||||
offset={20}
|
||||
props={{
|
||||
user: item.user!,
|
||||
member: item,
|
||||
}}
|
||||
>
|
||||
<ListItem key={item.user?.id}>
|
||||
<Container>
|
||||
<Wrapper offline={presence?.status === PresenceUpdateStatus.Offline}>
|
||||
<AvatarWrapper>
|
||||
<Avatar user={item.user!} size={32} presence={presence} />
|
||||
<Avatar user={item.user!} size={32} presence={presence} showPresence />
|
||||
</AvatarWrapper>
|
||||
<TextWrapper>
|
||||
<Text color={item.roleColor}>{item.nick ?? item.user?.username}</Text>
|
||||
@ -80,10 +85,6 @@ function MemberListItem({ item }: Props) {
|
||||
</Wrapper>
|
||||
</Container>
|
||||
</ListItem>
|
||||
|
||||
<FloatingContent>
|
||||
<UserProfilePopout user={app.account! as unknown as User} member={item} />
|
||||
</FloatingContent>
|
||||
</Floating>
|
||||
);
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ import Container from "./Container";
|
||||
import { GuildSidebarListItem } from "./GuildItem";
|
||||
import Icon, { IconProps } from "./Icon";
|
||||
import SidebarPill, { PillType } from "./SidebarPill";
|
||||
import Tooltip from "./Tooltip";
|
||||
import Floating from "./floating/Floating";
|
||||
import FloatingTrigger from "./floating/FloatingTrigger";
|
||||
|
||||
const Wrapper = styled(Container)<{
|
||||
margin?: boolean;
|
||||
@ -60,7 +61,15 @@ function SidebarAction(props: Props) {
|
||||
return (
|
||||
<GuildSidebarListItem>
|
||||
<SidebarPill type={pillType} />
|
||||
<Tooltip title={props.tooltip} placement="right">
|
||||
<Floating
|
||||
placement="right"
|
||||
type="tooltip"
|
||||
offset={20}
|
||||
props={{
|
||||
content: <span>{props.tooltip}</span>,
|
||||
}}
|
||||
>
|
||||
<FloatingTrigger>
|
||||
<Wrapper
|
||||
onClick={props.action}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
@ -78,7 +87,8 @@ function SidebarAction(props: Props) {
|
||||
)}
|
||||
{props.label && <span>{props.label}</span>}
|
||||
</Wrapper>
|
||||
</Tooltip>
|
||||
</FloatingTrigger>
|
||||
</Floating>
|
||||
</GuildSidebarListItem>
|
||||
);
|
||||
}
|
||||
|
@ -1,21 +1,24 @@
|
||||
import MuiTooltip, { TooltipProps as MuiTooltipProps, tooltipClasses } from "@mui/material/Tooltip";
|
||||
import styled from "styled-components";
|
||||
import { FloatingProps } from "./floating/Floating";
|
||||
|
||||
export default styled(({ className, ...props }: MuiTooltipProps) => (
|
||||
<MuiTooltip {...props} arrow classes={{ popper: className }} />
|
||||
))(() => ({
|
||||
[`& .${tooltipClasses.popper}`]: {
|
||||
maxWidth: 200,
|
||||
borderRadius: 5,
|
||||
},
|
||||
[`& .${tooltipClasses.arrow}`]: {
|
||||
color: "var(--background-tertiary)",
|
||||
},
|
||||
[`& .${tooltipClasses.tooltip}`]: {
|
||||
backgroundColor: "var(--background-tertiary)",
|
||||
fontSize: "14px",
|
||||
padding: "8px 12px",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
},
|
||||
}));
|
||||
const Container = styled.div`
|
||||
background-color: var(--background-tertiary);
|
||||
line-height: 16px;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
padding: 8px 12px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 250px;
|
||||
border-radius: 4px;
|
||||
color: var(--text);
|
||||
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.5);
|
||||
`;
|
||||
|
||||
function Tooltip(props: FloatingProps<"tooltip">) {
|
||||
if (!props) return null;
|
||||
return <Container aria-label={props.aria}>{props.content}</Container>;
|
||||
}
|
||||
|
||||
export default Tooltip;
|
||||
|
@ -4,11 +4,8 @@ import User from "../stores/objects/User";
|
||||
import Avatar from "./Avatar";
|
||||
import Icon from "./Icon";
|
||||
import IconButton from "./IconButton";
|
||||
import Tooltip from "./Tooltip";
|
||||
import Floating from "./floating/Floating";
|
||||
import FloatingContent from "./floating/FloatingContent";
|
||||
import FloatingTrigger from "./floating/FloatingTrigger";
|
||||
import UserProfilePopout from "./floating/UserProfilePopout";
|
||||
|
||||
const Section = styled.section`
|
||||
flex: 0 0 auto;
|
||||
@ -76,7 +73,13 @@ function UserPanel() {
|
||||
const openSettingsModal = () => {};
|
||||
|
||||
return (
|
||||
<Floating placement="bottom">
|
||||
<Floating
|
||||
placement="bottom"
|
||||
type="userPopout"
|
||||
props={{
|
||||
user: app.account! as unknown as User,
|
||||
}}
|
||||
>
|
||||
<Section>
|
||||
<Container>
|
||||
<AvatarWrapper>
|
||||
@ -88,18 +91,23 @@ function UserPanel() {
|
||||
</AvatarWrapper>
|
||||
|
||||
<ActionsWrapper>
|
||||
<Tooltip title="Settings">
|
||||
<Floating
|
||||
placement="top"
|
||||
type="tooltip"
|
||||
offset={10}
|
||||
props={{
|
||||
content: <span>Settings</span>,
|
||||
}}
|
||||
>
|
||||
<FloatingTrigger>
|
||||
<IconButton aria-label="settings" color="#fff" onClick={openSettingsModal}>
|
||||
<Icon icon="mdiCog" size="20px" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</FloatingTrigger>
|
||||
</Floating>
|
||||
</ActionsWrapper>
|
||||
</Container>
|
||||
</Section>
|
||||
|
||||
<FloatingContent>
|
||||
<UserProfilePopout user={app.account! as unknown as User} />
|
||||
</FloatingContent>
|
||||
</Floating>
|
||||
);
|
||||
}
|
||||
|
@ -1,14 +1,86 @@
|
||||
import { FloatingArrow, FloatingPortal, Placement } from "@floating-ui/react";
|
||||
import { motion } from "framer-motion";
|
||||
import { FloatingContext } from "../../contexts/FloatingContext";
|
||||
import useFloating, { FloatingOptions } from "../../hooks/useFloating";
|
||||
import useFloating from "../../hooks/useFloating";
|
||||
import GuildMember from "../../stores/objects/GuildMember";
|
||||
import User from "../../stores/objects/User";
|
||||
import Tooltip from "../Tooltip";
|
||||
import UserProfilePopout from "./UserProfilePopout";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type Components = Record<string, React.FC<any>>;
|
||||
|
||||
const components: Components = {
|
||||
userPopout: UserProfilePopout,
|
||||
tooltip: Tooltip,
|
||||
};
|
||||
|
||||
export type FloatingOptions = {
|
||||
initialOpen?: boolean;
|
||||
placement?: Placement;
|
||||
offset?: number;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
} & (
|
||||
| {
|
||||
type: "userPopout";
|
||||
props: {
|
||||
user: User;
|
||||
member?: GuildMember;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: "tooltip";
|
||||
props: {
|
||||
content: JSX.Element;
|
||||
aria?: string;
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export type FloatingProps<T extends FloatingOptions["type"]> = (FloatingOptions & {
|
||||
type: T;
|
||||
})["props"];
|
||||
|
||||
function Floating({
|
||||
type,
|
||||
children,
|
||||
props,
|
||||
...restOptions
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
} & FloatingOptions) {
|
||||
const floating = useFloating({ ...restOptions });
|
||||
return <FloatingContext.Provider value={floating}>{children}</FloatingContext.Provider>;
|
||||
const floating = useFloating({ type, ...restOptions });
|
||||
|
||||
const Component = components[type];
|
||||
|
||||
return (
|
||||
<FloatingContext.Provider value={floating}>
|
||||
{children}
|
||||
{Component && floating.open && (
|
||||
<FloatingPortal>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.1, easing: [0.87, 0, 0.13, 1] }}
|
||||
ref={floating.refs.setFloating}
|
||||
style={{ ...floating.context.floatingStyles, zIndex: 1000, outline: "none" }}
|
||||
{...floating.getFloatingProps()}
|
||||
>
|
||||
<Component {...props} />
|
||||
{type === "tooltip" && (
|
||||
<FloatingArrow
|
||||
ref={floating.arrowRef}
|
||||
context={floating.context}
|
||||
fill="var(--background-tertiary)"
|
||||
/>
|
||||
)}
|
||||
</motion.div>
|
||||
</FloatingPortal>
|
||||
)}
|
||||
</FloatingContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export default Floating;
|
||||
|
@ -9,7 +9,10 @@ import { HorizontalDivider } from "../Divider";
|
||||
import { CDNRoutes, ImageFormat } from "@spacebarchat/spacebar-api-types/v9";
|
||||
import dayjs from "dayjs";
|
||||
import { ReactComponent as SpacebarLogoBlue } from "../../assets/images/logo/Spacebar_Icon.svg";
|
||||
import { useAppStore } from "../../stores/AppStore";
|
||||
import REST from "../../utils/REST";
|
||||
import Floating from "./Floating";
|
||||
import FloatingTrigger from "./FloatingTrigger";
|
||||
|
||||
const Container = styled.div`
|
||||
background-color: #252525;
|
||||
@ -158,10 +161,12 @@ interface Props {
|
||||
}
|
||||
|
||||
function UserProfilePopout({ user, member }: Props) {
|
||||
const app = useAppStore();
|
||||
const logger = useLogger("UserProfilePopout");
|
||||
|
||||
const id = user.id;
|
||||
const { timestamp: createdAt } = Snowflake.deconstruct(id);
|
||||
const presence = app.presences.get(user.id);
|
||||
|
||||
return (
|
||||
<Container>
|
||||
@ -176,11 +181,12 @@ function UserProfilePopout({ user, member }: Props) {
|
||||
logger.debug("open profile modal");
|
||||
}}
|
||||
user={user}
|
||||
// presence={presence}
|
||||
presence={presence}
|
||||
statusDotStyle={{
|
||||
width: 16,
|
||||
height: 16,
|
||||
}}
|
||||
showPresence
|
||||
/>
|
||||
</Top>
|
||||
<Bottom>
|
||||
@ -204,11 +210,19 @@ function UserProfilePopout({ user, member }: Props) {
|
||||
<Section>
|
||||
<Heading>Member Since</Heading>
|
||||
<MemberSinceContainer>
|
||||
{/* <Tooltip title="Spacebar" placement="top"> */}
|
||||
<Floating
|
||||
placement="top"
|
||||
type="tooltip"
|
||||
props={{
|
||||
content: <span>Spacebar</span>,
|
||||
}}
|
||||
>
|
||||
<FloatingTrigger>
|
||||
<div>
|
||||
<SpacebarLogoBlue width={16} height={16} style={{ borderRadius: "50%" }} />
|
||||
</div>
|
||||
{/* </Tooltip> */}
|
||||
</FloatingTrigger>
|
||||
</Floating>
|
||||
<MemberSinceText>{dayjs(createdAt).format("MMM D, YYYY")}</MemberSinceText>
|
||||
{member && (
|
||||
<>
|
||||
@ -221,11 +235,22 @@ function UserProfilePopout({ user, member }: Props) {
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* <Tooltip title={member.guild.name} placement="top"> */}
|
||||
<Floating
|
||||
placement="top"
|
||||
type="tooltip"
|
||||
props={{
|
||||
content: <span>{member.guild.name}</span>,
|
||||
}}
|
||||
>
|
||||
<FloatingTrigger>
|
||||
{member.guild.icon ? (
|
||||
<img
|
||||
src={REST.makeCDNUrl(
|
||||
CDNRoutes.guildIcon(member.guild.id, member.guild.icon, ImageFormat.PNG),
|
||||
CDNRoutes.guildIcon(
|
||||
member.guild.id,
|
||||
member.guild.icon,
|
||||
ImageFormat.PNG,
|
||||
),
|
||||
)}
|
||||
width={16}
|
||||
height={16}
|
||||
@ -239,7 +264,8 @@ function UserProfilePopout({ user, member }: Props) {
|
||||
<AcronymText>{member.guild.acronym}</AcronymText>
|
||||
</Acronym>
|
||||
)}
|
||||
{/* </Tooltip> */}
|
||||
</FloatingTrigger>
|
||||
</Floating>
|
||||
<MemberSinceText>{dayjs(member.joined_at).format("MMM D, YYYY")}</MemberSinceText>
|
||||
</>
|
||||
)}
|
||||
@ -250,8 +276,8 @@ function UserProfilePopout({ user, member }: Props) {
|
||||
<Section>
|
||||
<Heading>{member.roles.length ? "Roles" : "No Roles"}</Heading>
|
||||
<RoleList>
|
||||
{member.roles.map((x) => (
|
||||
<RolePill>
|
||||
{member.roles.map((x, i) => (
|
||||
<RolePill key={i}>
|
||||
<RolePillDot color={x.color} />
|
||||
<RoleName>{x.name}</RoleName>
|
||||
</RolePill>
|
||||
|
@ -1,7 +1,8 @@
|
||||
import dayjs from "dayjs";
|
||||
import { memo } from "react";
|
||||
import styled from "styled-components";
|
||||
import Tooltip from "../Tooltip";
|
||||
import Floating from "../floating/Floating";
|
||||
import FloatingTrigger from "../floating/FloatingTrigger";
|
||||
|
||||
const Container = styled.div`
|
||||
background-color: hsl(var(--background-tertiary-hsl) / 0.3);
|
||||
@ -43,9 +44,17 @@ function Timestamp({ timestamp, style }: Props) {
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Tooltip title={date.format("dddd, MMMM MM, h:mm A")} placement="top">
|
||||
<Floating
|
||||
placement="top"
|
||||
type="tooltip"
|
||||
props={{
|
||||
content: <span>{date.format("dddd, MMMM MM, h:mm A")}</span>,
|
||||
}}
|
||||
>
|
||||
<FloatingTrigger>
|
||||
<span>{value}</span>
|
||||
</Tooltip>
|
||||
</FloatingTrigger>
|
||||
</Floating>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
@ -6,7 +6,8 @@ import { useAppStore } from "../../stores/AppStore";
|
||||
import Channel from "../../stores/objects/Channel";
|
||||
import Icon from "../Icon";
|
||||
import { SectionHeader } from "../SectionHeader";
|
||||
import Tooltip from "../Tooltip";
|
||||
import Floating from "../floating/Floating";
|
||||
import FloatingTrigger from "../floating/FloatingTrigger";
|
||||
|
||||
const IconButton = styled.button`
|
||||
margin: 0;
|
||||
@ -120,13 +121,21 @@ function ActionItem({ icon, active, ariaLabel, tooltip, onClick }: ActionItemPro
|
||||
const logger = useLogger("ChatHeader.tsx:ActionItem");
|
||||
|
||||
return (
|
||||
<Tooltip title={tooltip}>
|
||||
<Floating
|
||||
placement="bottom"
|
||||
type="tooltip"
|
||||
props={{
|
||||
content: <span>{tooltip}</span>,
|
||||
}}
|
||||
>
|
||||
<FloatingTrigger>
|
||||
<IconWrapper>
|
||||
<IconButton onClick={onClick}>
|
||||
<CustomIcon $active={active} icon={icon} size="24px" aria-label={ariaLabel} />
|
||||
</IconButton>
|
||||
</IconWrapper>
|
||||
</Tooltip>
|
||||
</FloatingTrigger>
|
||||
</Floating>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,7 @@ import styled from "styled-components";
|
||||
import { useAppStore } from "../../stores/AppStore";
|
||||
import { MessageLike } from "../../stores/objects/Message";
|
||||
import Floating from "../floating/Floating";
|
||||
import FloatingContent from "../floating/FloatingContent";
|
||||
import FloatingTrigger from "../floating/FloatingTrigger";
|
||||
import UserProfilePopout from "../floating/UserProfilePopout";
|
||||
|
||||
const Container = styled.div`
|
||||
font-size: 16px;
|
||||
@ -39,7 +37,13 @@ function MessageAuthor({ message }: Props) {
|
||||
}, [message]);
|
||||
|
||||
return (
|
||||
<Floating placement="right-start">
|
||||
<Floating
|
||||
placement="right-start"
|
||||
type="userPopout"
|
||||
props={{
|
||||
user: message.author,
|
||||
}}
|
||||
>
|
||||
<FloatingTrigger>
|
||||
<Container
|
||||
ref={ref}
|
||||
@ -50,9 +54,6 @@ function MessageAuthor({ message }: Props) {
|
||||
{message.author.username}
|
||||
</Container>
|
||||
</FloatingTrigger>
|
||||
<FloatingContent>
|
||||
<UserProfilePopout user={message.author} />
|
||||
</FloatingContent>
|
||||
</Floating>
|
||||
);
|
||||
}
|
||||
|
@ -3,7 +3,8 @@ import { observer } from "mobx-react-lite";
|
||||
import styled from "styled-components";
|
||||
import Message, { MessageLike } from "../../stores/objects/Message";
|
||||
import { calendarStrings } from "../../utils/i18n";
|
||||
import Tooltip from "../Tooltip";
|
||||
import Floating from "../floating/Floating";
|
||||
import FloatingTrigger from "../floating/FloatingTrigger";
|
||||
|
||||
interface Props {
|
||||
header?: boolean;
|
||||
@ -96,18 +97,33 @@ export const MessageDetails = observer(({ message, position }: { message: Messag
|
||||
if (message instanceof Message && message.edited_timestamp) {
|
||||
return (
|
||||
<div className="messageTimestampWrapper">
|
||||
<Tooltip title={dayjs(message.timestamp).format("dddd, MMMM D, YYYY h:mm A")} placement="top">
|
||||
<Floating
|
||||
placement="top"
|
||||
type="tooltip"
|
||||
props={{
|
||||
content: <span>{dayjs(message.timestamp).format("dddd, MMMM D, YYYY h:mm A")}</span>,
|
||||
}}
|
||||
>
|
||||
<FloatingTrigger>
|
||||
<time className="copyTime" dateTime={message.edited_timestamp.toISOString()}>
|
||||
{dayjs(message.edited_timestamp).format("h:mm A")}
|
||||
</time>
|
||||
</Tooltip>
|
||||
</FloatingTrigger>
|
||||
</Floating>
|
||||
<span className="edited">
|
||||
<Tooltip
|
||||
title={dayjs(message.edited_timestamp).format("dddd, MMMM D, YYYY h:mm A")}
|
||||
<Floating
|
||||
placement="top"
|
||||
type="tooltip"
|
||||
props={{
|
||||
content: (
|
||||
<span>{dayjs(message.edited_timestamp).format("dddd, MMMM D, YYYY h:mm A")}</span>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<FloatingTrigger>
|
||||
<span>(edited)</span>
|
||||
</Tooltip>
|
||||
</FloatingTrigger>
|
||||
</Floating>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
@ -121,15 +137,31 @@ export const MessageDetails = observer(({ message, position }: { message: Messag
|
||||
|
||||
return (
|
||||
<DetailBase>
|
||||
<Tooltip title={dayjs(message.timestamp).format("dddd, MMMM D, YYYY h:mm A")} placement="top">
|
||||
<Floating
|
||||
placement="top"
|
||||
type="tooltip"
|
||||
props={{
|
||||
content: <span>{dayjs(message.timestamp).format("dddd, MMMM D, YYYY h:mm A")}</span>,
|
||||
}}
|
||||
>
|
||||
<FloatingTrigger>
|
||||
<time className="copyTime" dateTime={message.timestamp.toISOString()}>
|
||||
{dayjs(message.timestamp).calendar(undefined, calendarStrings)}
|
||||
</time>
|
||||
</Tooltip>
|
||||
</FloatingTrigger>
|
||||
</Floating>
|
||||
{message instanceof Message && message.edited_timestamp && (
|
||||
<Tooltip title={dayjs(message.edited_timestamp).format("dddd, MMMM D, YYYY h:mm A")} placement="top">
|
||||
<Floating
|
||||
placement="top"
|
||||
type="tooltip"
|
||||
props={{
|
||||
content: <span>{dayjs(message.edited_timestamp).format("dddd, MMMM D, YYYY h:mm A")}</span>,
|
||||
}}
|
||||
>
|
||||
<FloatingTrigger>
|
||||
<span className="edited">(edited)</span>
|
||||
</Tooltip>
|
||||
</FloatingTrigger>
|
||||
</Floating>
|
||||
)}
|
||||
</DetailBase>
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {
|
||||
Placement,
|
||||
arrow,
|
||||
autoUpdate,
|
||||
flip,
|
||||
offset,
|
||||
@ -7,27 +7,24 @@ import {
|
||||
useClick,
|
||||
useDismiss,
|
||||
useFloating,
|
||||
useFocus,
|
||||
useHover,
|
||||
useInteractions,
|
||||
useRole,
|
||||
} from "@floating-ui/react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
export interface FloatingOptions {
|
||||
initialOpen?: boolean;
|
||||
placement?: Placement;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { FloatingOptions } from "../components/floating/Floating";
|
||||
|
||||
export default function ({
|
||||
type,
|
||||
initialOpen = false,
|
||||
offset: offsetMiddlewareOffset,
|
||||
placement,
|
||||
open: controlledOpen,
|
||||
onOpenChange: setControlledOpen,
|
||||
}: // config,
|
||||
FloatingOptions) {
|
||||
}: Omit<FloatingOptions, "props">) {
|
||||
const [uncontrolledOpen, setUncontrolledOpen] = useState(initialOpen);
|
||||
// const [key, setKey] = useState<string>();
|
||||
const arrowRef = useRef<SVGSVGElement>(null);
|
||||
|
||||
const open = controlledOpen ?? uncontrolledOpen;
|
||||
const setOpen = setControlledOpen ?? setUncontrolledOpen;
|
||||
@ -37,32 +34,38 @@ FloatingOptions) {
|
||||
open,
|
||||
onOpenChange: setOpen,
|
||||
whileElementsMounted: autoUpdate,
|
||||
middleware: [offset(5), flip(), shift()],
|
||||
middleware: [
|
||||
offset(type === "tooltip" && !offsetMiddlewareOffset ? 10 : offsetMiddlewareOffset ?? 5),
|
||||
flip(),
|
||||
shift({
|
||||
padding: 8,
|
||||
}),
|
||||
arrow({
|
||||
element: arrowRef,
|
||||
padding: 4,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const context = data.context;
|
||||
|
||||
const click = useClick(context);
|
||||
const click = useClick(context, {
|
||||
enabled: type !== "tooltip",
|
||||
});
|
||||
const dismiss = useDismiss(context);
|
||||
const role = useRole(context);
|
||||
const interactions = useInteractions([click, dismiss, role]);
|
||||
const role = useRole(context, {
|
||||
role: type === "tooltip" ? "tooltip" : undefined,
|
||||
});
|
||||
|
||||
// useEffect(() => {
|
||||
// if (open) {
|
||||
// const k = floatingController.add({
|
||||
// type,
|
||||
// data: {
|
||||
// ...interactions,
|
||||
// ...data,
|
||||
// },
|
||||
// open,
|
||||
// props: config,
|
||||
// });
|
||||
// setKey(k);
|
||||
// } else {
|
||||
// key && floatingController.remove(key);
|
||||
// }
|
||||
// }, [open]);
|
||||
const hover = useHover(context, {
|
||||
move: false,
|
||||
enabled: type == "tooltip",
|
||||
});
|
||||
const focus = useFocus(context, {
|
||||
enabled: type == "tooltip",
|
||||
});
|
||||
|
||||
const interactions = useInteractions([click, dismiss, role, hover, focus]);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
@ -70,6 +73,7 @@ FloatingOptions) {
|
||||
setOpen,
|
||||
...interactions,
|
||||
...data,
|
||||
arrowRef,
|
||||
}),
|
||||
[open, setOpen, interactions, data],
|
||||
);
|
||||
|
@ -1,11 +1,11 @@
|
||||
import type { GatewayPresenceUpdateDispatchData, Snowflake } from "@spacebarchat/spacebar-api-types/v9";
|
||||
import { ObservableMap, action, computed, makeObservable, observable } from "mobx";
|
||||
import { action, computed, makeObservable, observable } from "mobx";
|
||||
import AppStore from "./AppStore";
|
||||
import Presence from "./objects/Presence";
|
||||
|
||||
export default class PresenceStore {
|
||||
private readonly app: AppStore;
|
||||
@observable presences = observable.map<Snowflake, ObservableMap<Snowflake, Presence>>();
|
||||
@observable presences = observable.map<Snowflake, Presence>();
|
||||
|
||||
constructor(app: AppStore) {
|
||||
this.app = app;
|
||||
@ -15,11 +15,11 @@ export default class PresenceStore {
|
||||
|
||||
@action
|
||||
add(data: GatewayPresenceUpdateDispatchData) {
|
||||
if (!this.presences.has(data.guild_id)) {
|
||||
this.presences.set(data.guild_id, observable.map<Snowflake, Presence>());
|
||||
if (!this.presences.has(data.user.id)) {
|
||||
this.presences.set(data.user.id, new Presence(this.app, data));
|
||||
} else {
|
||||
this.update(data);
|
||||
}
|
||||
|
||||
this.presences.get(data.guild_id)?.set(data.user.id, new Presence(this.app, data));
|
||||
}
|
||||
|
||||
@action
|
||||
@ -39,7 +39,7 @@ export default class PresenceStore {
|
||||
|
||||
@action
|
||||
update(data: GatewayPresenceUpdateDispatchData) {
|
||||
this.presences.get(data.guild_id)?.get(data.user.id)?.update(data);
|
||||
this.presences.get(data.user.id)?.update(data);
|
||||
}
|
||||
|
||||
get(id: Snowflake) {
|
||||
|
Loading…
Reference in New Issue
Block a user