mirror of
https://github.com/Unleash/unleash.git
synced 2025-10-13 11:17:26 +02:00
https://linear.app/unleash/issue/2-2662/make-the-event-timeline-available-globally-through-a-new-header-button https://github.com/user-attachments/assets/bde38ee8-cdd8-409d-a95e-0c06189e3d9b (In the video, you’ll notice a slight delay before new events show up. This happens because the timeline automatically refreshes every 10 seconds) Removes the event timeline from the event log and integrates it into a new header option. I chose a middle-ground approach between options 1 and 2 from our Figma sketches. This solution provides the best of both worlds IMO: the timeline stands out as a global component, distinct from the current page context, while sliding in rather than overlapping the content. This way, users can view the timeline alongside the page content.
241 lines
8.6 KiB
TypeScript
241 lines
8.6 KiB
TypeScript
import { forwardRef, useState, type ReactNode } from 'react';
|
|
import { Box, Grid, styled, useMediaQuery, useTheme } from '@mui/material';
|
|
import Header from 'component/menu/Header/Header';
|
|
import OldHeader from 'component/menu/Header/OldHeader';
|
|
import Footer from 'component/menu/Footer/Footer';
|
|
import Proclamation from 'component/common/Proclamation/Proclamation';
|
|
import BreadcrumbNav from 'component/common/BreadcrumbNav/BreadcrumbNav';
|
|
import textureImage from 'assets/img/texture.png';
|
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
|
import { SkipNavLink } from 'component/common/SkipNavLink/SkipNavLink';
|
|
import { SkipNavTarget } from 'component/common/SkipNavLink/SkipNavTarget';
|
|
import { formatAssetPath } from 'utils/formatPath';
|
|
import { useOptionalPathParam } from 'hooks/useOptionalPathParam';
|
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
|
import { useChangeRequestsEnabled } from 'hooks/useChangeRequestsEnabled';
|
|
import { DraftBanner } from './DraftBanner/DraftBanner';
|
|
import { ThemeMode } from 'component/common/ThemeMode/ThemeMode';
|
|
import { NavigationSidebar } from './NavigationSidebar/NavigationSidebar';
|
|
import { useUiFlag } from 'hooks/useUiFlag';
|
|
import { EventTimeline } from 'component/events/EventTimeline/EventTimeline';
|
|
import AnimateOnMount from 'component/common/AnimateOnMount/AnimateOnMount';
|
|
|
|
interface IMainLayoutProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
const MainLayoutContainer = styled(Grid)(() => ({
|
|
height: '100%',
|
|
justifyContent: 'space-between',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
flexGrow: 1,
|
|
position: 'relative',
|
|
}));
|
|
|
|
const MainLayoutContentWrapper = styled('main')(({ theme }) => ({
|
|
margin: theme.spacing(0, 'auto'),
|
|
flexGrow: 1,
|
|
width: '100%',
|
|
backgroundColor: theme.palette.background.application,
|
|
position: 'relative',
|
|
}));
|
|
|
|
const OldMainLayoutContent = styled(Grid)(({ theme }) => ({
|
|
width: '100%',
|
|
maxWidth: '1512px',
|
|
margin: '0 auto',
|
|
paddingLeft: theme.spacing(2),
|
|
paddingRight: theme.spacing(2),
|
|
[theme.breakpoints.down('lg')]: {
|
|
maxWidth: '1250px',
|
|
paddingLeft: theme.spacing(1),
|
|
paddingRight: theme.spacing(1),
|
|
},
|
|
[theme.breakpoints.down(1024)]: {
|
|
marginLeft: 0,
|
|
marginRight: 0,
|
|
},
|
|
[theme.breakpoints.down('sm')]: {
|
|
minWidth: '100%',
|
|
},
|
|
}));
|
|
|
|
const NewMainLayoutContent = styled(Grid)(({ theme }) => ({
|
|
minWidth: 0, // this is a fix for overflowing flex
|
|
maxWidth: '1512px',
|
|
margin: '0 auto',
|
|
paddingLeft: theme.spacing(2),
|
|
paddingRight: theme.spacing(2),
|
|
[theme.breakpoints.down(1856)]: {
|
|
marginLeft: theme.spacing(7),
|
|
marginRight: theme.spacing(7),
|
|
},
|
|
[theme.breakpoints.down('lg')]: {
|
|
maxWidth: '1250px',
|
|
paddingLeft: theme.spacing(1),
|
|
paddingRight: theme.spacing(1),
|
|
},
|
|
[theme.breakpoints.down(1024)]: {
|
|
marginLeft: 0,
|
|
marginRight: 0,
|
|
},
|
|
[theme.breakpoints.down('sm')]: {
|
|
minWidth: '100%',
|
|
},
|
|
minHeight: '94vh',
|
|
}));
|
|
|
|
const StyledImg = styled('img')(() => ({
|
|
display: 'block',
|
|
position: 'fixed',
|
|
zIndex: 0,
|
|
bottom: 0,
|
|
right: 0,
|
|
width: 400,
|
|
pointerEvents: 'none',
|
|
userSelect: 'none',
|
|
}));
|
|
|
|
const MainLayoutContentContainer = styled('div')(({ theme }) => ({
|
|
height: '100%',
|
|
padding: theme.spacing(0, 0, 6.5, 0),
|
|
position: 'relative',
|
|
[theme.breakpoints.down('md')]: {
|
|
padding: theme.spacing(0, 1.5, 6.5, 1.5),
|
|
},
|
|
zIndex: 200,
|
|
}));
|
|
|
|
const timelineAnimations = {
|
|
start: {
|
|
maxHeight: 0,
|
|
overflow: 'hidden',
|
|
transition: 'max-height 0.3s ease-in-out',
|
|
},
|
|
enter: {
|
|
maxHeight: '105px',
|
|
},
|
|
leave: {
|
|
maxHeight: 0,
|
|
},
|
|
};
|
|
|
|
export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>(
|
|
({ children }, ref) => {
|
|
const { uiConfig } = useUiConfig();
|
|
const projectId = useOptionalPathParam('projectId');
|
|
const { isChangeRequestConfiguredInAnyEnv } = useChangeRequestsEnabled(
|
|
projectId || '',
|
|
);
|
|
const eventTimeline = useUiFlag('eventTimeline');
|
|
const [showTimeline, setShowTimeline] = useState(false);
|
|
|
|
const sidebarNavigationEnabled = useUiFlag('navigationSidebar');
|
|
const StyledMainLayoutContent = sidebarNavigationEnabled
|
|
? NewMainLayoutContent
|
|
: OldMainLayoutContent;
|
|
const theme = useTheme();
|
|
const isSmallScreen = useMediaQuery(theme.breakpoints.down('lg'));
|
|
|
|
return (
|
|
<>
|
|
<SkipNavLink />
|
|
<ConditionallyRender
|
|
condition={sidebarNavigationEnabled}
|
|
show={
|
|
<Header
|
|
showTimeline={showTimeline}
|
|
setShowTimeline={setShowTimeline}
|
|
/>
|
|
}
|
|
elseShow={
|
|
<OldHeader
|
|
showTimeline={showTimeline}
|
|
setShowTimeline={setShowTimeline}
|
|
/>
|
|
}
|
|
/>
|
|
|
|
<SkipNavTarget />
|
|
<MainLayoutContainer>
|
|
<MainLayoutContentWrapper>
|
|
<ConditionallyRender
|
|
condition={Boolean(
|
|
projectId &&
|
|
isChangeRequestConfiguredInAnyEnv(),
|
|
)}
|
|
show={<DraftBanner project={projectId || ''} />}
|
|
/>
|
|
|
|
<Box
|
|
sx={(theme) => ({
|
|
display: 'flex',
|
|
mt: theme.spacing(0.25),
|
|
})}
|
|
>
|
|
<ConditionallyRender
|
|
condition={
|
|
sidebarNavigationEnabled && !isSmallScreen
|
|
}
|
|
show={<NavigationSidebar />}
|
|
/>
|
|
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
flexGrow: 1,
|
|
minWidth: 0,
|
|
}}
|
|
>
|
|
<AnimateOnMount
|
|
mounted={eventTimeline && showTimeline}
|
|
start={timelineAnimations.start}
|
|
enter={timelineAnimations.enter}
|
|
leave={timelineAnimations.leave}
|
|
>
|
|
<Box
|
|
sx={(theme) => ({
|
|
padding: theme.spacing(2),
|
|
backgroundColor:
|
|
theme.palette.background.paper,
|
|
})}
|
|
>
|
|
<EventTimeline />
|
|
</Box>
|
|
</AnimateOnMount>
|
|
|
|
<StyledMainLayoutContent>
|
|
<MainLayoutContentContainer ref={ref}>
|
|
<BreadcrumbNav />
|
|
<Proclamation toast={uiConfig.toast} />
|
|
{children}
|
|
</MainLayoutContentContainer>
|
|
</StyledMainLayoutContent>
|
|
</Box>
|
|
</Box>
|
|
|
|
<ThemeMode
|
|
darkmode={
|
|
<StyledImg
|
|
style={{ opacity: 0.06 }}
|
|
src={formatAssetPath(textureImage)}
|
|
alt=''
|
|
/>
|
|
}
|
|
lightmode={
|
|
<StyledImg
|
|
src={formatAssetPath(textureImage)}
|
|
alt=''
|
|
/>
|
|
}
|
|
/>
|
|
</MainLayoutContentWrapper>
|
|
<Footer />
|
|
</MainLayoutContainer>
|
|
</>
|
|
);
|
|
},
|
|
);
|