Files
outline/app/components/Sidebar/Right.tsx
2024-07-03 20:37:56 -04:00

148 lines
3.7 KiB
TypeScript

import { m } from "framer-motion";
import { observer } from "mobx-react";
import * as React from "react";
import styled, { useTheme } from "styled-components";
import breakpoint from "styled-components-breakpoint";
import { depths, s } from "@shared/styles";
import ErrorBoundary from "~/components/ErrorBoundary";
import Flex from "~/components/Flex";
import ResizeBorder from "~/components/Sidebar/components/ResizeBorder";
import useMobile from "~/hooks/useMobile";
import useStores from "~/hooks/useStores";
import { sidebarAppearDuration } from "~/styles/animations";
interface Props extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode;
border?: boolean;
}
function Right({ children, border, className }: Props) {
const theme = useTheme();
const { ui } = useStores();
const [isResizing, setResizing] = React.useState(false);
const isMobile = useMobile();
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
);
ui.setRightSidebarWidth(width);
},
[minWidth, maxWidth, ui]
);
const handleReset = React.useCallback(() => {
ui.setRightSidebarWidth(theme.sidebarRightWidth);
}, [ui, theme.sidebarRightWidth]);
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((event) => {
event.preventDefault();
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: `${ui.sidebarRightWidth}px`,
}),
[ui.sidebarRightWidth]
);
const animationProps = {
initial: {
width: 0,
opacity: 0.9,
},
animate: {
transition: isResizing
? { duration: 0 }
: {
type: "spring",
bounce: 0.2,
duration: sidebarAppearDuration / 1000,
},
width: ui.sidebarRightWidth,
opacity: 1,
},
exit: {
width: 0,
opacity: 0,
},
};
return (
<Sidebar {...animationProps} $border={border} className={className}>
<Position style={style} column>
<ErrorBoundary>{children}</ErrorBoundary>
{!isMobile && (
<ResizeBorder
onMouseDown={handleMouseDown}
onDoubleClick={handleReset}
dir="right"
/>
)}
</Position>
</Sidebar>
);
}
const Position = styled(Flex)`
position: fixed;
top: 0;
bottom: 0;
max-width: 80%;
`;
const Sidebar = styled(m.div)<{
$border?: boolean;
}>`
display: block;
flex-shrink: 0;
background: ${s("background")};
max-width: 80%;
border-left: 1px solid ${s("divider")};
transition: border-left 100ms ease-in-out;
z-index: 1;
${breakpoint("mobile", "tablet")`
display: flex;
position: absolute;
top: 0;
right: 0;
bottom: 0;
z-index: ${depths.mobileSidebar};
`}
${breakpoint("tablet")`
position: relative;
`}
`;
export default observer(Right);