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;
+ }
+ }
}