mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-09 00:18:00 +01:00
chore: timeline ux alignment (#8283)
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 <github@nunogois.com>
This commit is contained in:
parent
4d97f59e62
commit
729acfd318
@ -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,8 +252,10 @@ export const EventTimeline = () => {
|
||||
setTimeSpan={setTimeSpan}
|
||||
environment={environment}
|
||||
setEnvironment={setEnvironment}
|
||||
setOpen={setOpen}
|
||||
/>
|
||||
</StyledRow>
|
||||
<StyledTimelineBody>
|
||||
<StyledTimelineContainer>
|
||||
<StyledTimeline />
|
||||
<StyledStart />
|
||||
@ -260,6 +279,7 @@ export const EventTimeline = () => {
|
||||
))}
|
||||
<StyledMarkerLabel>now</StyledMarkerLabel>
|
||||
</StyledRow>
|
||||
</StyledTimelineBody>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -47,7 +47,11 @@ const getEventIcon = (type: TimelineEventType) => {
|
||||
return <ToggleOffIcon />;
|
||||
}
|
||||
if (type.startsWith('strategy-') || type.startsWith('feature-strategy-')) {
|
||||
return <ExtensionOutlinedIcon />;
|
||||
return (
|
||||
<ExtensionOutlinedIcon
|
||||
sx={{ marginTop: '-2px', marginRight: '-2px' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (type.startsWith('feature-')) {
|
||||
return <FlagOutlinedIcon />;
|
||||
|
@ -36,7 +36,7 @@ export const EventTimelineEventGroup = ({
|
||||
<StyledEvent position={position}>
|
||||
<HtmlTooltip
|
||||
title={<EventTimelineEventTooltip group={group} />}
|
||||
maxWidth={320}
|
||||
maxWidth={350}
|
||||
arrow
|
||||
>
|
||||
<Badge
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { styled } from '@mui/material';
|
||||
import { Markdown } from 'component/common/Markdown/Markdown';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||
import {
|
||||
formatDateHMS,
|
||||
@ -35,20 +36,26 @@ const StyledDate = styled('div')(({ theme }) => ({
|
||||
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<HTMLElement>) => children;
|
||||
|
||||
interface IEventTimelineEventTooltipProps {
|
||||
group: TimelineEventGroup;
|
||||
}
|
||||
@ -78,7 +87,9 @@ export const EventTimelineEventTooltip = ({
|
||||
<StyledTooltipTitle>{event.label}</StyledTooltipTitle>
|
||||
<StyledDateTime>{eventDateTime}</StyledDateTime>
|
||||
</StyledTooltipHeader>
|
||||
<Markdown>{event.summary}</Markdown>
|
||||
<Markdown components={{ strong: BoldToNormal }}>
|
||||
{event.summary}
|
||||
</Markdown>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -97,6 +108,7 @@ export const EventTimelineEventTooltip = ({
|
||||
</StyledTooltipTitle>
|
||||
<StyledDate>{eventDate}</StyledDate>
|
||||
</StyledTooltipHeader>
|
||||
<StyledTooltipItemList>
|
||||
{group.map((event) => (
|
||||
<StyledTooltipItem key={event.id}>
|
||||
<StyledEventTimelineEventCircle group={[event]} />
|
||||
@ -107,10 +119,13 @@ export const EventTimelineEventTooltip = ({
|
||||
locationSettings?.locale,
|
||||
)}
|
||||
</StyledDate>
|
||||
<Markdown>{event.summary}</Markdown>
|
||||
<Markdown components={{ strong: BoldToNormal }}>
|
||||
{event.summary}
|
||||
</Markdown>
|
||||
</div>
|
||||
</StyledTooltipItem>
|
||||
))}
|
||||
</StyledTooltipItemList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -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 (
|
||||
<>
|
||||
<StyledCol>
|
||||
<span>
|
||||
<StyledTimelineEventsCount>
|
||||
{totalEvents} event
|
||||
{totalEvents === 1 ? '' : 's'}
|
||||
</span>
|
||||
</StyledTimelineEventsCount>
|
||||
<StyledFilter
|
||||
select
|
||||
size='small'
|
||||
@ -106,6 +119,15 @@ export const EventTimelineHeader = ({
|
||||
</StyledFilter>
|
||||
)}
|
||||
/>
|
||||
<Tooltip title='Hide timeline' arrow>
|
||||
<IconButton
|
||||
aria-label='close'
|
||||
size='small'
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</StyledCol>
|
||||
</>
|
||||
);
|
||||
|
@ -118,8 +118,14 @@ export const MainLayout = forwardRef<HTMLDivElement, IMainLayoutProps>(
|
||||
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<HTMLDivElement, IMainLayoutProps>(
|
||||
>
|
||||
<MainLayoutEventTimeline
|
||||
open={eventTimeline && showTimeline}
|
||||
setOpen={setShowTimeline}
|
||||
timeSpan={timeSpan}
|
||||
setTimeSpan={setTimeSpan}
|
||||
environment={environment}
|
||||
setEnvironment={setEnvironment}
|
||||
/>
|
||||
|
||||
<StyledMainLayoutContent>
|
||||
|
@ -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 (
|
||||
<StyledEventTimelineWrapper
|
||||
<StyledEventTimelineSlider
|
||||
sx={{
|
||||
transition: isInitialLoad
|
||||
? 'none'
|
||||
@ -31,17 +49,20 @@ export const MainLayoutEventTimeline = ({
|
||||
maxHeight: open ? '105px' : '0',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={(theme) => ({
|
||||
padding: theme.spacing(2),
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
})}
|
||||
>
|
||||
<StyledEventTimelineWrapper>
|
||||
<ConditionallyRender
|
||||
condition={open}
|
||||
show={<EventTimeline />}
|
||||
show={
|
||||
<EventTimeline
|
||||
timeSpan={timeSpan}
|
||||
environment={environment}
|
||||
setTimeSpan={setTimeSpan}
|
||||
setEnvironment={setEnvironment}
|
||||
setOpen={setOpen}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</StyledEventTimelineWrapper>
|
||||
</StyledEventTimelineSlider>
|
||||
);
|
||||
};
|
||||
|
@ -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'
|
||||
>
|
||||
<TimelineIcon />
|
||||
<LinearScaleIcon />
|
||||
</StyledIconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
|
@ -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'
|
||||
>
|
||||
<TimelineIcon />
|
||||
<LinearScaleIcon />
|
||||
</StyledIconButton>
|
||||
</Tooltip>
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user