-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: implement direct children list view (#341)
* feat(listView): add tab * feat(listView): extremely basic list view * feat(listview): some basic styling * feat(listview): bullet icons and connectorlines * feat(listview): fix alignment and wrapping * feat(listview): coloring and styles * fix(listview): unstarted milestone bullets * feat(listview): header alignment & linkification * feat(listview): finishing touches on linkability and description rendering * feat(listview): parse description from github body * chore(listview): code cleanup * chore(listview): date sort function cleanup * chore(listview): remove circle progress label * fix(listView): 🗑️ Fixing build * chore: address PR comments * chore: fix lint --------- Co-authored-by: Nishant Arora <[email protected]>
- Loading branch information
Showing
21 changed files
with
485 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
.bulletConnector { | ||
/** | ||
* This top value must be set to the size of the .bulletIcon_Wrapper class in order to not appear underneath | ||
* transparent colors | ||
*/ | ||
top: 26px; | ||
width: 2px; | ||
bottom: 0; | ||
/** Spotlight Blue */ | ||
background: var(--chakra-colors-spotLightBlue); | ||
position: absolute; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import React from 'react'; | ||
import styles from './BulletConnector.module.css'; | ||
|
||
/** | ||
* The vertical line connecting each bulletIcon across all rows | ||
* @returns | ||
*/ | ||
export default function BulletConnector ({ isLast, children }: {isLast: boolean, children?: React.ReactNode}) { | ||
if (isLast) { | ||
return null | ||
} | ||
return ( | ||
<span className={styles.bulletConnector}>{children}</span> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
.bulletIcon_Wrapper { | ||
background: white; | ||
height: 32px; | ||
width: 32px; | ||
border-radius: 100%; | ||
flex-shrink: 0; | ||
font-weight: 600; | ||
display: inline-flex; | ||
align-items: center; | ||
justify-content: center; | ||
position: relative; | ||
z-index: 1; | ||
} | ||
|
||
.bulletIcon_Wrapper.notStarted { | ||
border: 6px solid var(--chakra-colors-inactiveAccent); | ||
} | ||
|
||
.bulletIcon_Wrapper.inProgress { | ||
border: 6px solid var(--chakra-colors-progressGreenAccent); | ||
} | ||
|
||
.bulletIcon_Wrapper.completed { | ||
border: 6px solid var(--chakra-colors-progressGreenAccent); | ||
} | ||
|
||
.bulletIcon { | ||
height: 24px; | ||
width: 24px; | ||
border-radius: 100%; | ||
flex-shrink: 0; | ||
font-weight: 600; | ||
display: inline-flex; | ||
align-items: center; | ||
justify-content: center; | ||
position: relative; | ||
z-index: 1; | ||
} | ||
|
||
.bulletIcon.notStarted { | ||
border: 1px solid var(--chakra-colors-inactive); | ||
} | ||
|
||
.bulletIcon.inProgress { | ||
z-index: 1; /** Just to get rid of empty ruleset warning */ | ||
} | ||
|
||
.bulletIcon.completed { | ||
background: var(--chakra-colors-progressGreen); | ||
border: 2px solid var(--chakra-colors-progressGreen); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { CircularProgress } from '@chakra-ui/react' | ||
import React from 'react' | ||
import styles from './BulletIcon.module.css' | ||
|
||
export default function BulletIcon ({ completion_rate }: {completion_rate: number}) { | ||
const wrapperClassNames = [styles.bulletIcon_Wrapper] | ||
const iconClassNames = [styles.bulletIcon] | ||
let color = 'inactive' | ||
if (completion_rate === 100) { | ||
color = 'progressGreenAccent' | ||
iconClassNames.push(styles.completed) | ||
wrapperClassNames.push(styles.completed) | ||
} else if (completion_rate > 0) { | ||
iconClassNames.push(styles.inProgress) | ||
wrapperClassNames.push(styles.inProgress) | ||
color = 'progressGreen' | ||
} else { | ||
iconClassNames.push(styles.notStarted) | ||
wrapperClassNames.push(styles.notStarted) | ||
} | ||
|
||
return ( | ||
<span className={wrapperClassNames.join(' ')}> | ||
<span className={iconClassNames.join(' ')}> | ||
<CircularProgress value={completion_rate} size='30px' trackColor="transparent" color={color} /> | ||
</span> | ||
</span> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.roadmapList { | ||
padding-top: 1vh; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { Grid, GridItem, Center, Link, HStack, Text, Skeleton } from '@chakra-ui/react'; | ||
import { LinkIcon } from '@chakra-ui/icons'; | ||
import NextLink from 'next/link'; | ||
import { useRouter } from 'next/router'; | ||
import React from 'react'; | ||
|
||
import SvgGitHubLogo from '../icons/svgr/SvgGitHubLogo'; | ||
import BulletConnector from './BulletConnector'; | ||
import BulletIcon from './BulletIcon'; | ||
import { paramsFromUrl } from '../../lib/paramsFromUrl'; | ||
import { dayjs } from '../../lib/client/dayjs'; | ||
import { getLinkForRoadmapChild } from '../../lib/client/getLinkForRoadmapChild'; | ||
import { ViewMode } from '../../lib/enums'; | ||
import { useGlobalLoadingState } from '../../hooks/useGlobalLoadingState'; | ||
import { ListIssueViewModel } from './types'; | ||
|
||
interface RoadmapListItemDefaultProps { | ||
issue: ListIssueViewModel | ||
index: number | ||
issues: ListIssueViewModel[] | ||
} | ||
|
||
function TitleText ({ hasChildren, issue }: Pick<RoadmapListItemDefaultProps, 'issue'> & {hasChildren: boolean}) { | ||
return ( | ||
<Text fontWeight="semibold" color="linkBlue" fontSize={"xl"} lineHeight="32px"> | ||
{hasChildren ? <LinkIcon lineHeight="32px" boxSize="10px" /> : null} {issue.title} | ||
</Text> | ||
) | ||
} | ||
|
||
function TitleTextMaybeLink ({ issue, hasChildren, index, childLink }: Pick<RoadmapListItemDefaultProps, 'issue'> & {hasChildren: boolean, index: number, childLink: string}) { | ||
const titleText = <TitleText hasChildren={hasChildren} issue={issue} /> | ||
if (!hasChildren) { | ||
return titleText | ||
} | ||
return ( | ||
<NextLink key={`roadmapItem-${index}`} href={childLink} passHref> | ||
<Link cursor="pointer" _hover={{ textDecoration: 'none' }}> | ||
{titleText} | ||
</Link> | ||
</NextLink> | ||
) | ||
} | ||
|
||
export default function RoadmapListItemDefault ({ issue, index, issues }: RoadmapListItemDefaultProps) { | ||
const { owner, repo, issue_number } = paramsFromUrl(issue.html_url) | ||
const childLink = getLinkForRoadmapChild({ issueData: issue, query: useRouter().query, viewMode: ViewMode.List }) | ||
const globalLoadingState = useGlobalLoadingState(); | ||
const hasChildren = childLink !== '#' | ||
const issueDueDate = issue.due_date ? dayjs(issue.due_date).format('MMM D, YYYY') : 'unknown' | ||
|
||
return ( | ||
<Grid width="100%" key={issue.html_url} | ||
templateAreas={`"date icon title" | ||
"empty line description"`} | ||
gridTemplateRows={'1fr auto'} | ||
gridTemplateColumns={'1fr 1fr 8fr'} | ||
columnGap={0} | ||
rowGap={0}> | ||
<GridItem area="date" lineHeight="32px"> | ||
<Center> | ||
<Skeleton isLoaded={!globalLoadingState.get()}> | ||
<Text size="l" color="spotLightBlue" lineHeight="32px">{issueDueDate}</Text> | ||
</Skeleton> | ||
</Center> | ||
</GridItem> | ||
<GridItem area="icon" lineHeight="32px"> | ||
<Center> | ||
<Skeleton isLoaded={!globalLoadingState.get()}> | ||
<BulletIcon completion_rate={issue.completion_rate} /> | ||
</Skeleton> | ||
</Center> | ||
</GridItem> | ||
<GridItem area="title"> | ||
<HStack gap={0} alignItems="flex-start"> | ||
<Link href={issue.html_url} lineHeight="32px" isExternal> | ||
<Skeleton isLoaded={!globalLoadingState.get()}> | ||
<HStack gap={0} alignItems="center" wrap={"nowrap"}> | ||
<SvgGitHubLogo color="text" style={{ display:'inline', color: '#313239' }} fill="#313239" /> | ||
<Text color="text" style={{ whiteSpace: 'nowrap' }} fontSize="large">{owner}/{repo}#{issue_number}</Text> | ||
</HStack> | ||
</Skeleton> | ||
</Link> | ||
<Skeleton isLoaded={!globalLoadingState.get()}> | ||
<TitleTextMaybeLink hasChildren={hasChildren} issue={issue} index={index} childLink={childLink} /> | ||
</Skeleton> | ||
</HStack> | ||
</GridItem> | ||
<GridItem gridRow="1/-1" area="line" pos="relative"> | ||
<Center height="100%"> | ||
<BulletConnector isLast={index === issues.length - 1} /> | ||
</Center> | ||
</GridItem> | ||
<GridItem area="description" pb="2rem"> | ||
<Skeleton isLoaded={!globalLoadingState.get()}> | ||
<Text fontSize="medium">{issue.description}</Text> | ||
</Skeleton> | ||
</GridItem> | ||
</Grid> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import React, { useState } from 'react'; | ||
import { Radio, RadioGroup, Stack, Text } from '@chakra-ui/react'; | ||
|
||
import { IssueDataViewInput } from '../../lib/types'; | ||
import RoadmapListItemDefault from './RoadmapListItemDefault'; | ||
import styles from './RoadmapList.module.css'; | ||
import { getTimeFromDateString } from '../../lib/helpers'; | ||
|
||
interface RoadmapListProps extends IssueDataViewInput { | ||
maybe?: unknown | ||
} | ||
|
||
/** | ||
* Sorts milestones by due date, in ascending order (2022-01-01 before 2023-01-01) with invalid dates at the end. | ||
* @param {ListIssueViewModel | IssueData} a | ||
* @param {ListIssueViewModel | IssueData} b | ||
* @returns | ||
*/ | ||
function sortMilestones (a, b) { | ||
// Get either a unix timestamp or Number.MAX_VALUE | ||
const aTime = getTimeFromDateString(a.due_date, Number.MAX_VALUE) | ||
const bTime = getTimeFromDateString(b.due_date, Number.MAX_VALUE) | ||
|
||
// if a is valid and b is not, MAX_VALUE - validTime will be positive. | ||
// if b is valid and a is not, MAX_VALUE - validTime will be negative. | ||
// if both are valid, or invalid, result is 0. | ||
return aTime - bTime | ||
} | ||
|
||
export default function RoadmapList({ issueDataState }: RoadmapListProps): JSX.Element { | ||
const [groupBy, setGroupBy] = useState('directChildren') | ||
|
||
const [isDevModeGroupBy, _setIsDevModeGroupBy] = useState(false); | ||
const [isDevModeDuplicateDates, _setIsDevModeDuplicateDates] = useState(false); | ||
const [dupeDateToggleValue, setDupeDateToggleValue] = useState('show'); | ||
const flattenedIssues = issueDataState.children.flatMap((issueData) => issueData.get({ noproxy: true })) | ||
const sortedIssuesWithDueDates = flattenedIssues.sort(sortMilestones) | ||
|
||
let groupByToggle: JSX.Element | null = null | ||
if (isDevModeGroupBy) { | ||
groupByToggle = ( | ||
<RadioGroup onChange={setGroupBy} value={groupBy}> | ||
<Stack direction='row'> | ||
<Text>Group By:</Text> | ||
<Radio value='directChildren'>Current Children</Radio> | ||
<Radio value='parent'>Parent/Child</Radio> | ||
<Radio value='month'>Month</Radio> | ||
<Radio value='none'>None (flat)</Radio> | ||
</Stack> | ||
</RadioGroup> | ||
) | ||
} | ||
|
||
let dupeDateToggle: JSX.Element | null = null | ||
if (isDevModeDuplicateDates) { | ||
dupeDateToggle = ( | ||
<RadioGroup onChange={setDupeDateToggleValue} value={dupeDateToggleValue}> | ||
<Stack direction='row'> | ||
<Text>Duplicate Dates:</Text> | ||
<Radio value='hide'>Hide</Radio> | ||
<Radio value='show'>Show</Radio> | ||
</Stack> | ||
</RadioGroup> | ||
) | ||
} | ||
|
||
return ( | ||
<> | ||
{groupByToggle} | ||
{dupeDateToggle} | ||
<div className={styles.roadmapList}> | ||
{sortedIssuesWithDueDates.map((issue, index) => ( | ||
<RoadmapListItemDefault key={index} issue={issue} index={index} issues={sortedIssuesWithDueDates} /> | ||
))} | ||
</div> | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { ImmutableObject } from '@hookstate/core'; | ||
import { IssueData } from '../../lib/types'; | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-empty-interface | ||
export interface ListIssueViewModel extends Pick<ImmutableObject<IssueData>, 'html_url' | 'title' | 'completion_rate' | 'due_date' | 'description' | 'children' | 'parent'> { | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,15 @@ | ||
import * as React from 'react'; | ||
|
||
const SvgGitHubLogo = (props) => (<div> | ||
|
||
const SvgGitHubLogo = (props) => ( | ||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32.58 31.77' height="18px" width="18px" {...props}> | ||
<g data-name='Layer 2'> | ||
<path | ||
fill='#A3A3A3' | ||
fill={props.fill ?? '#A3A3A3'} | ||
fillRule='evenodd' | ||
d='M16.29 0a16.29 16.29 0 0 0-5.15 31.75c.82.15 1.11-.36 1.11-.79v-2.77C7.7 29.18 6.74 26 6.74 26a4.36 4.36 0 0 0-1.81-2.39c-1.47-1 .12-1 .12-1a3.43 3.43 0 0 1 2.49 1.68 3.48 3.48 0 0 0 4.74 1.36 3.46 3.46 0 0 1 1-2.18c-3.62-.41-7.42-1.81-7.42-8a6.3 6.3 0 0 1 1.67-4.37 5.94 5.94 0 0 1 .16-4.31s1.37-.44 4.48 1.67a15.41 15.41 0 0 1 8.16 0c3.11-2.11 4.47-1.67 4.47-1.67a5.91 5.91 0 0 1 .2 4.28 6.3 6.3 0 0 1 1.67 4.37c0 6.26-3.81 7.63-7.44 8a3.85 3.85 0 0 1 1.11 3v4.47c0 .53.29.94 1.12.78A16.29 16.29 0 0 0 16.29 0Z' | ||
data-name='Layer 1' | ||
/> | ||
</g> | ||
</svg> | ||
</div> | ||
); | ||
export default SvgGitHubLogo; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import * as React from 'react'; | ||
|
||
const SvgDetailViewIcon = () => ( | ||
/** | ||
* From https://www.iconfinder.com/icons/326721/list_view_icon#svg | ||
*/ | ||
<svg width="19" height="14" viewBox="0 0 19 14" xmlns="http://www.w3.org/2000/svg" version="1.1"> | ||
<g fill="none" fill-rule="evenodd" id="Page-1" stroke="#fff" stroke-width="1"> | ||
<g fill="#000" id="Core" transform="translate(-87.000000, -509.000000)"> | ||
<g id="view-list" transform="translate(87.500000, 509.000000)"> | ||
<path d="M0,9 L4,9 L4,5 L0,5 L0,9 L0,9 Z M0,14 L4,14 L4,10 L0,10 L0,14 L0,14 Z M0,4 L4,4 L4,0 L0,0 L0,4 L0,4 Z M5,9 L17,9 L17,5 L5,5 L5,9 L5,9 Z M5,14 L17,14 L17,10 L5,10 L5,14 L5,14 Z M5,0 L5,4 L17,4 L17,0 L5,0 L5,0 Z" id="Shape"/> | ||
</g> | ||
</g> | ||
</g> | ||
</svg> | ||
); | ||
|
||
export default SvgDetailViewIcon; |
Oops, something went wrong.
555e6b4
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
starmaps – ./
starmaps.vercel.app
starmaps-ipfs-ignite.vercel.app
starmaps-git-main-ipfs-ignite.vercel.app
starmap.site
starmaps.app
www.starmaps.app
www.starmap.site