From 35a870b59942341f29aa5abe5fd1f4a95e6ce87a Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Mon, 8 May 2023 20:59:44 -0400 Subject: [PATCH] feat(GuildSidebar): sidebar pills --- src/App.tsx | 2 +- src/components/{Guild.tsx => GuildItem.tsx} | 49 ++++++++++------- src/components/GuildSidebar.tsx | 33 ++++++++---- src/components/SidebarAction.tsx | 43 ++++++++++----- src/components/SidebarListItem.tsx | 11 ++++ src/components/SidebarPill.tsx | 58 +++++++++++++++++++++ 6 files changed, 154 insertions(+), 42 deletions(-) rename src/components/{Guild.tsx => GuildItem.tsx} (54%) create mode 100644 src/components/SidebarListItem.tsx create mode 100644 src/components/SidebarPill.tsx diff --git a/src/App.tsx b/src/App.tsx index 4e3da53..ec1e16e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -70,7 +70,7 @@ function App() { element={} /> } /> ` display: flex; align-items: center; justify-content: center; + width: 48px; + height: 48px; + border-radius: ${(props) => (props.active ? "30%" : "50%")}; + background-color: ${(props) => + props.active ? "var(--primary)" : "var(--background-secondary)"}; transition: border-radius 0.2s ease, background-color 0.2s ease; &:hover { @@ -31,15 +28,18 @@ const Wrapper = styled(Container)` interface Props { guildId: string; + active?: boolean; } /** * List item for use in the guild sidebar */ -function Guild(props: Props) { +function GuildItem(props: Props) { const app = useAppStore(); const navigate = useNavigate(); const guild = app.guilds.get(props.guildId); + const [pillType, setPillType] = React.useState("none"); + const [isHovered, setHovered] = React.useState(false); if (!guild) return null; @@ -47,10 +47,23 @@ function Guild(props: Props) { navigate(`/channels/${props.guildId}`); }; + React.useEffect(() => { + if (props.active) return setPillType("active"); + else if (isHovered) return setPillType("hover"); + // TODO: unread + else return setPillType("none"); + }, [props.active, isHovered]); + return ( - + + - + setHovered(true)} + onMouseLeave={() => setHovered(false)} + > {guild.icon ? ( - + ); } -export default Guild; +export default GuildItem; diff --git a/src/components/GuildSidebar.tsx b/src/components/GuildSidebar.tsx index d2dabfe..19f2808 100644 --- a/src/components/GuildSidebar.tsx +++ b/src/components/GuildSidebar.tsx @@ -1,13 +1,14 @@ import { observer } from "mobx-react-lite"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import styled from "styled-components"; import { useAppStore } from "../stores/AppStore"; -import Guild from "./Guild"; +import GuildItem from "./GuildItem"; import SidebarAction from "./SidebarAction"; +import SidebarListItem from "./SidebarListItem"; const List = styled.ul` list-style: none; - padding: 12px; + padding: 12px 12px 12px 0; margin: 0; display: flex; flex-direction: column; @@ -19,14 +20,17 @@ const List = styled.ul` } `; -const Hr = styled.hr` - width: 100%; - margin-top: 12px; +const Divider = styled.div` + height: 2px; + width: 32px; + border-radius: 1px; + background-color: white; `; function GuildSidebar() { const app = useAppStore(); const navigate = useNavigate(); + const { guildId } = useParams(); return ( @@ -38,11 +42,20 @@ function GuildSidebar() { }} action={() => navigate("/channels/@me")} margin={false} + active={guildId === "@me"} /> -
- {app.guilds.getAll().map((guild) => ( - - ))} + + + +
+ {app.guilds.getAll().map((guild) => ( + + ))} +
); } diff --git a/src/components/SidebarAction.tsx b/src/components/SidebarAction.tsx index 96001f9..a2c6d7b 100644 --- a/src/components/SidebarAction.tsx +++ b/src/components/SidebarAction.tsx @@ -1,20 +1,19 @@ +import React from "react"; import styled from "styled-components"; import Container from "./Container"; import Icon, { IconProps } from "./Icon"; +import SidebarListItem from "./SidebarListItem"; +import SidebarPill, { PillType } from "./SidebarPill"; import Tooltip from "./Tooltip"; -const ListItem = styled.li` - padding: 0; - margin: 0; -`; - -const Wrapper = styled(Container)<{ margin?: boolean }>` +const Wrapper = styled(Container)<{ margin?: boolean; active?: boolean }>` ${(props) => (props.margin !== false ? "margin-top: 9px;" : "")}}; padding: 0; width: 48px; height: 48px; - border-radius: 50%; - background-color: var(--background-secondary); + border-radius: ${(props) => (props.active ? "30%" : "50%")}; + background-color: ${(props) => + props.active ? "var(--primary)" : "var(--background-secondary)"}; display: flex; align-items: center; justify-content: center; @@ -36,6 +35,7 @@ interface Props { icon?: IconProps; label?: string; margin?: boolean; + active?: boolean; } function SidebarAction(props: Props) { @@ -44,16 +44,33 @@ function SidebarAction(props: Props) { "SidebarAction can only have one of image, icon, or label", ); + const [pillType, setPillType] = React.useState("none"); + const [isHovered, setHovered] = React.useState(false); + + React.useEffect(() => { + if (props.active) return setPillType("active"); + else if (isHovered) return setPillType("hover"); + // TODO: unread + else return setPillType("none"); + }, [props.active, isHovered]); + return ( - + + - - {props.image && } + setHovered(true)} + onMouseLeave={() => setHovered(false)} + margin={props.margin} + active={props.active} + > + {/* {props.image && } */} {props.icon && } - {props.label && {props.label}} + {/* {props.label && {props.label}} */} - + ); } diff --git a/src/components/SidebarListItem.tsx b/src/components/SidebarListItem.tsx new file mode 100644 index 0000000..e3b29f6 --- /dev/null +++ b/src/components/SidebarListItem.tsx @@ -0,0 +1,11 @@ +import styled from "styled-components"; + +const SidebarListItem = styled.li` + position: relative; + margin: 0 0 8px; + display: flex; + justify-content: center; + width: 72px; +`; + +export default SidebarListItem; diff --git a/src/components/SidebarPill.tsx b/src/components/SidebarPill.tsx new file mode 100644 index 0000000..2918708 --- /dev/null +++ b/src/components/SidebarPill.tsx @@ -0,0 +1,58 @@ +import styled from "styled-components"; +import Container from "./Container"; + +export type PillType = "none" | "unread" | "hover" | "active"; + +const Wrapper = styled(Container)` + position: absolute; + top: 0; + left: 0; + width: 8px; + height: 48px; + display: flex; + justify-content: flex-start; + align-items: center; +`; + +const Pill = styled.span<{ type: PillType }>` + width: 8px; + border-radius: 0 4px 4px 0; + background-color: white; + margin-left: -4px; + transition: height 0.3s ease; + + ${(props) => { + switch (props.type) { + case "unread": + return ` + height: 8px; + `; + case "hover": + return ` + height: 20px; + `; + case "active": + return ` + height: 40px; + `; + default: + return ` + height: 0; + `; + } + }} +`; + +interface Props { + type: PillType; +} + +function SidebarPill({ type }: Props) { + return ( + + + + ); +} + +export default SidebarPill;