mirror of
https://github.com/spacebarchat/client.git
synced 2024-11-22 10:22:30 +01:00
feat(GuildSidebar): sidebar pills
This commit is contained in:
parent
e53abb0849
commit
35a870b599
@ -70,7 +70,7 @@ function App() {
|
||||
element={<AuthenticationGuard component={AppPage} />}
|
||||
/>
|
||||
<Route
|
||||
path="/channels/:channelId"
|
||||
path="/channels/:guildId/:channelId?"
|
||||
element={<AuthenticationGuard component={ChannelPage} />}
|
||||
/>
|
||||
<Route
|
||||
|
@ -1,26 +1,23 @@
|
||||
import { Tooltip } from "@mui/material";
|
||||
import { CDNRoutes, ImageFormat } from "@spacebarchat/spacebar-api-types/v9";
|
||||
import React from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import { useAppStore } from "../stores/AppStore";
|
||||
import REST from "../utils/REST";
|
||||
import Container from "./Container";
|
||||
import Tooltip from "./Tooltip";
|
||||
import SidebarListItem from "./SidebarListItem";
|
||||
import SidebarPill, { PillType } from "./SidebarPill";
|
||||
|
||||
const ListItem = styled.li`
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
const Wrapper = styled(Container)`
|
||||
margin-top: 9px;
|
||||
padding: 0;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--background-secondary);
|
||||
const Wrapper = styled(Container)<{ active?: boolean }>`
|
||||
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<PillType>("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 (
|
||||
<ListItem>
|
||||
<SidebarListItem>
|
||||
<SidebarPill type={pillType} />
|
||||
<Tooltip title={guild.name} placement="right">
|
||||
<Wrapper onClick={doNavigate}>
|
||||
<Wrapper
|
||||
onClick={doNavigate}
|
||||
active={props.active}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
{guild.icon ? (
|
||||
<img
|
||||
src={REST.makeCDNUrl(
|
||||
@ -70,8 +83,8 @@ function Guild(props: Props) {
|
||||
)}
|
||||
</Wrapper>
|
||||
</Tooltip>
|
||||
</ListItem>
|
||||
</SidebarListItem>
|
||||
);
|
||||
}
|
||||
|
||||
export default Guild;
|
||||
export default GuildItem;
|
@ -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 (
|
||||
<List>
|
||||
@ -38,11 +42,20 @@ function GuildSidebar() {
|
||||
}}
|
||||
action={() => navigate("/channels/@me")}
|
||||
margin={false}
|
||||
active={guildId === "@me"}
|
||||
/>
|
||||
<Hr key="hr" />
|
||||
<SidebarListItem>
|
||||
<Divider key="divider" />
|
||||
</SidebarListItem>
|
||||
<div aria-label="Servers">
|
||||
{app.guilds.getAll().map((guild) => (
|
||||
<Guild key={guild.id} guildId={guild.id} />
|
||||
<GuildItem
|
||||
key={guild.id}
|
||||
guildId={guild.id}
|
||||
active={guild.id === guildId}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</List>
|
||||
);
|
||||
}
|
||||
|
@ -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<PillType>("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 (
|
||||
<ListItem>
|
||||
<SidebarListItem>
|
||||
<SidebarPill type={pillType} />
|
||||
<Tooltip title={props.tooltip} placement="right">
|
||||
<Wrapper onClick={props.action} margin={props.margin}>
|
||||
{props.image && <img {...props.image} />}
|
||||
<Wrapper
|
||||
onClick={props.action}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
margin={props.margin}
|
||||
active={props.active}
|
||||
>
|
||||
{/* {props.image && <img {...props.image} />} */}
|
||||
{props.icon && <Icon {...props.icon} />}
|
||||
{props.label && <span>{props.label}</span>}
|
||||
{/* {props.label && <span>{props.label}</span>} */}
|
||||
</Wrapper>
|
||||
</Tooltip>
|
||||
</ListItem>
|
||||
</SidebarListItem>
|
||||
);
|
||||
}
|
||||
|
||||
|
11
src/components/SidebarListItem.tsx
Normal file
11
src/components/SidebarListItem.tsx
Normal file
@ -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;
|
58
src/components/SidebarPill.tsx
Normal file
58
src/components/SidebarPill.tsx
Normal file
@ -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 (
|
||||
<Wrapper>
|
||||
<Pill type={type} />
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export default SidebarPill;
|
Loading…
Reference in New Issue
Block a user