Sidebar improvements from comments branch (#4419)
This commit is contained in:
@@ -9,7 +9,7 @@ import PaginatedEventList from "~/components/PaginatedEventList";
|
||||
import useKeyDown from "~/hooks/useKeyDown";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { documentUrl } from "~/utils/routeHelpers";
|
||||
import Sidebar from "./RightSidebar";
|
||||
import Sidebar from "./SidebarLayout";
|
||||
|
||||
const EMPTY_ARRAY: Event[] = [];
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import useKeyDown from "~/hooks/useKeyDown";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useTextSelection from "~/hooks/useTextSelection";
|
||||
import { documentUrl } from "~/utils/routeHelpers";
|
||||
import Sidebar from "./RightSidebar";
|
||||
import Sidebar from "./SidebarLayout";
|
||||
|
||||
function Insights() {
|
||||
const { views, documents } = useStores();
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
import { m } from "framer-motion";
|
||||
import { observer } from "mobx-react";
|
||||
import { BackIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import breakpoint from "styled-components-breakpoint";
|
||||
import Button from "~/components/Button";
|
||||
import Flex from "~/components/Flex";
|
||||
import Scrollable from "~/components/Scrollable";
|
||||
import ResizeBorder from "~/components/Sidebar/components/ResizeBorder";
|
||||
import Tooltip from "~/components/Tooltip";
|
||||
import usePersistedState from "~/hooks/usePersistedState";
|
||||
|
||||
type Props = React.HTMLAttributes<HTMLDivElement> & {
|
||||
title: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
onClose: React.MouseEventHandler;
|
||||
border?: boolean;
|
||||
};
|
||||
|
||||
function RightSidebar({ title, onClose, children, border, className }: Props) {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const [width, setWidth] = usePersistedState(
|
||||
"rightSidebarWidth",
|
||||
theme.sidebarWidth
|
||||
);
|
||||
const [isResizing, setResizing] = React.useState(false);
|
||||
const maxWidth = theme.sidebarMaxWidth;
|
||||
const minWidth = theme.sidebarMinWidth + 16; // padding
|
||||
|
||||
const handleDrag = React.useCallback(
|
||||
(event: MouseEvent) => {
|
||||
// suppresses text selection
|
||||
event.preventDefault();
|
||||
const width = Math.max(
|
||||
Math.min(window.innerWidth - event.pageX, maxWidth),
|
||||
minWidth
|
||||
);
|
||||
setWidth(width);
|
||||
},
|
||||
[minWidth, maxWidth, setWidth]
|
||||
);
|
||||
|
||||
const handleReset = React.useCallback(() => {
|
||||
setWidth(theme.sidebarWidth);
|
||||
}, [setWidth, theme.sidebarWidth]);
|
||||
|
||||
const handleStopDrag = React.useCallback(() => {
|
||||
setResizing(false);
|
||||
|
||||
if (document.activeElement) {
|
||||
// @ts-expect-error ts-migrate(2339) FIXME: Property 'blur' does not exist on type 'Element'.
|
||||
document.activeElement.blur();
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleMouseDown = React.useCallback(() => {
|
||||
setResizing(true);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (isResizing) {
|
||||
document.addEventListener("mousemove", handleDrag);
|
||||
document.addEventListener("mouseup", handleStopDrag);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("mousemove", handleDrag);
|
||||
document.removeEventListener("mouseup", handleStopDrag);
|
||||
};
|
||||
}, [isResizing, handleDrag, handleStopDrag]);
|
||||
|
||||
const style = React.useMemo(
|
||||
() => ({
|
||||
width: `${width}px`,
|
||||
}),
|
||||
[width]
|
||||
);
|
||||
|
||||
return (
|
||||
<Sidebar
|
||||
initial={{
|
||||
width: 0,
|
||||
}}
|
||||
animate={{
|
||||
transition: isResizing
|
||||
? { duration: 0 }
|
||||
: {
|
||||
type: "spring",
|
||||
bounce: 0.2,
|
||||
duration: 0.6,
|
||||
},
|
||||
width,
|
||||
}}
|
||||
exit={{
|
||||
width: 0,
|
||||
}}
|
||||
$border={border}
|
||||
className={className}
|
||||
>
|
||||
<Position style={style} column>
|
||||
<Header>
|
||||
<Title>{title}</Title>
|
||||
<Tooltip tooltip={t("Close")} shortcut="Esc" delay={500}>
|
||||
<Button
|
||||
icon={<ForwardIcon />}
|
||||
onClick={onClose}
|
||||
borderOnHover
|
||||
neutral
|
||||
/>
|
||||
</Tooltip>
|
||||
</Header>
|
||||
<Scrollable topShadow>{children}</Scrollable>
|
||||
<ResizeBorder
|
||||
onMouseDown={handleMouseDown}
|
||||
onDoubleClick={handleReset}
|
||||
dir="right"
|
||||
/>
|
||||
</Position>
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
||||
const ForwardIcon = styled(BackIcon)`
|
||||
transform: rotate(180deg);
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const Position = styled(Flex)`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
`;
|
||||
|
||||
const Sidebar = styled(m.div)<{ $border?: boolean }>`
|
||||
display: none;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
background: ${(props) => props.theme.background};
|
||||
width: ${(props) => props.theme.sidebarWidth}px;
|
||||
border-left: 1px solid ${(props) => props.theme.divider};
|
||||
transition: border-left 100ms ease-in-out;
|
||||
z-index: 1;
|
||||
|
||||
${breakpoint("tablet")`
|
||||
display: flex;
|
||||
`};
|
||||
`;
|
||||
|
||||
const Title = styled(Flex)`
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
width: 0;
|
||||
flex-grow: 1;
|
||||
`;
|
||||
|
||||
const Header = styled(Flex)`
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: 16px 12px 16px 16px;
|
||||
color: ${(props) => props.theme.text};
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
export default observer(RightSidebar);
|
||||
66
app/scenes/Document/components/SidebarLayout.tsx
Normal file
66
app/scenes/Document/components/SidebarLayout.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { BackIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import Button from "~/components/Button";
|
||||
import Flex from "~/components/Flex";
|
||||
import Scrollable from "~/components/Scrollable";
|
||||
import Tooltip from "~/components/Tooltip";
|
||||
|
||||
type Props = React.HTMLAttributes<HTMLDivElement> & {
|
||||
title: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
onClose: React.MouseEventHandler;
|
||||
border?: boolean;
|
||||
};
|
||||
|
||||
function SidebarLayout({ title, onClose, children }: Props) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header>
|
||||
<Title>{title}</Title>
|
||||
<Tooltip tooltip={t("Close")} shortcut="Esc" delay={500}>
|
||||
<Button
|
||||
icon={<ForwardIcon />}
|
||||
onClick={onClose}
|
||||
borderOnHover
|
||||
neutral
|
||||
/>
|
||||
</Tooltip>
|
||||
</Header>
|
||||
<Scrollable topShadow>{children}</Scrollable>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const ForwardIcon = styled(BackIcon)`
|
||||
transform: rotate(180deg);
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const Title = styled(Flex)`
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
width: 0;
|
||||
flex-grow: 1;
|
||||
`;
|
||||
|
||||
const Header = styled(Flex)`
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: 16px 12px 16px 16px;
|
||||
color: ${(props) => props.theme.text};
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
export default observer(SidebarLayout);
|
||||
Reference in New Issue
Block a user