Redesign Timeline (#1772)
* Timeline redesign for desktop and mobile * Fixed nowrap on desktop --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -1,41 +1,85 @@
|
|||||||
import { Tooltip } from 'react-tooltip';
|
import { Tooltip } from 'react-tooltip';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
|
||||||
|
|
||||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||||
import { useCompleteTask } from '@/activities/tasks/hooks/useCompleteTask';
|
|
||||||
import { Activity } from '@/activities/types/Activity';
|
import { Activity } from '@/activities/types/Activity';
|
||||||
import { IconNotes } from '@/ui/display/icon';
|
import { IconCheckbox, IconNotes } from '@/ui/display/icon';
|
||||||
import { OverflowingTextWithTooltip } from '@/ui/display/tooltip/OverflowingTextWithTooltip';
|
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||||
import {
|
import {
|
||||||
beautifyExactDateTime,
|
beautifyExactDateTime,
|
||||||
beautifyPastDateRelativeToNow,
|
beautifyPastDateRelativeToNow,
|
||||||
} from '~/utils/date-utils';
|
} from '~/utils/date-utils';
|
||||||
|
|
||||||
import { TimelineActivityCardFooter } from './TimelineActivityCardFooter';
|
const StyledAvatarContainer = styled.div`
|
||||||
import { TimelineActivityTitle } from './TimelineActivityTitle';
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
height: 26px;
|
||||||
|
justify-content: center;
|
||||||
|
user-select: none;
|
||||||
|
width: 26px;
|
||||||
|
z-index: 2;
|
||||||
|
`;
|
||||||
|
|
||||||
const StyledIconContainer = styled.div`
|
const StyledIconContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 20px;
|
height: 16px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 20px;
|
text-decoration-line: underline;
|
||||||
|
width: 16px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledItemTitleContainer = styled.div`
|
const StyledActivityTitle = styled.div`
|
||||||
|
color: ${({ theme }) => theme.font.color.secondary};
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledActivityLink = styled.div`
|
||||||
|
color: ${({ theme }) => theme.font.color.secondary};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||||
|
overflow: hidden;
|
||||||
|
text-decoration-line: underline;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledItemContainer = styled.div`
|
||||||
align-content: center;
|
align-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
height: 20px;
|
|
||||||
span {
|
span {
|
||||||
color: ${({ theme }) => theme.font.color.secondary};
|
color: ${({ theme }) => theme.font.color.secondary};
|
||||||
}
|
}
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledItemTitleContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-flow: row ${() => (useIsMobile() ? 'wrap' : 'nowrap')};
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledItemAuthorText = styled.div`
|
||||||
|
display: flex;
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledItemTitle = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
overflow: hidden;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledItemTitleDate = styled.div`
|
const StyledItemTitleDate = styled.div`
|
||||||
@ -44,6 +88,7 @@ const StyledItemTitleDate = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
margin-left: auto;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledVerticalLineContainer = styled.div`
|
const StyledVerticalLineContainer = styled.div`
|
||||||
@ -52,7 +97,8 @@ const StyledVerticalLineContainer = styled.div`
|
|||||||
display: flex;
|
display: flex;
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 20px;
|
width: 26px;
|
||||||
|
z-index: 2;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledVerticalLine = styled.div`
|
const StyledVerticalLine = styled.div`
|
||||||
@ -62,35 +108,6 @@ const StyledVerticalLine = styled.div`
|
|||||||
width: 2px;
|
width: 2px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledCardContainer = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
|
||||||
max-width: 100%;
|
|
||||||
padding: 4px 0px 20px 0px;
|
|
||||||
width: ${() => (useIsMobile() ? '100%' : '400px')};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledCard = styled.div`
|
|
||||||
align-items: flex-start;
|
|
||||||
background: ${({ theme }) => theme.background.secondary};
|
|
||||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
|
||||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: ${({ theme }) => theme.spacing(3)};
|
|
||||||
width: calc(100% - ${({ theme }) => theme.spacing(4)});
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledCardContent = styled.div`
|
|
||||||
align-self: stretch;
|
|
||||||
color: ${({ theme }) => theme.font.color.secondary};
|
|
||||||
margin-top: ${({ theme }) => theme.spacing(2)};
|
|
||||||
width: calc(100% - ${({ theme }) => theme.spacing(4)});
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledTooltip = styled(Tooltip)`
|
const StyledTooltip = styled(Tooltip)`
|
||||||
background-color: ${({ theme }) => theme.background.primary};
|
background-color: ${({ theme }) => theme.background.primary};
|
||||||
|
|
||||||
@ -106,16 +123,15 @@ const StyledTooltip = styled(Tooltip)`
|
|||||||
padding: ${({ theme }) => theme.spacing(2)};
|
padding: ${({ theme }) => theme.spacing(2)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledCardDetailsContainer = styled.div`
|
const StyledTimelineItemContainer = styled.div<{ isGap?: boolean }>`
|
||||||
padding: ${({ theme }) => theme.spacing(2)};
|
|
||||||
width: calc(100% - ${({ theme }) => theme.spacing(4)});
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledTimelineItemContainer = styled.div`
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: ${({ theme }) => theme.spacing(4)};
|
gap: ${({ theme }) => theme.spacing(4)};
|
||||||
|
height: ${({ isGap, theme }) =>
|
||||||
|
isGap ? (useIsMobile() ? theme.spacing(6) : theme.spacing(3)) : 'auto'};
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type TimelineActivityProps = {
|
type TimelineActivityProps = {
|
||||||
@ -129,66 +145,80 @@ type TimelineActivityProps = {
|
|||||||
| 'type'
|
| 'type'
|
||||||
| 'comments'
|
| 'comments'
|
||||||
| 'dueAt'
|
| 'dueAt'
|
||||||
> & { author: Pick<WorkspaceMember, 'name'> } & {
|
> & { author: Pick<WorkspaceMember, 'name' | 'avatarUrl'> } & {
|
||||||
assignee?: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'> | null;
|
assignee?: Pick<WorkspaceMember, 'id' | 'name' | 'avatarUrl'> | null;
|
||||||
};
|
};
|
||||||
|
isLastActivity?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TimelineActivity = ({ activity }: TimelineActivityProps) => {
|
export const TimelineActivity = ({
|
||||||
|
activity,
|
||||||
|
isLastActivity,
|
||||||
|
}: TimelineActivityProps) => {
|
||||||
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(activity.createdAt);
|
const beautifiedCreatedAt = beautifyPastDateRelativeToNow(activity.createdAt);
|
||||||
const exactCreatedAt = beautifyExactDateTime(activity.createdAt);
|
const exactCreatedAt = beautifyExactDateTime(activity.createdAt);
|
||||||
const body = JSON.parse(
|
|
||||||
isNonEmptyString(activity.body) ? activity.body : '{}',
|
|
||||||
)[0]?.content[0]?.text;
|
|
||||||
|
|
||||||
const openActivityRightDrawer = useOpenActivityRightDrawer();
|
const openActivityRightDrawer = useOpenActivityRightDrawer();
|
||||||
const { completeTask } = useCompleteTask(activity);
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledTimelineItemContainer>
|
<StyledTimelineItemContainer>
|
||||||
<StyledIconContainer>
|
<StyledAvatarContainer>
|
||||||
<IconNotes />
|
<Avatar
|
||||||
</StyledIconContainer>
|
avatarUrl={activity.author.avatarUrl}
|
||||||
<StyledItemTitleContainer>
|
placeholder={activity.author.name.firstName ?? ''}
|
||||||
<span>
|
size="sm"
|
||||||
{activity.author.name.firstName +
|
type="rounded"
|
||||||
' ' +
|
/>
|
||||||
activity.author.name.lastName}
|
</StyledAvatarContainer>
|
||||||
</span>
|
<StyledItemContainer>
|
||||||
created a {activity.type.toLowerCase()}
|
<StyledItemTitleContainer>
|
||||||
</StyledItemTitleContainer>
|
<StyledItemAuthorText>
|
||||||
<StyledItemTitleDate id={`id-${activity.id}`}>
|
<span>
|
||||||
{beautifiedCreatedAt}
|
{activity.author.name.firstName} {activity.author.name.lastName}
|
||||||
</StyledItemTitleDate>
|
</span>
|
||||||
<StyledTooltip
|
created a {activity.type.toLowerCase()}
|
||||||
anchorSelect={`#id-${activity.id}`}
|
</StyledItemAuthorText>
|
||||||
content={exactCreatedAt}
|
<StyledItemTitle>
|
||||||
clickable
|
<StyledIconContainer>
|
||||||
noArrow
|
{activity.type === 'Note' && (
|
||||||
/>
|
<IconNotes size={theme.icon.size.sm} />
|
||||||
</StyledTimelineItemContainer>
|
)}
|
||||||
<StyledTimelineItemContainer>
|
{activity.type === 'Task' && (
|
||||||
<StyledVerticalLineContainer>
|
<IconCheckbox size={theme.icon.size.sm} />
|
||||||
<StyledVerticalLine></StyledVerticalLine>
|
)}
|
||||||
</StyledVerticalLineContainer>
|
</StyledIconContainer>
|
||||||
<StyledCardContainer>
|
{(activity.type === 'Note' || activity.type === 'Task') && (
|
||||||
<StyledCard onClick={() => openActivityRightDrawer(activity.id)}>
|
<StyledActivityTitle
|
||||||
<StyledCardDetailsContainer>
|
onClick={() => openActivityRightDrawer(activity.id)}
|
||||||
<TimelineActivityTitle
|
>
|
||||||
title={activity.title ?? ''}
|
“
|
||||||
completed={!!activity.completedAt}
|
<StyledActivityLink title={activity.title ?? '(No Title)'}>
|
||||||
type={activity.type}
|
{activity.title ?? '(No Title)'}
|
||||||
onCompletionChange={completeTask}
|
</StyledActivityLink>
|
||||||
/>
|
“
|
||||||
<StyledCardContent>
|
</StyledActivityTitle>
|
||||||
{body && <OverflowingTextWithTooltip text={body ? body : ''} />}
|
)}
|
||||||
</StyledCardContent>
|
</StyledItemTitle>
|
||||||
</StyledCardDetailsContainer>
|
</StyledItemTitleContainer>
|
||||||
<TimelineActivityCardFooter activity={activity} />
|
<StyledItemTitleDate id={`id-${activity.id}`}>
|
||||||
</StyledCard>
|
{beautifiedCreatedAt}
|
||||||
</StyledCardContainer>
|
</StyledItemTitleDate>
|
||||||
|
<StyledTooltip
|
||||||
|
anchorSelect={`#id-${activity.id}`}
|
||||||
|
content={exactCreatedAt}
|
||||||
|
clickable
|
||||||
|
noArrow
|
||||||
|
/>
|
||||||
|
</StyledItemContainer>
|
||||||
</StyledTimelineItemContainer>
|
</StyledTimelineItemContainer>
|
||||||
|
{!isLastActivity && (
|
||||||
|
<StyledTimelineItemContainer isGap>
|
||||||
|
<StyledVerticalLineContainer>
|
||||||
|
<StyledVerticalLine></StyledVerticalLine>
|
||||||
|
</StyledVerticalLineContainer>
|
||||||
|
</StyledTimelineItemContainer>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import React from 'react';
|
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { ActivityForDrawer } from '@/activities/types/ActivityForDrawer';
|
import { ActivityForDrawer } from '@/activities/types/ActivityForDrawer';
|
||||||
import { IconCircleDot } from '@/ui/display/icon';
|
|
||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
|
|
||||||
import { TimelineActivity } from './TimelineActivity';
|
import { groupActivitiesByMonth } from '../utils/groupActivitiesByMonth';
|
||||||
|
|
||||||
|
import { TimelineActivityGroup } from './TimelingeActivityGroup';
|
||||||
|
|
||||||
const StyledTimelineContainer = styled.div`
|
const StyledTimelineContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -18,15 +17,8 @@ const StyledTimelineContainer = styled.div`
|
|||||||
gap: ${({ theme }) => theme.spacing(1)};
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
|
||||||
padding: ${({ theme }) => theme.spacing(3)} ${({ theme }) => theme.spacing(4)};
|
padding: ${({ theme }) => theme.spacing(4)};
|
||||||
`;
|
width: calc(100% - ${({ theme }) => theme.spacing(8)});
|
||||||
|
|
||||||
const StyledStartIcon = styled.div`
|
|
||||||
align-self: flex-start;
|
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
|
||||||
display: flex;
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledScrollWrapper = styled(ScrollWrapper)``;
|
const StyledScrollWrapper = styled(ScrollWrapper)``;
|
||||||
@ -38,16 +30,26 @@ export type TimelineItemsContainerProps = {
|
|||||||
export const TimelineItemsContainer = ({
|
export const TimelineItemsContainer = ({
|
||||||
activities,
|
activities,
|
||||||
}: TimelineItemsContainerProps) => {
|
}: TimelineItemsContainerProps) => {
|
||||||
const theme = useTheme();
|
const groupedActivities = groupActivitiesByMonth(activities);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledScrollWrapper>
|
<StyledScrollWrapper>
|
||||||
<StyledTimelineContainer>
|
<StyledTimelineContainer>
|
||||||
{activities.map((activity) => (
|
{groupedActivities.map((group, index) => (
|
||||||
<TimelineActivity key={activity.id} activity={activity} />
|
<TimelineActivityGroup
|
||||||
|
key={group.year.toString() + group.month}
|
||||||
|
group={group}
|
||||||
|
month={new Date(group.items[0].createdAt).toLocaleString(
|
||||||
|
'default',
|
||||||
|
{ month: 'long' },
|
||||||
|
)}
|
||||||
|
year={
|
||||||
|
index === 0 || group.year !== groupedActivities[index - 1].year
|
||||||
|
? group.year
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
<StyledStartIcon>
|
|
||||||
<IconCircleDot size={theme.icon.size.lg} />
|
|
||||||
</StyledStartIcon>
|
|
||||||
</StyledTimelineContainer>
|
</StyledTimelineContainer>
|
||||||
</StyledScrollWrapper>
|
</StyledScrollWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,78 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import { ActivityGroup } from '../utils/groupActivitiesByMonth';
|
||||||
|
|
||||||
|
import { TimelineActivity } from './TimelineActivity';
|
||||||
|
|
||||||
|
type TimelineActivityGroupProps = {
|
||||||
|
group: ActivityGroup;
|
||||||
|
month: string;
|
||||||
|
year?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledActivityGroup = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
gap: ${({ theme }) => theme.spacing(4)};
|
||||||
|
margin-bottom: ${({ theme }) => theme.spacing(4)};
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledActivityGroupContainer = styled.div`
|
||||||
|
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||||
|
padding-top: ${({ theme }) => theme.spacing(2)};
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledActivityGroupBar = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
background: ${({ theme }) => theme.background.secondary};
|
||||||
|
border: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.xl};
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 24px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledMonthSeperator = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
align-self: stretch;
|
||||||
|
color: ${({ theme }) => theme.font.color.light};
|
||||||
|
display: flex;
|
||||||
|
gap: ${({ theme }) => theme.spacing(4)};
|
||||||
|
`;
|
||||||
|
const StyledMonthSeperatorLine = styled.div`
|
||||||
|
background: ${({ theme }) => theme.border.color.light};
|
||||||
|
border-radius: 50px;
|
||||||
|
flex: 1 0 0;
|
||||||
|
height: 1px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const TimelineActivityGroup = ({
|
||||||
|
group,
|
||||||
|
month,
|
||||||
|
year,
|
||||||
|
}: TimelineActivityGroupProps) => {
|
||||||
|
return (
|
||||||
|
<StyledActivityGroup>
|
||||||
|
<StyledMonthSeperator>
|
||||||
|
{month} {year}
|
||||||
|
<StyledMonthSeperatorLine />
|
||||||
|
</StyledMonthSeperator>
|
||||||
|
<StyledActivityGroupContainer>
|
||||||
|
<StyledActivityGroupBar />
|
||||||
|
{group.items.map((activity, index) => (
|
||||||
|
<TimelineActivity
|
||||||
|
key={activity.id}
|
||||||
|
activity={activity}
|
||||||
|
isLastActivity={index === group.items.length - 1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</StyledActivityGroupContainer>
|
||||||
|
</StyledActivityGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { ActivityForDrawer } from '@/activities/types/ActivityForDrawer';
|
||||||
|
|
||||||
|
export interface ActivityGroup {
|
||||||
|
month: number;
|
||||||
|
year: number;
|
||||||
|
items: ActivityForDrawer[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const groupActivitiesByMonth = (activities: ActivityForDrawer[]) => {
|
||||||
|
const acitivityGroups: ActivityGroup[] = [];
|
||||||
|
for (const activity of activities) {
|
||||||
|
const d = new Date(activity.createdAt);
|
||||||
|
const month = d.getMonth();
|
||||||
|
const year = d.getFullYear();
|
||||||
|
|
||||||
|
const matchingGroup = acitivityGroups.find(
|
||||||
|
(x) => x.year === year && x.month === month,
|
||||||
|
);
|
||||||
|
if (matchingGroup) {
|
||||||
|
matchingGroup.items.push(activity);
|
||||||
|
} else {
|
||||||
|
acitivityGroups.push({
|
||||||
|
year,
|
||||||
|
month,
|
||||||
|
items: [activity],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return acitivityGroups.sort((a, b) => b.year - a.year || b.month - a.month);
|
||||||
|
};
|
||||||
@ -28,6 +28,7 @@ const StyledContainer = styled.div`
|
|||||||
gap: ${({ theme }) => theme.spacing(2)};
|
gap: ${({ theme }) => theme.spacing(2)};
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||||
|
user-select: none;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TabList = ({ tabs, context }: TabListProps) => {
|
export const TabList = ({ tabs, context }: TabListProps) => {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ const common = {
|
|||||||
xs: '2px',
|
xs: '2px',
|
||||||
sm: '4px',
|
sm: '4px',
|
||||||
md: '8px',
|
md: '8px',
|
||||||
|
xl: '20px',
|
||||||
rounded: '100%',
|
rounded: '100%',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user