Skip to content

Commit

Permalink
feat(ProjectListItemCollapsible): add goals button
Browse files Browse the repository at this point in the history
  • Loading branch information
asabotovich committed Jun 21, 2023
1 parent c46a735 commit 0630b3f
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 85 deletions.
71 changes: 42 additions & 29 deletions src/components/CollapsableItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FC, ReactNode } from 'react';
import styled, { css } from 'styled-components';
import { gray7, radiusM } from '@taskany/colors';
import { nullable } from '@taskany/bricks';

export const collapseOffset = 20;

Expand Down Expand Up @@ -34,11 +35,11 @@ const Dot = styled.div`

const ParentDot = styled(Dot)``;

const CollapseHeader = styled.div`
const CollapsableHeader = styled.div`
border-radius: ${radiusM};
`;

export const TableRowCollapseContent = styled.div`
export const CollapsableItem = styled.div`
position: relative;
&:before {
Expand All @@ -48,12 +49,29 @@ export const TableRowCollapseContent = styled.div`
}
`;

const TableRowCollapseContainer = styled.div<{ collapsed: boolean; deep: number; showLine: boolean }>`
const CollapsableContainer = styled.div<{ collapsed: boolean; deep: number; showLine: boolean }>`
position: relative;
border-radius: ${radiusM};
${({ collapsed, deep, showLine }) =>
> ${CollapsableItem}:before {
display: none;
}
&:last-child:before {
display: none;
}
&:last-child > ${CollapsableHeader}:after {
content: '';
${line}
bottom: 50%;
${({ deep }) => deep === 0 && 'display: none;'}
}
${({ collapsed, deep }) =>
!collapsed &&
css`
padding-left: ${collapseOffset}px;
Expand All @@ -66,7 +84,7 @@ const TableRowCollapseContainer = styled.div<{ collapsed: boolean; deep: number;
margin-left: -${collapseOffset}px;
}
& > & > ${CollapseHeader}, & > ${CollapseHeader} {
& > & > ${CollapsableHeader}, & > ${CollapsableHeader} {
padding-left: ${collapseOffset}px;
margin-left: -${collapseOffset}px;
position: relative;
Expand All @@ -80,7 +98,7 @@ const TableRowCollapseContainer = styled.div<{ collapsed: boolean; deep: number;
/** add parent dot if not first lvl */
& > ${CollapseHeader} > ${ParentDot} {
& > ${CollapsableHeader} > ${ParentDot} {
${deep > 0 &&
css`
display: block;
Expand All @@ -91,18 +109,14 @@ const TableRowCollapseContainer = styled.div<{ collapsed: boolean; deep: number;
/** first item vertical line */
& > &:before,
& > ${CollapseHeader}:before {
& > ${CollapsableHeader}:before {
content: '';
${line}
}
& > ${TableRowCollapseContent}:before, & > ${CollapseHeader}:before {
${!showLine && 'display: none;'}
}
/** middle item vertical line */
/** first item vertical line */
& > ${CollapseHeader}:before {
& > ${CollapsableHeader}:before {
top: 50%;
}
Expand All @@ -116,37 +130,36 @@ const TableRowCollapseContainer = styled.div<{ collapsed: boolean; deep: number;
margin-left: -${collapseOffset}px;
}
&:last-of-type:before {
display: none;
}
&:last-of-type > ${CollapseHeader}:after {
content: '';
${line}
&:last-child > ${CollapsableHeader}:after {
margin-left: -${collapseOffset}px;
bottom: 50%;
}
${deep === 0 && 'display: none;'}
> ${CollapsableItem}:before {
display: block;
}
`}
`;

export const TableRowCollapse: FC<{
export const Collapsable: FC<{
children?: ReactNode;
onClick?: () => void;
header: ReactNode;
content: ReactNode;
deep?: number;
collapsed: boolean;
showLine?: boolean;
}> = ({ onClick, children, header, collapsed, deep = 0, showLine = true }) => {
}> = ({ onClick, children, header, collapsed, deep = 0, showLine = true, content }) => {
return (
<TableRowCollapseContainer collapsed={collapsed} deep={deep} showLine={showLine}>
<CollapseHeader onClick={onClick}>
<CollapsableContainer collapsed={collapsed} deep={deep} showLine={showLine}>
<CollapsableHeader onClick={onClick}>
<ParentDot />
<Dot />
{header}
</CollapseHeader>
{!collapsed ? children : null}
</TableRowCollapseContainer>
</CollapsableHeader>
{nullable(children, (ch) => (
<CollapsableItem>{ch}</CollapsableItem>
))}
{!collapsed ? content : null}
</CollapsableContainer>
);
};
130 changes: 80 additions & 50 deletions src/components/ProjectListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import React, { FC, MouseEventHandler, ReactNode, useCallback, useState } from 'react';
import React, { FC, MouseEvent, MouseEventHandler, ReactNode, useCallback, useMemo, useState } from 'react';
import styled from 'styled-components';
import Link from 'next/link';
import { EyeIcon, StarFilledIcon, Text, nullable } from '@taskany/bricks';
import { gray6, radiusM } from '@taskany/colors';
import { Badge, Button, EyeIcon, StarFilledIcon, Text, nullable } from '@taskany/bricks';
import { gapS, gray3, gray4, gray6, radiusM } from '@taskany/colors';

import { ActivityByIdReturnType, GoalByIdReturnType, ProjectByIdReturnType } from '../../trpc/inferredTypes';
import { routes } from '../hooks/router';
import { trpc } from '../utils/trpcClient';
import { refreshInterval } from '../utils/config';
import { QueryState } from '../hooks/useUrlFilterParams';

import { Table, TableCell, TableRow } from './Table';
import { UserGroup } from './UserGroup';
import { GoalListItem, GoalsListContainer } from './GoalListItem';
import { TableRowCollapse, TableRowCollapseContent, collapseOffset } from './CollapsableItem';
import { Collapsable, CollapsableItem, collapseOffset } from './CollapsableItem';

const ProjectTableRowCollapseContent = styled(TableRowCollapseContent)`
// background: ${gray6};
// border-radius: ${radiusM};
// ${TableRow}:hover ${TableCell} {
// background: ${gray6};
// }
const ShowGoalsButton = styled(Button)`
margin-left: ${gapS};
cursor: pointer;
`;

export const ProjectListContainer: FC<{ children: ReactNode; offset?: number }> = ({ children, offset = 0 }) => (
Expand All @@ -37,16 +36,17 @@ interface ProjectListItemBaseProps {

interface ProjectListItemProps extends ProjectListItemBaseProps {
href?: string;
children?: ReactNode;
}

interface ProjectListItemCollapsibleProps extends ProjectListItemBaseProps {
id: string;
href: string;
fetchGoals: (id: string) => Promise<NonNullable<GoalByIdReturnType>[] | null>;
fetchProjects: (id: string) => Promise<NonNullable<ProjectByIdReturnType>[] | null>;
onTagClick?: React.ComponentProps<typeof GoalListItem>['onTagClick'];
onClickProvider?: (g: NonNullable<GoalByIdReturnType>) => MouseEventHandler<HTMLAnchorElement>;
selectedResolver?: (id: string) => boolean;
queryState?: QueryState;
deep?: number;
}

Expand All @@ -57,13 +57,15 @@ export const ProjectListItem: React.FC<ProjectListItemProps> = ({
participants,
starred,
watching,
children,
}) => {
const row = (
<TableRow>
<TableCell>
<Text size="l" weight="bold">
{title}
</Text>
{children}
</TableCell>

<TableCell>
Expand Down Expand Up @@ -104,39 +106,62 @@ export const ProjectListItemCollapsible: React.FC<ProjectListItemCollapsibleProp
watching,
owner,
participants,
fetchGoals,
fetchProjects,
onTagClick,
onClickProvider,
selectedResolver,
queryState,
deep = 0,
}) => {
const [collapsed, setIsCollapsed] = useState(true);
const [goals, setGoals] = useState<undefined | null | NonNullable<GoalByIdReturnType>[]>(undefined);
const [collapsedGoals, setIsCollapsedGoals] = useState(true);
const [projects, setProjects] = useState<undefined | null | NonNullable<ProjectByIdReturnType>[]>(undefined);

const { data: projectDeepInfo } = trpc.project.getDeepInfo.useQuery(
{
id,
...queryState,
},
{
keepPreviousData: true,
staleTime: refreshInterval,
},
);

const goals = useMemo(
() => (projectDeepInfo ? projectDeepInfo.goals.filter((g) => g.projectId === id) : null),
[projectDeepInfo, id],
);

const offset = collapseOffset * (collapsed ? deep - 1 : deep);

const onClick = useCallback(() => {
if (typeof goals === 'undefined' || typeof projects === 'undefined') {
Promise.all([fetchGoals(id), fetchProjects(id)]).then(([goals, projects]) => {
setGoals(goals);
if (typeof projects === 'undefined') {
fetchProjects(id).then((projects) => {
setProjects(projects);

if (projects?.length || goals?.length) {
if (projects?.length) {
setIsCollapsed(false);
}
});
} else if (projects?.length || goals?.length) {
} else if (projects?.length) {
setIsCollapsed((value) => !value);
}
}, [fetchGoals, goals, projects]);
}, [fetchProjects, projects]);

const onHeaderButtonClick = useCallback(
(e: MouseEvent) => {
e.stopPropagation();

setIsCollapsedGoals((value) => !value);
},
[projects, collapsedGoals, onClick, collapsed],
);

return (
<TableRowCollapse
<Collapsable
collapsed={collapsed}
onClick={onClick}
showLine={Boolean(projects?.length)}
onClick={projects !== null ? onClick : undefined}
header={
<ProjectListContainer offset={offset}>
<ProjectListItem
Expand All @@ -145,13 +170,40 @@ export const ProjectListItemCollapsible: React.FC<ProjectListItemCollapsibleProp
participants={participants}
starred={starred}
watching={watching}
/>
>
<ShowGoalsButton
onClick={onHeaderButtonClick}
disabled={!goals?.length}
text={'Goals'}
iconRight={<Badge size="s">{goals?.length ?? 0}</Badge>}
/>
</ProjectListItem>
</ProjectListContainer>
}
content={nullable(projects, (projects) =>
projects.map((p, i) => (
<ProjectListItemCollapsible
id={p.id}
key={`${p.id}_${i}`}
href={routes.project(p.id)}
title={p.title}
owner={p?.activity}
participants={p?.participants}
starred={p?._isStarred}
watching={p?._isWatching}
deep={deep + 1}
fetchProjects={fetchProjects}
onTagClick={onTagClick}
onClickProvider={onClickProvider}
selectedResolver={selectedResolver}
queryState={queryState}
/>
)),
)}
deep={deep}
>
{nullable(goals, (goals) => (
<ProjectTableRowCollapseContent>
{!collapsedGoals &&
nullable(goals, (goals) => (
<GoalsListContainer offset={offset}>
{goals.map((g) => (
<GoalListItem
Expand All @@ -178,30 +230,8 @@ export const ProjectListItemCollapsible: React.FC<ProjectListItemCollapsibleProp
/>
))}
</GoalsListContainer>
</ProjectTableRowCollapseContent>
))}

{nullable(projects, (projects) =>
projects.map((p, i) => (
<ProjectListItemCollapsible
id={p.id}
key={`${p.id}_${i}`}
href={routes.project(p.id)}
title={p.title}
owner={p?.activity}
participants={p?.participants}
starred={p?._isStarred}
watching={p?._isWatching}
deep={deep + 1}
fetchGoals={fetchGoals}
fetchProjects={fetchProjects}
onTagClick={onTagClick}
onClickProvider={onClickProvider}
selectedResolver={selectedResolver}
/>
)),
)}
</TableRowCollapse>
))}
</Collapsable>
);
};

Expand Down
Loading

0 comments on commit 0630b3f

Please sign in to comment.