1
0
mirror of https://github.com/spacebarchat/client.git synced 2024-11-22 02:12:38 +01:00

new tooltips

This commit is contained in:
Puyodead1 2023-12-14 00:18:59 -05:00
parent 2c587af4a8
commit 71e47c96cd
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
16 changed files with 429 additions and 221 deletions

View File

@ -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>
);
}

View File

@ -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,18 +94,27 @@ function ChannelListItem({ channel, isCategory, active }: Props) {
</Text>
</div>
{isCategory && (
<Tooltip title="Create Channel" placement="top">
<span>
<Icon
icon="mdiPlus"
size="18px"
style={{
marginLeft: "auto",
}}
color={hovered ? "var(--text)" : "var(--text-secondary)"}
/>
</span>
</Tooltip>
<Floating
placement="top"
type="tooltip"
offset={10}
props={{
content: <span>Create Channel</span>,
}}
>
<FloatingTrigger>
<span>
<Icon
icon="mdiPlus"
size="18px"
style={{
marginLeft: "auto",
}}
color={hovered ? "var(--text)" : "var(--text-secondary)"}
/>
</span>
</FloatingTrigger>
</Floating>
)}
</Wrapper>
</ListItem>

View File

@ -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">
<a onClick={onCopy}>{text}</a>
</Tooltip>
<Floating
placement="top"
type="tooltip"
props={{
content: <span>"Copy to Clipboard</span>,
}}
>
<FloatingTrigger>
<a onClick={onCopy}>{text}</a>
</FloatingTrigger>
</Floating>
</Actions>
{props.children}
</pre>

View File

@ -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,34 +71,43 @@ function GuildItem({ guild, active }: Props) {
return (
<GuildSidebarListItem>
<SidebarPill type={pillType} />
<Tooltip title={guild.name} placement="right">
<Wrapper
onClick={doNavigate}
active={active}
hasImage={!!guild?.icon}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
{guild.icon ? (
<img
src={REST.makeCDNUrl(CDNRoutes.guildIcon(guild.id, guild?.icon, ImageFormat.PNG))}
width={48}
height={48}
loading="lazy"
/>
) : (
<span
style={{
fontSize: "18px",
fontWeight: "bold",
cursor: "pointer",
}}
>
{guild?.acronym}
</span>
)}
</Wrapper>
</Tooltip>
<Floating
placement="right"
type="tooltip"
offset={20}
props={{
content: <span>{guild.name}</span>,
}}
>
<FloatingTrigger>
<Wrapper
onClick={doNavigate}
active={active}
hasImage={!!guild?.icon}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
{guild.icon ? (
<img
src={REST.makeCDNUrl(CDNRoutes.guildIcon(guild.id, guild?.icon, ImageFormat.PNG))}
width={48}
height={48}
loading="lazy"
/>
) : (
<span
style={{
fontSize: "18px",
fontWeight: "bold",
cursor: "pointer",
}}
>
{guild?.acronym}
</span>
)}
</Wrapper>
</FloatingTrigger>
</Floating>
</GuildSidebarListItem>
);
}

View File

@ -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>
);
}

View File

@ -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,25 +61,34 @@ function SidebarAction(props: Props) {
return (
<GuildSidebarListItem>
<SidebarPill type={pillType} />
<Tooltip title={props.tooltip} placement="right">
<Wrapper
onClick={props.action}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
margin={props.margin}
active={props.active}
useGreenColorScheme={props.useGreenColorScheme}
>
{props.image && <img {...props.image} loading="lazy" />}
{props.icon && (
<Icon
{...props.icon}
color={isHovered && props.useGreenColorScheme ? "var(--text)" : props.icon.color}
/>
)}
{props.label && <span>{props.label}</span>}
</Wrapper>
</Tooltip>
<Floating
placement="right"
type="tooltip"
offset={20}
props={{
content: <span>{props.tooltip}</span>,
}}
>
<FloatingTrigger>
<Wrapper
onClick={props.action}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
margin={props.margin}
active={props.active}
useGreenColorScheme={props.useGreenColorScheme}
>
{props.image && <img {...props.image} loading="lazy" />}
{props.icon && (
<Icon
{...props.icon}
color={isHovered && props.useGreenColorScheme ? "var(--text)" : props.icon.color}
/>
)}
{props.label && <span>{props.label}</span>}
</Wrapper>
</FloatingTrigger>
</Floating>
</GuildSidebarListItem>
);
}

View File

@ -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;

View File

@ -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">
<IconButton aria-label="settings" color="#fff" onClick={openSettingsModal}>
<Icon icon="mdiCog" size="20px" />
</IconButton>
</Tooltip>
<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>
</FloatingTrigger>
</Floating>
</ActionsWrapper>
</Container>
</Section>
<FloatingContent>
<UserProfilePopout user={app.account! as unknown as User} />
</FloatingContent>
</Floating>
);
}

View File

@ -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;

View File

@ -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"> */}
<div>
<SpacebarLogoBlue width={16} height={16} style={{ borderRadius: "50%" }} />
</div>
{/* </Tooltip> */}
<Floating
placement="top"
type="tooltip"
props={{
content: <span>Spacebar</span>,
}}
>
<FloatingTrigger>
<div>
<SpacebarLogoBlue width={16} height={16} style={{ borderRadius: "50%" }} />
</div>
</FloatingTrigger>
</Floating>
<MemberSinceText>{dayjs(createdAt).format("MMM D, YYYY")}</MemberSinceText>
{member && (
<>
@ -221,25 +235,37 @@ function UserProfilePopout({ user, member }: Props) {
}}
/>
{/* <Tooltip title={member.guild.name} placement="top"> */}
{member.guild.icon ? (
<img
src={REST.makeCDNUrl(
CDNRoutes.guildIcon(member.guild.id, member.guild.icon, ImageFormat.PNG),
<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,
),
)}
width={16}
height={16}
loading="lazy"
style={{
borderRadius: "50%",
}}
/>
) : (
<Acronym>
<AcronymText>{member.guild.acronym}</AcronymText>
</Acronym>
)}
width={16}
height={16}
loading="lazy"
style={{
borderRadius: "50%",
}}
/>
) : (
<Acronym>
<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>

View File

@ -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">
<span>{value}</span>
</Tooltip>
<Floating
placement="top"
type="tooltip"
props={{
content: <span>{date.format("dddd, MMMM MM, h:mm A")}</span>,
}}
>
<FloatingTrigger>
<span>{value}</span>
</FloatingTrigger>
</Floating>
</Container>
);
}

View File

@ -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}>
<IconWrapper>
<IconButton onClick={onClick}>
<CustomIcon $active={active} icon={icon} size="24px" aria-label={ariaLabel} />
</IconButton>
</IconWrapper>
</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>
</FloatingTrigger>
</Floating>
);
}

View File

@ -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>
);
}

View File

@ -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">
<time className="copyTime" dateTime={message.edited_timestamp.toISOString()}>
{dayjs(message.edited_timestamp).format("h:mm A")}
</time>
</Tooltip>
<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>
</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>
),
}}
>
<span>(edited)</span>
</Tooltip>
<FloatingTrigger>
<span>(edited)</span>
</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">
<time className="copyTime" dateTime={message.timestamp.toISOString()}>
{dayjs(message.timestamp).calendar(undefined, calendarStrings)}
</time>
</Tooltip>
<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>
</FloatingTrigger>
</Floating>
{message instanceof Message && message.edited_timestamp && (
<Tooltip title={dayjs(message.edited_timestamp).format("dddd, MMMM D, YYYY h:mm A")} placement="top">
<span className="edited">(edited)</span>
</Tooltip>
<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>
</FloatingTrigger>
</Floating>
)}
</DetailBase>
);

View File

@ -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],
);

View File

@ -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) {