diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index dc7a26e..e904433 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -1,9 +1,11 @@ +import { PresenceUpdateStatus } from "@spacebarchat/spacebar-api-types/v9"; import { observer } from "mobx-react-lite"; import React from "react"; import styled from "styled-components"; import { PopoutContext } from "../contexts/PopoutContext"; import AccountStore from "../stores/AccountStore"; import { useAppStore } from "../stores/AppStore"; +import Presence from "../stores/objects/Presence"; import User from "../stores/objects/User"; import Container from "./Container"; import UserProfilePopout from "./UserProfilePopout"; @@ -11,8 +13,8 @@ import UserProfilePopout from "./UserProfilePopout"; const Wrapper = styled(Container)<{ size: number }>` width: ${(props) => props.size}px; height: ${(props) => props.size}px; - border-radius: 50%; position: relative; + background-color: transparent; &:hover { text-decoration: underline; @@ -20,12 +22,28 @@ const Wrapper = styled(Container)<{ size: number }>` } `; +const StatusDot = styled.span<{ color: string; width?: number; height?: number }>` + position: absolute; + bottom: 0; + right: 0; + background-color: ${(props) => props.color}; + border-radius: 50%; + border: 2px solid var(--background-primary); + width: ${(props) => props.width ?? 10}px; + height: ${(props) => props.height ?? 10}px; +`; + interface Props { user?: User | AccountStore; size?: number; style?: React.CSSProperties; onClick?: (() => void) | null; popoutPlacement?: "left" | "right" | "top" | "bottom"; + presence?: Presence; + statusDotStyle?: { + width?: number; + height?: number; + }; } function Avatar(props: Props) { @@ -47,7 +65,7 @@ function Avatar(props: Props) { if (!rect) return; popoutContext.open({ - element: , + element: , position: rect, placement: props.popoutPlacement, }); @@ -57,7 +75,18 @@ function Avatar(props: Props) { return ( - + + {props.presence && props.presence.status !== PresenceUpdateStatus.Offline && ( + + )} ); } diff --git a/src/components/MemberList/MemberListItem.tsx b/src/components/MemberList/MemberListItem.tsx index 5e7c9ff..f31e1a4 100644 --- a/src/components/MemberList/MemberListItem.tsx +++ b/src/components/MemberList/MemberListItem.tsx @@ -82,7 +82,7 @@ function MemberListItem({ item }: Props) { e.preventDefault(); e.stopPropagation(); popoutContext.open({ - element: , + element: , position: e.currentTarget.getBoundingClientRect(), placement: "right", }); @@ -91,7 +91,7 @@ function MemberListItem({ item }: Props) { - + {item.nick ?? item.user?.username} diff --git a/src/components/UserProfilePopout.tsx b/src/components/UserProfilePopout.tsx index de17704..ac05110 100644 --- a/src/components/UserProfilePopout.tsx +++ b/src/components/UserProfilePopout.tsx @@ -3,6 +3,8 @@ import styled from "styled-components"; import { ReactComponent as SpacebarLogoBlue } from "../assets/images/logo/Spacebar_Icon.svg"; import useLogger from "../hooks/useLogger"; import AccountStore from "../stores/AccountStore"; +import GuildMember from "../stores/objects/GuildMember"; +import Presence from "../stores/objects/Presence"; import User from "../stores/objects/User"; import Snowflake from "../utils/Snowflake"; import Avatar from "./Avatar"; @@ -80,7 +82,13 @@ const MemberSinceText = styled.span` font-weight: var(--font-weight-regular); `; -function UserProfilePopout({ user }: { user: User | AccountStore }) { +interface Props { + user: User | AccountStore; + presence?: Presence; + member?: GuildMember; +} + +function UserProfilePopout({ user, presence, member }: Props) { const logger = useLogger("UserProfilePopout"); // if (!member.user) { // logger.error("member.user is undefined"); @@ -103,6 +111,11 @@ function UserProfilePopout({ user }: { user: User | AccountStore }) { // TODO: open profile modal }} user={user} + presence={presence} + statusDotStyle={{ + width: 16, + height: 16, + }} /> diff --git a/src/contexts/Theme.tsx b/src/contexts/Theme.tsx index 4fc1cc1..7a1edf7 100644 --- a/src/contexts/Theme.tsx +++ b/src/contexts/Theme.tsx @@ -56,7 +56,9 @@ export type ThemeVariables = | "warningContrastText" | "interactive" | "scrollbarTrack" - | "scrollbarThumb"; + | "scrollbarThumb" + | "statusIdle" + | "statusOffline"; export type Overrides = { [variable in ThemeVariables]: string; @@ -102,13 +104,13 @@ export const ThemePresets: Record = { inputBackground: "#757575", error: "#e83f36", divider: "#3c3c3c", - primary: "", - primaryLight: "", - primaryDark: "", - primaryContrastText: "", - secondary: "", - secondaryLight: "", - secondaryDark: "", + primary: "#0185ff", + primaryLight: "#339dff", + primaryDark: "#005db2", + primaryContrastText: "#ffffff", + secondary: "#000115", + secondaryLight: "#000677", + secondaryDark: "#000111", secondaryContrastText: "", danger: "", dangerLight: "", @@ -125,6 +127,8 @@ export const ThemePresets: Record = { scrollbarTrack: "", scrollbarThumb: "", interactive: "", + statusIdle: "#ff7c01", + statusOffline: "#5d5d5d", font: font, }, dark: { @@ -149,9 +153,11 @@ export const ThemePresets: Record = { primaryLight: "#339dff", primaryDark: "#005db2", primaryContrastText: "#ffffff", - secondary: "#ff7c01", - secondaryLight: "#ff9633", - secondaryDark: "#b25600", + secondary: "#000115", + secondaryLight: "#000677", + secondaryDark: "#000111", + // secondary: "#ff7c01", + // secondaryLight: "#ff9633", secondaryContrastText: "#040404", danger: "#ff3a3b", dangerLight: "#ff6162", @@ -168,6 +174,8 @@ export const ThemePresets: Record = { scrollbarTrack: "#232323", scrollbarThumb: "#171717", interactive: "#424242", + statusIdle: "#ff7c01", + statusOffline: "#5d5d5d", font: font, }, }; diff --git a/src/stores/ThemeStore.ts b/src/stores/ThemeStore.ts index c1e8c53..8154d0a 100644 --- a/src/stores/ThemeStore.ts +++ b/src/stores/ThemeStore.ts @@ -1,3 +1,4 @@ +import { PresenceUpdateStatus } from "@spacebarchat/spacebar-api-types/v9"; import { computed, makeAutoObservable } from "mobx"; import type { Theme } from "../contexts/Theme"; import { ThemePresets } from "../contexts/Theme"; @@ -21,4 +22,19 @@ export default class ThemeStore { return variables as unknown as Theme; } + + @computed + getStatusColor(status?: PresenceUpdateStatus) { + switch (status) { + case PresenceUpdateStatus.Online: + return ThemePresets["dark"].successLight; + case PresenceUpdateStatus.Idle: + return ThemePresets["dark"].statusIdle; + case PresenceUpdateStatus.DoNotDisturb: + return ThemePresets["dark"].dangerLight; + case PresenceUpdateStatus.Offline: + default: + return ThemePresets["dark"].statusOffline; + } + } }