From 729acfd318a506dd9f84a37eb5327d4d058ad006 Mon Sep 17 00:00:00 2001 From: David Leek Date: Tue, 1 Oct 2024 15:32:54 +0200 Subject: [PATCH] chore: timeline ux alignment (#8283) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://linear.app/unleash/issue/2-2703/align-with-ux Timeline UI/UX improvements after sync with UX, including: - Added some spacing between each event in the grouping tooltip - Aligned the x events occurred header with filter dropdown - Improved the strategy icon somewhat so it doesn't look as off center - New timeline icon - Improve icon position relative to timestamp on each event in the grouping tooltip - Changed text color in dropdowns to a lighter gray - Removed bold formatting in tooltip - Adjusted paddings and margins - Added close button - Added shadow - Added left border There are a few details missing, which will be tackled in separate PRs. ![image](https://github.com/user-attachments/assets/b911696e-1a50-4968-9b73-b01af626d44e) --------- Co-authored-by: Nuno Góis --- .../events/EventTimeline/EventTimeline.tsx | 76 ++++++++++++------- .../EventTimelineEventCircle.tsx | 6 +- .../EventTimelineEventGroup.tsx | 2 +- .../EventTimelineEventTooltip.tsx | 51 ++++++++----- .../EventTimelineHeader.tsx | 30 +++++++- .../layout/MainLayout/MainLayout.tsx | 15 +++- .../MainLayout/MainLayoutEventTimeline.tsx | 51 +++++++++---- frontend/src/component/menu/Header/Header.tsx | 4 +- .../src/component/menu/Header/OldHeader.tsx | 4 +- 9 files changed, 166 insertions(+), 73 deletions(-) diff --git a/frontend/src/component/events/EventTimeline/EventTimeline.tsx b/frontend/src/component/events/EventTimeline/EventTimeline.tsx index dda221500a41..e71cd6ef6e1c 100644 --- a/frontend/src/component/events/EventTimeline/EventTimeline.tsx +++ b/frontend/src/component/events/EventTimeline/EventTimeline.tsx @@ -4,7 +4,7 @@ import { startOfDay, sub } from 'date-fns'; import { useEventSearch } from 'hooks/api/getters/useEventSearch/useEventSearch'; import { EventTimelineEventGroup } from './EventTimelineEventGroup/EventTimelineEventGroup'; import { EventTimelineHeader } from './EventTimelineHeader/EventTimelineHeader'; -import { useEventTimeline } from './useEventTimeline'; +import type { TimeSpanOption } from './useEventTimeline'; import { useMemo } from 'react'; import { useSignalQuery } from 'hooks/api/getters/useSignalQuery/useSignalQuery'; import type { ISignalQuerySignal } from 'interfaces/signal'; @@ -30,6 +30,12 @@ const StyledRow = styled('div')({ justifyContent: 'space-between', }); +const StyledTimelineBody = styled('div')(({ theme }) => ({ + display: 'flex', + flexDirection: 'column', + padding: theme.spacing(1, 0), +})); + const StyledTimelineContainer = styled('div')(({ theme }) => ({ position: 'relative', height: theme.spacing(1), @@ -151,10 +157,21 @@ const getTimelineEvent = ( } }; -export const EventTimeline = () => { - const { timeSpan, environment, setTimeSpan, setEnvironment } = - useEventTimeline(); - +interface IEventTimelineProps { + timeSpan: TimeSpanOption; + environment: IEnvironment | undefined; + setTimeSpan: (timeSpan: TimeSpanOption) => void; + setEnvironment: (environment: IEnvironment) => void; + setOpen: (open: boolean) => void; +} + +export const EventTimeline = ({ + timeSpan, + environment, + setTimeSpan, + setEnvironment, + setOpen, +}: IEventTimelineProps) => { const endDate = new Date(); const startDate = sub(endDate, timeSpan.value); const endTime = endDate.getTime(); @@ -235,31 +252,34 @@ export const EventTimeline = () => { setTimeSpan={setTimeSpan} environment={environment} setEnvironment={setEnvironment} + setOpen={setOpen} /> - - - - {groups.map((group) => ( - - ))} - - - - {timeSpan.markers[0]} - {timeSpan.markers.slice(1).map((marker) => ( - - - {marker} - - ))} - now - + + + + + {groups.map((group) => ( + + ))} + + + + {timeSpan.markers[0]} + {timeSpan.markers.slice(1).map((marker) => ( + + + {marker} + + ))} + now + + ); }; diff --git a/frontend/src/component/events/EventTimeline/EventTimelineEventGroup/EventTimelineEventCircle.tsx b/frontend/src/component/events/EventTimeline/EventTimelineEventGroup/EventTimelineEventCircle.tsx index 5d3969f56d66..22bdd0ca38ff 100644 --- a/frontend/src/component/events/EventTimeline/EventTimelineEventGroup/EventTimelineEventCircle.tsx +++ b/frontend/src/component/events/EventTimeline/EventTimelineEventGroup/EventTimelineEventCircle.tsx @@ -47,7 +47,11 @@ const getEventIcon = (type: TimelineEventType) => { return ; } if (type.startsWith('strategy-') || type.startsWith('feature-strategy-')) { - return ; + return ( + + ); } if (type.startsWith('feature-')) { return ; diff --git a/frontend/src/component/events/EventTimeline/EventTimelineEventGroup/EventTimelineEventGroup.tsx b/frontend/src/component/events/EventTimeline/EventTimelineEventGroup/EventTimelineEventGroup.tsx index 9c458a4cbd69..14e628783710 100644 --- a/frontend/src/component/events/EventTimeline/EventTimelineEventGroup/EventTimelineEventGroup.tsx +++ b/frontend/src/component/events/EventTimeline/EventTimelineEventGroup/EventTimelineEventGroup.tsx @@ -36,7 +36,7 @@ export const EventTimelineEventGroup = ({ } - maxWidth={320} + maxWidth={350} arrow > ({ whiteSpace: 'nowrap', })); +const StyledTooltipItemList = styled('div')(({ theme }) => ({ + marginTop: theme.spacing(1), + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2), +})); + const StyledTooltipItem = styled('div')(({ theme }) => ({ display: 'flex', gap: theme.spacing(1), - marginBottom: theme.spacing(1), })); const StyledEventTimelineEventCircle = styled(EventTimelineEventCircle)( ({ theme }) => ({ - marginTop: theme.spacing(0.5), + marginTop: theme.spacing(0.125), height: theme.spacing(2.5), width: theme.spacing(2.5), transition: 'none', '& > svg': { - height: theme.spacing(2), + height: theme.spacing(1.75), }, '&:hover': { transform: 'none', @@ -56,6 +63,8 @@ const StyledEventTimelineEventCircle = styled(EventTimelineEventCircle)( }), ); +const BoldToNormal = ({ children }: HTMLAttributes) => children; + interface IEventTimelineEventTooltipProps { group: TimelineEventGroup; } @@ -78,7 +87,9 @@ export const EventTimelineEventTooltip = ({ {event.label} {eventDateTime} - {event.summary} + + {event.summary} + ); } @@ -97,20 +108,24 @@ export const EventTimelineEventTooltip = ({ {eventDate} - {group.map((event) => ( - - -
- - {formatDateHMS( - event.timestamp, - locationSettings?.locale, - )} - - {event.summary} -
-
- ))} + + {group.map((event) => ( + + +
+ + {formatDateHMS( + event.timestamp, + locationSettings?.locale, + )} + + + {event.summary} + +
+
+ ))} +
); }; diff --git a/frontend/src/component/events/EventTimeline/EventTimelineHeader/EventTimelineHeader.tsx b/frontend/src/component/events/EventTimeline/EventTimelineHeader/EventTimelineHeader.tsx index d7c29ea76d4b..4fcda1262384 100644 --- a/frontend/src/component/events/EventTimeline/EventTimelineHeader/EventTimelineHeader.tsx +++ b/frontend/src/component/events/EventTimeline/EventTimelineHeader/EventTimelineHeader.tsx @@ -1,9 +1,16 @@ -import { MenuItem, styled, TextField } from '@mui/material'; +import { + IconButton, + MenuItem, + styled, + TextField, + Tooltip, +} from '@mui/material'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { useEnvironments } from 'hooks/api/getters/useEnvironments/useEnvironments'; import type { IEnvironment } from 'interfaces/environments'; import { useEffect, useMemo } from 'react'; import { type TimeSpanOption, timeSpanOptions } from '../useEventTimeline'; +import CloseIcon from '@mui/icons-material/Close'; const StyledCol = styled('div')(({ theme }) => ({ display: 'flex', @@ -12,9 +19,9 @@ const StyledCol = styled('div')(({ theme }) => ({ })); const StyledFilter = styled(TextField)(({ theme }) => ({ - color: theme.palette.text.secondary, '& > div': { background: 'transparent', + color: theme.palette.text.secondary, '& > .MuiSelect-select': { padding: theme.spacing(0.5, 4, 0.5, 1), background: 'transparent', @@ -23,12 +30,17 @@ const StyledFilter = styled(TextField)(({ theme }) => ({ }, })); +const StyledTimelineEventsCount = styled('span')(({ theme }) => ({ + marginTop: theme.spacing(0.25), +})); + interface IEventTimelineHeaderProps { totalEvents: number; timeSpan: TimeSpanOption; setTimeSpan: (timeSpan: TimeSpanOption) => void; environment: IEnvironment | undefined; setEnvironment: (environment: IEnvironment) => void; + setOpen: (open: boolean) => void; } export const EventTimelineHeader = ({ @@ -37,6 +49,7 @@ export const EventTimelineHeader = ({ setTimeSpan, environment, setEnvironment, + setOpen, }: IEventTimelineHeaderProps) => { const { environments } = useEnvironments(); @@ -57,10 +70,10 @@ export const EventTimelineHeader = ({ return ( <> - + {totalEvents} event {totalEvents === 1 ? '' : 's'} - + )} /> + + setOpen(false)} + > + + + ); diff --git a/frontend/src/component/layout/MainLayout/MainLayout.tsx b/frontend/src/component/layout/MainLayout/MainLayout.tsx index ab8af57676bf..0d3f581d52b2 100644 --- a/frontend/src/component/layout/MainLayout/MainLayout.tsx +++ b/frontend/src/component/layout/MainLayout/MainLayout.tsx @@ -118,8 +118,14 @@ export const MainLayout = forwardRef( projectId || '', ); const eventTimeline = useUiFlag('eventTimeline') && !isOss(); - const { open: showTimeline, setOpen: setShowTimeline } = - useEventTimeline(); + const { + open: showTimeline, + timeSpan, + environment, + setOpen: setShowTimeline, + setTimeSpan, + setEnvironment, + } = useEventTimeline(); const sidebarNavigationEnabled = useUiFlag('navigationSidebar'); const StyledMainLayoutContent = sidebarNavigationEnabled @@ -181,6 +187,11 @@ export const MainLayout = forwardRef( > diff --git a/frontend/src/component/layout/MainLayout/MainLayoutEventTimeline.tsx b/frontend/src/component/layout/MainLayout/MainLayoutEventTimeline.tsx index 4272deccfc11..db44cb5ba33b 100644 --- a/frontend/src/component/layout/MainLayout/MainLayoutEventTimeline.tsx +++ b/frontend/src/component/layout/MainLayout/MainLayoutEventTimeline.tsx @@ -1,20 +1,38 @@ import { Box, styled } from '@mui/material'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { EventTimeline } from 'component/events/EventTimeline/EventTimeline'; +import type { TimeSpanOption } from 'component/events/EventTimeline/useEventTimeline'; +import type { IEnvironment } from 'interfaces/environments'; import { useEffect, useState } from 'react'; -interface IMainLayoutEventTimelineProps { - open: boolean; -} - -const StyledEventTimelineWrapper = styled(Box)(({ theme }) => ({ +const StyledEventTimelineSlider = styled(Box)(({ theme }) => ({ backgroundColor: theme.palette.background.paper, height: '105px', overflow: 'hidden', + boxShadow: theme.boxShadows.popup, + borderLeft: `1px solid ${theme.palette.divider}`, +})); + +const StyledEventTimelineWrapper = styled(Box)(({ theme }) => ({ + padding: theme.spacing(1.5, 2), })); +interface IMainLayoutEventTimelineProps { + open: boolean; + timeSpan: TimeSpanOption; + environment: IEnvironment | undefined; + setTimeSpan: (timeSpan: TimeSpanOption) => void; + setEnvironment: (environment: IEnvironment) => void; + setOpen: (open: boolean) => void; +} + export const MainLayoutEventTimeline = ({ open, + timeSpan, + environment, + setTimeSpan, + setEnvironment, + setOpen, }: IMainLayoutEventTimelineProps) => { const [isInitialLoad, setIsInitialLoad] = useState(true); @@ -23,7 +41,7 @@ export const MainLayoutEventTimeline = ({ }, []); return ( - - ({ - padding: theme.spacing(2), - backgroundColor: theme.palette.background.paper, - })} - > + } + show={ + + } /> - - + + ); }; diff --git a/frontend/src/component/menu/Header/Header.tsx b/frontend/src/component/menu/Header/Header.tsx index 27bf1ef90316..07591f2567ec 100644 --- a/frontend/src/component/menu/Header/Header.tsx +++ b/frontend/src/component/menu/Header/Header.tsx @@ -33,7 +33,7 @@ import { useAdminRoutes } from 'component/admin/useAdminRoutes'; import InviteLinkButton from './InviteLink/InviteLinkButton/InviteLinkButton'; import { useUiFlag } from 'hooks/useUiFlag'; import { CommandBar } from 'component/commandBar/CommandBar'; -import TimelineIcon from '@mui/icons-material/Timeline'; +import LinearScaleIcon from '@mui/icons-material/LinearScale'; const HeaderComponent = styled(AppBar)(({ theme }) => ({ backgroundColor: theme.palette.background.paper, @@ -204,7 +204,7 @@ const Header = ({ showTimeline, setShowTimeline }: IHeaderProps) => { } size='large' > - + } diff --git a/frontend/src/component/menu/Header/OldHeader.tsx b/frontend/src/component/menu/Header/OldHeader.tsx index 40c4f9c5ea78..2c2f77721ad0 100644 --- a/frontend/src/component/menu/Header/OldHeader.tsx +++ b/frontend/src/component/menu/Header/OldHeader.tsx @@ -36,7 +36,7 @@ import { Notifications } from 'component/common/Notifications/Notifications'; import { useAdminRoutes } from 'component/admin/useAdminRoutes'; import InviteLinkButton from './InviteLink/InviteLinkButton/InviteLinkButton'; import { useUiFlag } from 'hooks/useUiFlag'; -import TimelineIcon from '@mui/icons-material/Timeline'; +import LinearScaleIcon from '@mui/icons-material/LinearScale'; const HeaderComponent = styled(AppBar)(({ theme }) => ({ backgroundColor: theme.palette.background.paper, @@ -269,7 +269,7 @@ const OldHeader = ({ showTimeline, setShowTimeline }: IOldHeaderProps) => { } size='large' > - + }