From 306dcb051fbf9095553cc72671867f8ea13538fb Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Wed, 10 May 2023 20:38:10 -0400 Subject: [PATCH] feat: user panel --- src/components/Avatar.tsx | 31 +++++++ src/components/Button.tsx | 129 +++++++++++++++--------------- src/components/ChannelHeader.tsx | 2 +- src/components/ChannelSidebar.tsx | 2 + src/components/IconButton.tsx | 66 +++++++++++++++ src/components/UserPanel.tsx | 88 ++++++++++++++++++++ src/contexts/Theme.tsx | 3 + src/stores/AccountStore.ts | 21 +++++ 8 files changed, 278 insertions(+), 64 deletions(-) create mode 100644 src/components/Avatar.tsx create mode 100644 src/components/IconButton.tsx create mode 100644 src/components/UserPanel.tsx diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx new file mode 100644 index 0000000..f95aedb --- /dev/null +++ b/src/components/Avatar.tsx @@ -0,0 +1,31 @@ +import { observer } from "mobx-react-lite"; +import styled from "styled-components"; +import { useAppStore } from "../stores/AppStore"; +import Container from "./Container"; + +const Wrapper = styled(Container)<{ size: number }>` + width: ${(props) => props.size}px; + height: ${(props) => props.size}px; + border-radius: 50%; + position: relative; +`; + +interface Props { + size?: number; +} + +function Avatar(props: Props) { + const app = useAppStore(); + + return ( + + + + ); +} + +export default observer(Avatar); diff --git a/src/components/Button.tsx b/src/components/Button.tsx index f2034b1..6bc02d5 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -6,86 +6,89 @@ interface Props { } export default styled.button` -background: ${(props) => { - if (props.outlined) return "transparent"; - switch (props.variant) { - case "primary": - return "var(--primary)"; - case "secondary": - return "var(--secondary)"; - case "danger": - return "var(--danger)"; - case "success": - return "var(--success)"; - case "warning": - return "var(--warning)"; - default: - return "var(--primary)"; - } -}}; - -border: ${(props) => { - if (!props.outlined) return "none"; - switch (props.variant) { - case "primary": - return "1px solid var(--primary)"; - case "secondary": - return "1px solid var(--secondary)"; - case "danger": - return "1px solid var(--danger)"; - case "success": - return "1px solid var(--success)"; - case "warning": - return "1px solid var(--warning)"; - default: - return "1px solid var(--primary)"; - } -}}; - -color: var(--text); -padding: 8px 16px; -border-radius: 8px; -font-size: 13px; -font-weight: 700; -cursor: pointer; -outline: none; -transition: background 0.2s ease-in-out; -pointer-events:${(props) => (props.disabled ? "none" : null)}; -opacity: ${(props) => (props.disabled ? 0.5 : 1)}; - -&:hover { - background: ${(props) => { + background: ${(props) => { + if (props.outlined) return "transparent"; switch (props.variant) { case "primary": - return "var(--primary-light)"; + return "var(--primary)"; case "secondary": - return "var(--secondary-light)"; + return "var(--secondary)"; case "danger": - return "var(--danger-light)"; + return "var(--danger)"; case "success": - return "var(--success-light)"; + return "var(--success)"; case "warning": - return "var(--warning-light)"; + return "var(--warning)"; default: - return "var(--primary-light)"; + return "var(--primary)"; } }}; -&:active { - background: ${(props) => { + border: ${(props) => { + if (!props.outlined) return "none"; switch (props.variant) { case "primary": - return "var(--primary-dark)"; + return "1px solid var(--primary)"; case "secondary": - return "var(--secondary-dark)"; + return "1px solid var(--secondary)"; case "danger": - return "var(--danger-dark)"; + return "1px solid var(--danger)"; case "success": - return "var(--success-dark)"; + return "1px solid var(--success)"; case "warning": - return "var(--warning-dark)"; + return "1px solid var(--warning)"; default: - return "var(--primary-dark)"; + return "1px solid var(--primary)"; } }}; + + color: var(--text); + padding: 8px 16px; + border-radius: 8px; + font-size: 13px; + font-weight: 700; + cursor: pointer; + outline: none; + transition: background 0.2s ease-in-out; + pointer-events: ${(props) => (props.disabled ? "none" : null)}; + opacity: ${(props) => (props.disabled ? 0.5 : 1)}; + + &:hover { + background: ${(props) => { + switch (props.variant) { + case "primary": + return "var(--primary-light)"; + case "secondary": + return "var(--secondary-light)"; + case "danger": + return "var(--danger-light)"; + case "success": + return "var(--success-light)"; + case "warning": + return "var(--warning-light)"; + default: + return "var(--primary-light)"; + } + }}; + cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")}; + } + + &:active { + background: ${(props) => { + switch (props.variant) { + case "primary": + return "var(--primary-dark)"; + case "secondary": + return "var(--secondary-dark)"; + case "danger": + return "var(--danger-dark)"; + case "success": + return "var(--success-dark)"; + case "warning": + return "var(--warning-dark)"; + default: + return "var(--primary-dark)"; + } + }}; + } `; diff --git a/src/components/ChannelHeader.tsx b/src/components/ChannelHeader.tsx index b51f64f..69e073c 100644 --- a/src/components/ChannelHeader.tsx +++ b/src/components/ChannelHeader.tsx @@ -18,7 +18,7 @@ interface Props { function ChannelHeader({ text }: Props) { return ( - {text} +
{text}
); } diff --git a/src/components/ChannelSidebar.tsx b/src/components/ChannelSidebar.tsx index 8bc5d04..8e984f7 100644 --- a/src/components/ChannelSidebar.tsx +++ b/src/components/ChannelSidebar.tsx @@ -4,6 +4,7 @@ import { useAppStore } from "../stores/AppStore"; import ChannelHeader from "./ChannelHeader"; import ChannelList from "./ChannelList"; import Container from "./Container"; +import UserPanel from "./UserPanel"; const Wrapper = styled(Container)` display: flex; @@ -29,6 +30,7 @@ function ChannelSidebar() { {/* // TODO: replace with dm search if no guild */} + ); } diff --git a/src/components/IconButton.tsx b/src/components/IconButton.tsx new file mode 100644 index 0000000..cb41f1f --- /dev/null +++ b/src/components/IconButton.tsx @@ -0,0 +1,66 @@ +import styled from "styled-components"; + +interface Props { + // variant?: "primary" | "secondary" | "danger" | "success" | "warning"; + outlined?: boolean; + color?: string; +} + +export default styled.button` + display: flex; + align-items: center; + justify-content: center; + position: relative; + margin: 0; + padding: 0; + width: 32px; + height: 32px; + border-radius: 4px; + cursor: pointer; + outline: none; + opacity: ${(props) => (props.disabled ? 0.5 : 1)}; + background-color: transparent; + + color: ${(props) => { + if (props.outlined) return "transparent"; + // switch (props.variant) { + // case "primary": + // return "var(--primary)"; + // case "secondary": + // return "var(--secondary)"; + // case "danger": + // return "var(--danger)"; + // case "success": + // return "var(--success)"; + // case "warning": + // return "var(--warning)"; + // default: + // return "var(--primary)"; + // } + return props.color; + }}; + + border: ${(props) => { + if (!props.outlined) return "none"; + // switch (props.variant) { + // case "primary": + // return "1px solid var(--primary)"; + // case "secondary": + // return "1px solid var(--secondary)"; + // case "danger": + // return "1px solid var(--danger)"; + // case "success": + // return "1px solid var(--success)"; + // case "warning": + // return "1px solid var(--warning)"; + // default: + // return "1px solid var(--primary)"; + // } + return props.color; + }}; + + &:hover { + background-color: var(--background-primary-alt); + cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")}; + } +`; diff --git a/src/components/UserPanel.tsx b/src/components/UserPanel.tsx new file mode 100644 index 0000000..ce8e82b --- /dev/null +++ b/src/components/UserPanel.tsx @@ -0,0 +1,88 @@ +import { MdSettings } from "react-icons/md"; +import styled from "styled-components"; +import { useAppStore } from "../stores/AppStore"; +import Avatar from "./Avatar"; +import IconButton from "./IconButton"; +import Tooltip from "./Tooltip"; + +const Section = styled.section` + flex: 0 0 auto; + background-color: var(--background-secondary-alt); +`; + +const Container = styled.div` + display: flex; + height: 52px; + align-items: center; + padding: 0 8px; + margin-bottom: 1px; + background-color: var(--background-secondary-alt); +`; + +const AvatarWrapper = styled.div` + display: flex; + align-items: center; + min-width: 120px; + padding-left: 2px; + margin-right: 8px; + border-radius: 4px; + + // &:hover { + // background-color: var(--background-primary-alt); + // } +`; + +const Name = styled.div` + padding: 4px 0px 4px 8px; + margin-right: 4px; +`; + +const Username = styled.div` + font-size: 14px; + font-weight: 600; +`; + +const Discriminator = styled.div` + font-size: 12px; +`; + +const ActionsWrapper = styled.div` + flex: 1; + flex-direction: row; + flex-wrap: no-wrap; + justify-content: flex-end; + align-items: stretch; + display: flex; +`; + +function UserPanel() { + const app = useAppStore(); + + return ( +
+ + + + +
+ {app.account?.username} +
+ + #{app.account?.discriminator} + +
+
+ + + + + + + + +
+
+ ); +} + +export default UserPanel; diff --git a/src/contexts/Theme.tsx b/src/contexts/Theme.tsx index aebc8b6..7b75e22 100644 --- a/src/contexts/Theme.tsx +++ b/src/contexts/Theme.tsx @@ -6,6 +6,7 @@ export type ThemeVariables = | "backgroundPrimary" | "backgroundPrimaryAlt" | "backgroundSecondary" + | "backgroundSecondaryAlt" | "backgroundTertiary" | "text" | "textSecondary" @@ -49,6 +50,7 @@ export const ThemePresets: Record = { backgroundPrimary: "#ffffff", backgroundPrimaryAlt: "", backgroundSecondary: "#ebe5e4", + backgroundSecondaryAlt: "#ebe5e4", backgroundTertiary: "#e9e2e1", text: "#000000", textSecondary: "#bdbdbd", @@ -83,6 +85,7 @@ export const ThemePresets: Record = { backgroundPrimary: "#242424", backgroundPrimaryAlt: "#2b2b2b", backgroundSecondary: "#1b1b1b", + backgroundSecondaryAlt: "#181818", backgroundTertiary: "#141414", text: "#e9e2e1", textSecondary: "#bdbdbd", diff --git a/src/stores/AccountStore.ts b/src/stores/AccountStore.ts index 758ba87..ae11a2f 100644 --- a/src/stores/AccountStore.ts +++ b/src/stores/AccountStore.ts @@ -1,9 +1,13 @@ import { APIUser, + CDNRoutes, + DefaultUserAvatarAssets, + ImageFormat, UserFlags, UserPremiumType, } from "@spacebarchat/spacebar-api-types/v9"; import { observable } from "mobx"; +import REST from "../utils/REST"; export default class AccountStore { id: string; @@ -58,4 +62,21 @@ export default class AccountStore { // this.phone = user.phone; // this.nsfwAllowed = user.nsfw_allowed; } + + /** + * Gets the users display avatar url + * @returns The URL to the user's avatar or the default avatar if they don't have one. + */ + getAvatarURL(): string { + if (this.avatar) + return REST.makeCDNUrl( + CDNRoutes.userAvatar(this.id, this.avatar, ImageFormat.PNG), + ); + else + return REST.makeCDNUrl( + CDNRoutes.defaultUserAvatar( + (Number(this.discriminator) % 5) as DefaultUserAvatarAssets, + ), + ); + } }