feat(header): add chevron / unified sidebar collapse

This commit is contained in:
Paul 2021-12-24 13:02:49 +00:00
parent e263b627aa
commit d8d002cc4a
7 changed files with 91 additions and 79 deletions

View file

@ -1,3 +1,4 @@
import { observer } from "mobx-react-lite";
import { Route, Switch } from "react-router"; import { Route, Switch } from "react-router";
import { useApplicationState } from "../../mobx/State"; import { useApplicationState } from "../../mobx/State";
@ -8,7 +9,7 @@ import HomeSidebar from "./left/HomeSidebar";
import ServerListSidebar from "./left/ServerListSidebar"; import ServerListSidebar from "./left/ServerListSidebar";
import ServerSidebar from "./left/ServerSidebar"; import ServerSidebar from "./left/ServerSidebar";
export default function LeftSidebar() { export default observer(() => {
const layout = useApplicationState().layout; const layout = useApplicationState().layout;
const isOpen = layout.getSectionState(SIDEBAR_CHANNELS, true); const isOpen = layout.getSectionState(SIDEBAR_CHANNELS, true);
@ -35,4 +36,4 @@ export default function LeftSidebar() {
</Switch> </Switch>
</SidebarBase> </SidebarBase>
); );
} });

View file

@ -1,15 +1,25 @@
import { Menu } from "@styled-icons/boxicons-regular"; import {
ChevronLeft,
ChevronRight,
Menu,
} from "@styled-icons/boxicons-regular";
import { observer } from "mobx-react-lite";
import styled, { css } from "styled-components"; import styled, { css } from "styled-components";
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
import { useApplicationState } from "../../mobx/State";
import { SIDEBAR_CHANNELS } from "../../mobx/stores/Layout";
import { Children } from "../../types/Preact";
interface Props { interface Props {
borders?: boolean; borders?: boolean;
background?: boolean; background?: boolean;
placement: "primary" | "secondary"; placement: "primary" | "secondary";
} }
export default styled.div<Props>` const Header = styled.div<Props>`
gap: 10px; gap: 10px;
height: 48px; height: 48px;
flex: 0 auto; flex: 0 auto;
@ -33,10 +43,6 @@ export default styled.div<Props>`
color: var(--secondary-foreground); color: var(--secondary-foreground);
} }
/*@media only screen and (max-width: 768px) {
padding: 0 12px;
}*/
${() => ${() =>
isTouchscreenDevice && isTouchscreenDevice &&
css` css`
@ -66,6 +72,60 @@ export default styled.div<Props>`
`} `}
`; `;
export default Header;
const IconContainer = styled.div`
display: flex;
align-items: center;
cursor: pointer;
color: var(--secondary-foreground);
margin-right: 5px;
> svg {
margin-right: -5px;
}
${!isTouchscreenDevice &&
css`
&:hover {
color: var(--foreground);
}
`}
`;
interface PageHeaderProps {
noBurger?: boolean;
children: Children;
icon: Children;
}
export const PageHeader = observer(
({ children, icon, noBurger }: PageHeaderProps) => {
const layout = useApplicationState().layout;
return (
<Header placement="primary">
{!noBurger && <HamburgerAction />}
<IconContainer
onClick={() =>
layout.toggleSectionState(SIDEBAR_CHANNELS, true)
}>
{!isTouchscreenDevice &&
layout.getSectionState(SIDEBAR_CHANNELS, true) && (
<ChevronLeft size={18} />
)}
{icon}
{!isTouchscreenDevice &&
!layout.getSectionState(SIDEBAR_CHANNELS, true) && (
<ChevronRight size={18} />
)}
</IconContainer>
{children}
</Header>
);
},
);
export function HamburgerAction() { export function HamburgerAction() {
if (!isTouchscreenDevice) return null; if (!isTouchscreenDevice) return null;

View file

@ -177,6 +177,6 @@ export default class Layout implements Store, Persistent<Data> {
* @param def Default state value * @param def Default state value
*/ */
@action toggleSectionState(id: string, def?: boolean) { @action toggleSectionState(id: string, def?: boolean) {
this.setSectionState(id, !this.getSectionState(id, def)); this.setSectionState(id, !this.getSectionState(id, def), def);
} }
} }

View file

@ -1,4 +1,9 @@
import { At, Hash } from "@styled-icons/boxicons-regular"; import {
At,
ChevronLeft,
ChevronRight,
Hash,
} from "@styled-icons/boxicons-regular";
import { Notepad, Group } from "@styled-icons/boxicons-solid"; import { Notepad, Group } from "@styled-icons/boxicons-solid";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import { Channel } from "revolt.js/dist/maps/Channels"; import { Channel } from "revolt.js/dist/maps/Channels";
@ -8,14 +13,17 @@ import styled, { css } from "styled-components";
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice"; import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
import { useApplicationState } from "../../mobx/State"; import { useApplicationState } from "../../mobx/State";
import { SIDEBAR_MEMBERS } from "../../mobx/stores/Layout"; import { SIDEBAR_CHANNELS, SIDEBAR_MEMBERS } from "../../mobx/stores/Layout";
import { useIntermediate } from "../../context/intermediate/Intermediate"; import { useIntermediate } from "../../context/intermediate/Intermediate";
import { getChannelName } from "../../context/revoltjs/util"; import { getChannelName } from "../../context/revoltjs/util";
import { useStatusColour } from "../../components/common/user/UserIcon"; import { useStatusColour } from "../../components/common/user/UserIcon";
import UserStatus from "../../components/common/user/UserStatus"; import UserStatus from "../../components/common/user/UserStatus";
import Header, { HamburgerAction } from "../../components/ui/Header"; import Header, {
HamburgerAction,
PageHeader,
} from "../../components/ui/Header";
import Markdown from "../../components/markdown/Markdown"; import Markdown from "../../components/markdown/Markdown";
import HeaderActions from "./actions/HeaderActions"; import HeaderActions from "./actions/HeaderActions";
@ -68,25 +76,6 @@ const Info = styled.div`
} }
`; `;
const IconConainer = styled.div`
display: flex;
align-items: center;
cursor: pointer;
color: var(--secondary-foreground);
margin-right: 5px;
> svg {
margin-right: -5px;
}
${!isTouchscreenDevice &&
css`
&:hover {
color: var(--foreground);
}
`}
`;
export default observer(({ channel }: ChannelHeaderProps) => { export default observer(({ channel }: ChannelHeaderProps) => {
const { openScreen } = useIntermediate(); const { openScreen } = useIntermediate();
const layout = useApplicationState().layout; const layout = useApplicationState().layout;
@ -110,15 +99,7 @@ export default observer(({ channel }: ChannelHeaderProps) => {
} }
return ( return (
<Header placement="primary"> <PageHeader icon={icon}>
<HamburgerAction />
<IconConainer
onClick={() =>
layout.toggleSectionState(SIDEBAR_MEMBERS, true)
}>
{/*isTouchscreenDevice && <ChevronLeft size={18} /> FIXME: requires mobx merge */}
{icon}
</IconConainer>
<Info> <Info>
<span className="name">{name}</span> <span className="name">{name}</span>
{isTouchscreenDevice && {isTouchscreenDevice &&
@ -162,6 +143,6 @@ export default observer(({ channel }: ChannelHeaderProps) => {
)} )}
</Info> </Info>
<HeaderActions channel={channel} /> <HeaderActions channel={channel} />
</Header> </PageHeader>
); );
}); });

View file

@ -7,7 +7,7 @@ import { TextReact } from "../../lib/i18n";
import { AppContext } from "../../context/revoltjs/RevoltClient"; import { AppContext } from "../../context/revoltjs/RevoltClient";
import Header from "../../components/ui/Header"; import Header, { PageHeader } from "../../components/ui/Header";
export default function Developer() { export default function Developer() {
// const voice = useContext(VoiceContext); // const voice = useContext(VoiceContext);
@ -27,10 +27,7 @@ export default function Developer() {
return ( return (
<div> <div>
<Header placement="primary"> <PageHeader icon={<Wrench size="24" />}>Developer Tab</PageHeader>
<Wrench size="24" />
Developer Tab
</Header>
<div style={{ padding: "16px" }}> <div style={{ padding: "16px" }}>
<PaintCounter always /> <PaintCounter always />
</div> </div>

View file

@ -16,7 +16,7 @@ import { useClient } from "../../context/revoltjs/RevoltClient";
import CollapsibleSection from "../../components/common/CollapsibleSection"; import CollapsibleSection from "../../components/common/CollapsibleSection";
import Tooltip from "../../components/common/Tooltip"; import Tooltip from "../../components/common/Tooltip";
import UserIcon from "../../components/common/user/UserIcon"; import UserIcon from "../../components/common/user/UserIcon";
import Header from "../../components/ui/Header"; import Header, { PageHeader } from "../../components/ui/Header";
import IconButton from "../../components/ui/IconButton"; import IconButton from "../../components/ui/IconButton";
import { Children } from "../../types/Preact"; import { Children } from "../../types/Preact";
@ -72,8 +72,7 @@ export default observer(() => {
const isEmpty = lists.reduce((p: number, n) => p + n.length, 0) === 0; const isEmpty = lists.reduce((p: number, n) => p + n.length, 0) === 0;
return ( return (
<> <>
<Header placement="primary"> <PageHeader icon={<UserDetail size={24} />} noBurger>
{!isTouchscreenDevice && <UserDetail size={24} />}
<div className={styles.title}> <div className={styles.title}>
<Text id="app.navigation.tabs.friends" /> <Text id="app.navigation.tabs.friends" />
</div> </div>
@ -115,7 +114,7 @@ export default observer(() => {
</Tooltip> </Tooltip>
*/} */}
</div> </div>
</Header> </PageHeader>
<div <div
className={styles.list} className={styles.list}
data-empty={isEmpty} data-empty={isEmpty}

View file

@ -17,30 +17,15 @@ import "./snow.scss";
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import { useContext, useMemo } from "preact/hooks"; import { useContext, useMemo } from "preact/hooks";
import { isTouchscreenDevice } from "../../lib/isTouchscreenDevice";
import { useApplicationState } from "../../mobx/State"; import { useApplicationState } from "../../mobx/State";
import { SIDEBAR_CHANNELS } from "../../mobx/stores/Layout";
import { AppContext } from "../../context/revoltjs/RevoltClient"; import { AppContext } from "../../context/revoltjs/RevoltClient";
import wideSVG from "../../../public/assets/wide.svg"; import wideSVG from "../../../public/assets/wide.svg";
import Tooltip from "../../components/common/Tooltip"; import Tooltip from "../../components/common/Tooltip";
import Header from "../../components/ui/Header"; import { PageHeader } from "../../components/ui/Header";
import CategoryButton from "../../components/ui/fluent/CategoryButton"; import CategoryButton from "../../components/ui/fluent/CategoryButton";
const IconConainer = styled.div`
cursor: pointer;
color: var(--secondary-foreground);
${!isTouchscreenDevice &&
css`
&:hover {
color: var(--foreground);
}
`}
`;
const Overlay = styled.div` const Overlay = styled.div`
display: grid; display: grid;
height: 100%; height: 100%;
@ -58,14 +43,6 @@ export default observer(() => {
const client = useContext(AppContext); const client = useContext(AppContext);
const state = useApplicationState(); const state = useApplicationState();
const toggleChannelSidebar = () => {
if (isTouchscreenDevice) {
return;
}
state.layout.toggleSectionState(SIDEBAR_CHANNELS, true);
};
const toggleSeasonalTheme = () => const toggleSeasonalTheme = () =>
state.settings.set( state.settings.set(
"appearance:seasonal", "appearance:seasonal",
@ -107,12 +84,9 @@ export default observer(() => {
</div> </div>
)} )}
<div className="content"> <div className="content">
<Header placement="primary"> <PageHeader icon={<HomeIcon size={24} />}>
<IconConainer onClick={toggleChannelSidebar}>
<HomeIcon size={24} />
</IconConainer>
<Text id="app.navigation.tabs.home" /> <Text id="app.navigation.tabs.home" />
</Header> </PageHeader>
<div className={styles.homeScreen}> <div className={styles.homeScreen}>
<h3> <h3>
<Text id="app.special.modals.onboarding.welcome" /> <Text id="app.special.modals.onboarding.welcome" />