feat: Sidebar Improvements (#1862)
* wip * refactor behaviorg * stash * simplify
This commit is contained in:
@ -76,7 +76,6 @@ class Layout extends React.Component<Props> {
|
|||||||
|
|
||||||
@keydown("shift+/")
|
@keydown("shift+/")
|
||||||
handleOpenKeyboardShortcuts() {
|
handleOpenKeyboardShortcuts() {
|
||||||
if (this.props.ui.editMode) return;
|
|
||||||
this.keyboardShortcutsOpen = true;
|
this.keyboardShortcutsOpen = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +85,6 @@ class Layout extends React.Component<Props> {
|
|||||||
|
|
||||||
@keydown(["t", "/", `${meta}+k`])
|
@keydown(["t", "/", `${meta}+k`])
|
||||||
goToSearch(ev: SyntheticEvent<>) {
|
goToSearch(ev: SyntheticEvent<>) {
|
||||||
if (this.props.ui.editMode) return;
|
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
this.redirectTo = searchUrl();
|
this.redirectTo = searchUrl();
|
||||||
@ -94,7 +92,6 @@ class Layout extends React.Component<Props> {
|
|||||||
|
|
||||||
@keydown("d")
|
@keydown("d")
|
||||||
goToDashboard() {
|
goToDashboard() {
|
||||||
if (this.props.ui.editMode) return;
|
|
||||||
this.redirectTo = homeUrl();
|
this.redirectTo = homeUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +99,7 @@ class Layout extends React.Component<Props> {
|
|||||||
const { auth, t, ui } = this.props;
|
const { auth, t, ui } = this.props;
|
||||||
const { user, team } = auth;
|
const { user, team } = auth;
|
||||||
const showSidebar = auth.authenticated && user && team;
|
const showSidebar = auth.authenticated && user && team;
|
||||||
const sidebarCollapsed = ui.editMode || ui.sidebarCollapsed;
|
const sidebarCollapsed = ui.isEditing || ui.sidebarCollapsed;
|
||||||
|
|
||||||
if (auth.isSuspended) return <ErrorSuspended />;
|
if (auth.isSuspended) return <ErrorSuspended />;
|
||||||
if (this.redirectTo) return <Redirect to={this.redirectTo} push />;
|
if (this.redirectTo) return <Redirect to={this.redirectTo} push />;
|
||||||
|
@ -3,29 +3,37 @@ import { observer } from "mobx-react";
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Portal } from "react-portal";
|
import { Portal } from "react-portal";
|
||||||
import { withRouter } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import type { Location } from "react-router-dom";
|
|
||||||
import styled, { useTheme } from "styled-components";
|
import styled, { useTheme } from "styled-components";
|
||||||
import breakpoint from "styled-components-breakpoint";
|
import breakpoint from "styled-components-breakpoint";
|
||||||
import Fade from "components/Fade";
|
import Fade from "components/Fade";
|
||||||
import Flex from "components/Flex";
|
import Flex from "components/Flex";
|
||||||
import CollapseToggle, {
|
|
||||||
Button as CollapseButton,
|
|
||||||
} from "./components/CollapseToggle";
|
|
||||||
import ResizeBorder from "./components/ResizeBorder";
|
import ResizeBorder from "./components/ResizeBorder";
|
||||||
import ResizeHandle from "./components/ResizeHandle";
|
import Toggle, { ToggleButton, Positioner } from "./components/Toggle";
|
||||||
import usePrevious from "hooks/usePrevious";
|
import usePrevious from "hooks/usePrevious";
|
||||||
import useStores from "hooks/useStores";
|
import useStores from "hooks/useStores";
|
||||||
|
|
||||||
let firstRender = true;
|
let firstRender = true;
|
||||||
let BOUNCE_ANIMATION_MS = 250;
|
let ANIMATION_MS = 250;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
location: Location,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const useResize = ({ width, minWidth, maxWidth, setWidth }) => {
|
function Sidebar({ children }: Props) {
|
||||||
|
const [isCollapsing, setCollapsing] = React.useState(false);
|
||||||
|
const theme = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { ui } = useStores();
|
||||||
|
const location = useLocation();
|
||||||
|
const previousLocation = usePrevious(location);
|
||||||
|
|
||||||
|
const width = ui.sidebarWidth;
|
||||||
|
const collapsed = ui.isEditing || ui.sidebarCollapsed;
|
||||||
|
const maxWidth = theme.sidebarMaxWidth;
|
||||||
|
const minWidth = theme.sidebarMinWidth + 16; // padding
|
||||||
|
const setWidth = ui.setSidebarWidth;
|
||||||
|
|
||||||
const [offset, setOffset] = React.useState(0);
|
const [offset, setOffset] = React.useState(0);
|
||||||
const [isAnimating, setAnimating] = React.useState(false);
|
const [isAnimating, setAnimating] = React.useState(false);
|
||||||
const [isResizing, setResizing] = React.useState(false);
|
const [isResizing, setResizing] = React.useState(false);
|
||||||
@ -38,24 +46,45 @@ const useResize = ({ width, minWidth, maxWidth, setWidth }) => {
|
|||||||
|
|
||||||
// this is simple because the sidebar is always against the left edge
|
// this is simple because the sidebar is always against the left edge
|
||||||
const width = Math.min(event.pageX - offset, maxWidth);
|
const width = Math.min(event.pageX - offset, maxWidth);
|
||||||
setWidth(width);
|
const isSmallerThanCollapsePoint = width < minWidth / 2;
|
||||||
|
|
||||||
|
if (isSmallerThanCollapsePoint) {
|
||||||
|
setWidth(theme.sidebarCollapsedWidth);
|
||||||
|
} else {
|
||||||
|
setWidth(width);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[offset, maxWidth, setWidth]
|
[theme, offset, minWidth, maxWidth, setWidth]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleStopDrag = React.useCallback(() => {
|
const handleStopDrag = React.useCallback(
|
||||||
setResizing(false);
|
(event: MouseEvent) => {
|
||||||
|
setResizing(false);
|
||||||
|
|
||||||
if (isSmallerThanMinimum) {
|
if (document.activeElement) {
|
||||||
setWidth(minWidth);
|
document.activeElement.blur();
|
||||||
setAnimating(true);
|
}
|
||||||
} else {
|
|
||||||
setWidth(width);
|
|
||||||
}
|
|
||||||
}, [isSmallerThanMinimum, minWidth, width, setWidth]);
|
|
||||||
|
|
||||||
const handleStartDrag = React.useCallback(
|
if (isSmallerThanMinimum) {
|
||||||
(event) => {
|
const isSmallerThanCollapsePoint = width < minWidth / 2;
|
||||||
|
|
||||||
|
if (isSmallerThanCollapsePoint) {
|
||||||
|
setAnimating(false);
|
||||||
|
setCollapsing(true);
|
||||||
|
ui.collapseSidebar();
|
||||||
|
} else {
|
||||||
|
setWidth(minWidth);
|
||||||
|
setAnimating(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setWidth(width);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[ui, isSmallerThanMinimum, minWidth, width, setWidth]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleMouseDown = React.useCallback(
|
||||||
|
(event: MouseEvent) => {
|
||||||
setOffset(event.pageX - width);
|
setOffset(event.pageX - width);
|
||||||
setResizing(true);
|
setResizing(true);
|
||||||
setAnimating(false);
|
setAnimating(false);
|
||||||
@ -65,10 +94,19 @@ const useResize = ({ width, minWidth, maxWidth, setWidth }) => {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isAnimating) {
|
if (isAnimating) {
|
||||||
setTimeout(() => setAnimating(false), BOUNCE_ANIMATION_MS);
|
setTimeout(() => setAnimating(false), ANIMATION_MS);
|
||||||
}
|
}
|
||||||
}, [isAnimating]);
|
}, [isAnimating]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isCollapsing) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setWidth(minWidth);
|
||||||
|
setCollapsing(false);
|
||||||
|
}, ANIMATION_MS);
|
||||||
|
}
|
||||||
|
}, [setWidth, minWidth, isCollapsing]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isResizing) {
|
if (isResizing) {
|
||||||
document.addEventListener("mousemove", handleDrag);
|
document.addEventListener("mousemove", handleDrag);
|
||||||
@ -81,32 +119,6 @@ const useResize = ({ width, minWidth, maxWidth, setWidth }) => {
|
|||||||
};
|
};
|
||||||
}, [isResizing, handleDrag, handleStopDrag]);
|
}, [isResizing, handleDrag, handleStopDrag]);
|
||||||
|
|
||||||
return { isAnimating, isSmallerThanMinimum, isResizing, handleStartDrag };
|
|
||||||
};
|
|
||||||
|
|
||||||
function Sidebar({ location, children }: Props) {
|
|
||||||
const theme = useTheme();
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { ui } = useStores();
|
|
||||||
const previousLocation = usePrevious(location);
|
|
||||||
|
|
||||||
const width = ui.sidebarWidth;
|
|
||||||
const maxWidth = theme.sidebarMaxWidth;
|
|
||||||
const minWidth = theme.sidebarMinWidth + 16; // padding
|
|
||||||
const collapsed = ui.editMode || ui.sidebarCollapsed;
|
|
||||||
|
|
||||||
const {
|
|
||||||
isAnimating,
|
|
||||||
isSmallerThanMinimum,
|
|
||||||
isResizing,
|
|
||||||
handleStartDrag,
|
|
||||||
} = useResize({
|
|
||||||
width,
|
|
||||||
minWidth,
|
|
||||||
maxWidth,
|
|
||||||
setWidth: ui.setSidebarWidth,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleReset = React.useCallback(() => {
|
const handleReset = React.useCallback(() => {
|
||||||
ui.setSidebarWidth(theme.sidebarWidth);
|
ui.setSidebarWidth(theme.sidebarWidth);
|
||||||
}, [ui, theme.sidebarWidth]);
|
}, [ui, theme.sidebarWidth]);
|
||||||
@ -124,49 +136,60 @@ function Sidebar({ location, children }: Props) {
|
|||||||
const style = React.useMemo(
|
const style = React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
width: `${width}px`,
|
width: `${width}px`,
|
||||||
left:
|
|
||||||
collapsed && !ui.mobileSidebarVisible
|
|
||||||
? `${-width + theme.sidebarCollapsedWidth}px`
|
|
||||||
: 0,
|
|
||||||
}),
|
}),
|
||||||
[width, collapsed, theme.sidebarCollapsedWidth, ui.mobileSidebarVisible]
|
[width]
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleStyle = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
right: "auto",
|
||||||
|
marginLeft: `${collapsed ? theme.sidebarCollapsedWidth : width}px`,
|
||||||
|
}),
|
||||||
|
[width, theme.sidebarCollapsedWidth, collapsed]
|
||||||
);
|
);
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<Container
|
<>
|
||||||
style={style}
|
<Container
|
||||||
$sidebarWidth={ui.sidebarWidth}
|
style={style}
|
||||||
$isAnimating={isAnimating}
|
$sidebarWidth={ui.sidebarWidth}
|
||||||
$isSmallerThanMinimum={isSmallerThanMinimum}
|
$isCollapsing={isCollapsing}
|
||||||
$mobileSidebarVisible={ui.mobileSidebarVisible}
|
$isAnimating={isAnimating}
|
||||||
$collapsed={collapsed}
|
$isSmallerThanMinimum={isSmallerThanMinimum}
|
||||||
column
|
$mobileSidebarVisible={ui.mobileSidebarVisible}
|
||||||
>
|
$collapsed={collapsed}
|
||||||
{!isResizing && (
|
column
|
||||||
<CollapseToggle
|
>
|
||||||
collapsed={ui.sidebarCollapsed}
|
{ui.mobileSidebarVisible && (
|
||||||
|
<Portal>
|
||||||
|
<Fade>
|
||||||
|
<Background onClick={ui.toggleMobileSidebar} />
|
||||||
|
</Fade>
|
||||||
|
</Portal>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
<ResizeBorder
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
onDoubleClick={ui.sidebarCollapsed ? undefined : handleReset}
|
||||||
|
$isResizing={isResizing}
|
||||||
|
/>
|
||||||
|
{ui.sidebarCollapsed && !ui.isEditing && (
|
||||||
|
<Toggle
|
||||||
|
onClick={ui.toggleCollapsedSidebar}
|
||||||
|
direction={"right"}
|
||||||
|
aria-label={t("Expand")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
{!ui.isEditing && (
|
||||||
|
<Toggle
|
||||||
|
style={toggleStyle}
|
||||||
onClick={ui.toggleCollapsedSidebar}
|
onClick={ui.toggleCollapsedSidebar}
|
||||||
|
direction={ui.sidebarCollapsed ? "right" : "left"}
|
||||||
|
aria-label={ui.sidebarCollapsed ? t("Expand") : t("Collapse")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{ui.mobileSidebarVisible && (
|
</>
|
||||||
<Portal>
|
|
||||||
<Fade>
|
|
||||||
<Background onClick={ui.toggleMobileSidebar} />
|
|
||||||
</Fade>
|
|
||||||
</Portal>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{children}
|
|
||||||
{!ui.sidebarCollapsed && (
|
|
||||||
<ResizeBorder
|
|
||||||
onMouseDown={handleStartDrag}
|
|
||||||
onDoubleClick={handleReset}
|
|
||||||
$isResizing={isResizing}
|
|
||||||
>
|
|
||||||
<ResizeHandle aria-label={t("Resize sidebar")} />
|
|
||||||
</ResizeBorder>
|
|
||||||
)}
|
|
||||||
</Container>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Fade in the sidebar on first render after page load
|
// Fade in the sidebar on first render after page load
|
||||||
@ -195,29 +218,36 @@ const Container = styled(Flex)`
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: ${(props) => props.theme.sidebarBackground};
|
background: ${(props) => props.theme.sidebarBackground};
|
||||||
transition: box-shadow, 100ms, ease-in-out, margin-left 100ms ease-out,
|
transition: box-shadow 100ms ease-in-out, transform 100ms ease-out,
|
||||||
left 100ms ease-out,
|
|
||||||
${(props) => props.theme.backgroundTransition}
|
${(props) => props.theme.backgroundTransition}
|
||||||
${(props) =>
|
${(props) =>
|
||||||
props.$isAnimating ? `,width ${BOUNCE_ANIMATION_MS}ms ease-out` : ""};
|
props.$isAnimating ? `,width ${ANIMATION_MS}ms ease-out` : ""};
|
||||||
margin-left: ${(props) => (props.$mobileSidebarVisible ? 0 : "-100%")};
|
transform: translateX(
|
||||||
|
${(props) => (props.$mobileSidebarVisible ? 0 : "-100%")}
|
||||||
|
);
|
||||||
z-index: ${(props) => props.theme.depths.sidebar};
|
z-index: ${(props) => props.theme.depths.sidebar};
|
||||||
max-width: 70%;
|
max-width: 70%;
|
||||||
min-width: 280px;
|
min-width: 280px;
|
||||||
|
|
||||||
|
${Positioner} {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
display: none;
|
display: none;
|
||||||
left: 0;
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
${breakpoint("tablet")`
|
${breakpoint("tablet")`
|
||||||
margin: 0;
|
margin: 0;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
transform: translateX(${(props) =>
|
||||||
|
props.$collapsed ? "calc(-100% + 16px)" : 0});
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus-within {
|
&:focus-within {
|
||||||
left: 0 !important;
|
transform: none;
|
||||||
box-shadow: ${(props) =>
|
box-shadow: ${(props) =>
|
||||||
props.$collapsed
|
props.$collapsed
|
||||||
? "rgba(0, 0, 0, 0.2) 1px 0 4px"
|
? "rgba(0, 0, 0, 0.2) 1px 0 4px"
|
||||||
@ -225,11 +255,11 @@ const Container = styled(Flex)`
|
|||||||
? "rgba(0, 0, 0, 0.1) inset -1px 0 2px"
|
? "rgba(0, 0, 0, 0.1) inset -1px 0 2px"
|
||||||
: "none"};
|
: "none"};
|
||||||
|
|
||||||
& ${CollapseButton} {
|
${Positioner} {
|
||||||
opacity: .75;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
& ${CollapseButton}:hover {
|
${ToggleButton} {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,4 +271,4 @@ const Container = styled(Flex)`
|
|||||||
`};
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default withRouter(observer(Sidebar));
|
export default observer(Sidebar);
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
// @flow
|
|
||||||
import { NextIcon, BackIcon } from "outline-icons";
|
|
||||||
import * as React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import styled from "styled-components";
|
|
||||||
import Tooltip from "components/Tooltip";
|
|
||||||
import { meta } from "utils/keyboard";
|
|
||||||
|
|
||||||
type Props = {|
|
|
||||||
collapsed: boolean,
|
|
||||||
onClick?: (event: SyntheticEvent<>) => void,
|
|
||||||
|};
|
|
||||||
|
|
||||||
function CollapseToggle({ collapsed, ...rest }: Props) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
tooltip={collapsed ? t("Expand") : t("Collapse")}
|
|
||||||
shortcut={`${meta}+.`}
|
|
||||||
delay={500}
|
|
||||||
placement="bottom"
|
|
||||||
>
|
|
||||||
<Button {...rest} tabIndex="-1" aria-hidden>
|
|
||||||
{collapsed ? (
|
|
||||||
<NextIcon color="currentColor" />
|
|
||||||
) : (
|
|
||||||
<BackIcon color="currentColor" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Button = styled.button`
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: 28px;
|
|
||||||
right: 8px;
|
|
||||||
border: 0;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
z-index: 1;
|
|
||||||
font-weight: 600;
|
|
||||||
color: ${(props) => props.theme.sidebarText};
|
|
||||||
background: transparent;
|
|
||||||
transition: opacity 100ms ease-in-out;
|
|
||||||
border-radius: 4px;
|
|
||||||
opacity: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: ${(props) => props.theme.white};
|
|
||||||
background: ${(props) => props.theme.primary};
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default CollapseToggle;
|
|
@ -42,8 +42,6 @@ class Collections extends React.Component<Props> {
|
|||||||
|
|
||||||
@keydown("n")
|
@keydown("n")
|
||||||
goToNewDocument() {
|
goToNewDocument() {
|
||||||
if (this.props.ui.editMode) return;
|
|
||||||
|
|
||||||
const { activeCollectionId } = this.props.ui;
|
const { activeCollectionId } = this.props.ui;
|
||||||
if (!activeCollectionId) return;
|
if (!activeCollectionId) return;
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import ResizeHandle from "./ResizeHandle";
|
|
||||||
|
|
||||||
const ResizeBorder = styled.div`
|
const ResizeBorder = styled.div`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -9,20 +8,6 @@ const ResizeBorder = styled.div`
|
|||||||
right: -6px;
|
right: -6px;
|
||||||
width: 12px;
|
width: 12px;
|
||||||
cursor: ew-resize;
|
cursor: ew-resize;
|
||||||
|
|
||||||
${(props) =>
|
|
||||||
props.$isResizing &&
|
|
||||||
`
|
|
||||||
${ResizeHandle} {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
${ResizeHandle} {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default ResizeBorder;
|
export default ResizeBorder;
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
// @flow
|
|
||||||
import styled from "styled-components";
|
|
||||||
import breakpoint from "styled-components-breakpoint";
|
|
||||||
|
|
||||||
const ResizeHandle = styled.button`
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 100ms ease-in-out;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
height: 40px;
|
|
||||||
right: -10px;
|
|
||||||
width: 8px;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
background: ${(props) => props.theme.sidebarBackground};
|
|
||||||
border-radius: 8px;
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: -24px;
|
|
||||||
bottom: -24px;
|
|
||||||
left: -12px;
|
|
||||||
right: -12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
background: ${(props) => props.theme.sidebarText};
|
|
||||||
}
|
|
||||||
|
|
||||||
${breakpoint("tablet")`
|
|
||||||
pointer-events: all;
|
|
||||||
cursor: ew-resize;
|
|
||||||
`}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export default ResizeHandle;
|
|
75
app/components/Sidebar/components/Toggle.js
Normal file
75
app/components/Sidebar/components/Toggle.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// @flow
|
||||||
|
import * as React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import breakpoint from "styled-components-breakpoint";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
direction: "left" | "right",
|
||||||
|
style?: Object,
|
||||||
|
onClick?: () => any,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Toggle = React.forwardRef<Props, HTMLButtonElement>(
|
||||||
|
({ direction = "left", onClick, style }: Props, ref) => {
|
||||||
|
return (
|
||||||
|
<Positioner style={style}>
|
||||||
|
<ToggleButton ref={ref} $direction={direction} onClick={onClick}>
|
||||||
|
<svg
|
||||||
|
width="13"
|
||||||
|
height="30"
|
||||||
|
viewBox="0 0 13 30"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M7.40242 1.48635C8.23085 0.0650039 10.0656 -0.421985 11.5005 0.39863C12.9354 1.21924 13.427 3.03671 12.5986 4.45806L5.59858 16.4681C4.77015 17.8894 2.93538 18.3764 1.5005 17.5558C0.065623 16.7352 -0.426002 14.9177 0.402425 13.4964L7.40242 1.48635Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M12.5986 25.5419C13.427 26.9633 12.9354 28.7808 11.5005 29.6014C10.0656 30.422 8.23087 29.935 7.40244 28.5136L0.402438 16.5036C-0.425989 15.0823 0.0656365 13.2648 1.50051 12.4442C2.93539 11.6236 4.77016 12.1106 5.59859 13.5319L12.5986 25.5419Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</ToggleButton>
|
||||||
|
</Positioner>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ToggleButton = styled.button`
|
||||||
|
opacity: 0;
|
||||||
|
background: none;
|
||||||
|
transition: opacity 100ms ease-in-out;
|
||||||
|
transform: translateY(-50%)
|
||||||
|
scaleX(${(props) => (props.$direction === "left" ? 1 : -1)});
|
||||||
|
position: absolute;
|
||||||
|
top: 50vh;
|
||||||
|
padding: 8px;
|
||||||
|
border: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
color: ${(props) => props.theme.divider};
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: ${(props) => props.theme.sidebarText};
|
||||||
|
}
|
||||||
|
|
||||||
|
${breakpoint("tablet")`
|
||||||
|
pointer-events: all;
|
||||||
|
cursor: pointer;
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Positioner = styled.div`
|
||||||
|
z-index: 2;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: -30px;
|
||||||
|
width: 30px;
|
||||||
|
|
||||||
|
&:hover ${ToggleButton}, &:focus-within ${ToggleButton} {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Toggle;
|
@ -21,7 +21,7 @@ class UiStore {
|
|||||||
@observable activeDocumentId: ?string;
|
@observable activeDocumentId: ?string;
|
||||||
@observable activeCollectionId: ?string;
|
@observable activeCollectionId: ?string;
|
||||||
@observable progressBarVisible: boolean = false;
|
@observable progressBarVisible: boolean = false;
|
||||||
@observable editMode: boolean = false;
|
@observable isEditing: boolean = false;
|
||||||
@observable tocVisible: boolean = false;
|
@observable tocVisible: boolean = false;
|
||||||
@observable mobileSidebarVisible: boolean = false;
|
@observable mobileSidebarVisible: boolean = false;
|
||||||
@observable sidebarWidth: number;
|
@observable sidebarWidth: number;
|
||||||
@ -151,12 +151,12 @@ class UiStore {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
enableEditMode = () => {
|
enableEditMode = () => {
|
||||||
this.editMode = true;
|
this.isEditing = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
disableEditMode = () => {
|
disableEditMode = () => {
|
||||||
this.editMode = false;
|
this.isEditing = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -153,7 +153,7 @@
|
|||||||
"react-waypoint": "^9.0.2",
|
"react-waypoint": "^9.0.2",
|
||||||
"react-window": "^1.8.6",
|
"react-window": "^1.8.6",
|
||||||
"reakit": "^1.3.4",
|
"reakit": "^1.3.4",
|
||||||
"rich-markdown-editor": "^11.1.6",
|
"rich-markdown-editor": "^11.2.0-0",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.2",
|
||||||
"sequelize": "^6.3.4",
|
"sequelize": "^6.3.4",
|
||||||
"sequelize-cli": "^6.2.0",
|
"sequelize-cli": "^6.2.0",
|
||||||
|
@ -86,8 +86,6 @@
|
|||||||
"Change Language": "Change Language",
|
"Change Language": "Change Language",
|
||||||
"Dismiss": "Dismiss",
|
"Dismiss": "Dismiss",
|
||||||
"Keyboard shortcuts": "Keyboard shortcuts",
|
"Keyboard shortcuts": "Keyboard shortcuts",
|
||||||
"Expand": "Expand",
|
|
||||||
"Collapse": "Collapse",
|
|
||||||
"New collection": "New collection",
|
"New collection": "New collection",
|
||||||
"Collections": "Collections",
|
"Collections": "Collections",
|
||||||
"Untitled": "Untitled",
|
"Untitled": "Untitled",
|
||||||
@ -110,7 +108,8 @@
|
|||||||
"Export Data": "Export Data",
|
"Export Data": "Export Data",
|
||||||
"Integrations": "Integrations",
|
"Integrations": "Integrations",
|
||||||
"Installation": "Installation",
|
"Installation": "Installation",
|
||||||
"Resize sidebar": "Resize sidebar",
|
"Expand": "Expand",
|
||||||
|
"Collapse": "Collapse",
|
||||||
"Unstar": "Unstar",
|
"Unstar": "Unstar",
|
||||||
"Star": "Star",
|
"Star": "Star",
|
||||||
"Appearance": "Appearance",
|
"Appearance": "Appearance",
|
||||||
|
Reference in New Issue
Block a user