mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
163 lines
4.0 KiB
TypeScript
163 lines
4.0 KiB
TypeScript
import {useCallback, useEffect, useRef} from 'react';
|
|
import {createPortal} from 'react-dom';
|
|
import {css} from '@emotion/react';
|
|
import styled from '@emotion/styled';
|
|
|
|
import {IconClose} from 'sentry/icons';
|
|
import HookStore from 'sentry/stores/hookStore';
|
|
import {slideInLeft} from 'sentry/styles/animations';
|
|
import {space} from 'sentry/styles/space';
|
|
|
|
import type {CommonSidebarProps} from './types';
|
|
|
|
type PositionProps = Pick<CommonSidebarProps, 'orientation' | 'collapsed'>;
|
|
|
|
const PanelContainer = styled('div')<PositionProps>`
|
|
position: fixed;
|
|
bottom: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: ${p => p.theme.background};
|
|
color: ${p => p.theme.textColor};
|
|
border-right: 1px solid ${p => p.theme.border};
|
|
box-shadow: 1px 0 2px rgba(0, 0, 0, 0.06);
|
|
text-align: left;
|
|
animation: 200ms ${slideInLeft};
|
|
z-index: ${p => p.theme.zIndex.sidebar - 1};
|
|
|
|
${p =>
|
|
p.orientation === 'top'
|
|
? css`
|
|
top: ${p.theme.sidebar.mobileHeight};
|
|
left: 0;
|
|
right: 0;
|
|
`
|
|
: css`
|
|
width: 460px;
|
|
top: 0;
|
|
left: ${
|
|
p.collapsed ? p.theme.sidebar.collapsedWidth : p.theme.sidebar.expandedWidth
|
|
};
|
|
`};
|
|
`;
|
|
|
|
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
|
collapsed: CommonSidebarProps['collapsed'];
|
|
hidePanel: CommonSidebarProps['hidePanel'];
|
|
orientation: CommonSidebarProps['orientation'];
|
|
title?: string;
|
|
}
|
|
|
|
const getSidebarPortal = () => {
|
|
let portal = document.getElementById('sidebar-flyout-portal');
|
|
|
|
if (!portal) {
|
|
portal = document.createElement('div');
|
|
portal.setAttribute('id', 'sidebar-flyout-portal');
|
|
document.body.appendChild(portal);
|
|
}
|
|
|
|
return portal as HTMLDivElement;
|
|
};
|
|
|
|
function SidebarPanel({
|
|
orientation,
|
|
collapsed,
|
|
hidePanel,
|
|
title,
|
|
children,
|
|
...props
|
|
}: Props): React.ReactElement {
|
|
const portalEl = useRef<HTMLDivElement>(getSidebarPortal());
|
|
|
|
const panelCloseHandler = useCallback(
|
|
(evt: MouseEvent) => {
|
|
if (!(evt.target instanceof Element)) {
|
|
return;
|
|
}
|
|
|
|
if (portalEl.current.contains(evt.target)) {
|
|
return;
|
|
}
|
|
|
|
// If we are in Sandbox, don't hide panel when the modal is clicked (before the email is added)
|
|
const blockHideSidebar = HookStore.get('onboarding:block-hide-sidebar')[0]?.();
|
|
if (blockHideSidebar) {
|
|
return;
|
|
}
|
|
hidePanel();
|
|
},
|
|
[hidePanel]
|
|
);
|
|
|
|
useEffect(() => {
|
|
// Wait one tick to setup the click handler so we don't detect the click
|
|
// that is bubbling up from opening the panel itself
|
|
window.setTimeout(() => document.addEventListener('click', panelCloseHandler));
|
|
|
|
return function cleanup() {
|
|
window.setTimeout(() => document.removeEventListener('click', panelCloseHandler));
|
|
};
|
|
}, [panelCloseHandler]);
|
|
|
|
return createPortal(
|
|
<PanelContainer
|
|
role="dialog"
|
|
collapsed={collapsed}
|
|
orientation={orientation}
|
|
{...props}
|
|
>
|
|
{title ? (
|
|
<SidebarPanelHeader>
|
|
<Title>{title}</Title>
|
|
<PanelClose onClick={hidePanel} />
|
|
</SidebarPanelHeader>
|
|
) : null}
|
|
<SidebarPanelBody hasHeader={!!title}>{children}</SidebarPanelBody>
|
|
</PanelContainer>,
|
|
portalEl.current
|
|
);
|
|
}
|
|
|
|
export default SidebarPanel;
|
|
|
|
const SidebarPanelHeader = styled('div')`
|
|
border-bottom: 1px solid ${p => p.theme.border};
|
|
padding: ${space(3)};
|
|
background: ${p => p.theme.background};
|
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
height: 60px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
flex-shrink: 1;
|
|
`;
|
|
|
|
const SidebarPanelBody = styled('div')<{hasHeader: boolean}>`
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-grow: 1;
|
|
overflow: auto;
|
|
position: relative;
|
|
`;
|
|
|
|
const PanelClose = styled(IconClose)`
|
|
color: ${p => p.theme.subText};
|
|
cursor: pointer;
|
|
position: relative;
|
|
padding: ${space(0.75)};
|
|
|
|
&:hover {
|
|
color: ${p => p.theme.textColor};
|
|
}
|
|
`;
|
|
|
|
PanelClose.defaultProps = {
|
|
size: 'lg',
|
|
};
|
|
|
|
const Title = styled('div')`
|
|
font-size: ${p => p.theme.fontSizeExtraLarge};
|
|
margin: 0;
|
|
`;
|