parent
a143b0ec20
commit
6abc5ff916
6 changed files with 200 additions and 183 deletions
@ -0,0 +1,52 @@ |
|||||||
|
import { ThumbsUp } from 'lucide-react'; |
||||||
|
import { cn } from '../../lib/classname.ts'; |
||||||
|
import { getRelativeTimeString } from '../../lib/date'; |
||||||
|
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions.tsx'; |
||||||
|
|
||||||
|
type HeroProjectProps = { |
||||||
|
project: ProjectStatusDocument & { |
||||||
|
title: string; |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
export function HeroProject({ project }: HeroProjectProps) { |
||||||
|
return ( |
||||||
|
<a |
||||||
|
href={`/projects/${project.projectId}`} |
||||||
|
className="group relative flex flex-col justify-between gap-2 rounded-md border border-slate-800 bg-slate-900 p-3.5 hover:border-slate-600" |
||||||
|
> |
||||||
|
<div className="relative z-10 flex items-start justify-between gap-2"> |
||||||
|
<h3 className="truncate font-medium text-slate-300 group-hover:text-slate-100"> |
||||||
|
{project.title} |
||||||
|
</h3> |
||||||
|
<span |
||||||
|
className={cn( |
||||||
|
'absolute -right-2 -top-2 flex flex-shrink-0 items-center gap-1 rounded-full text-xs uppercase tracking-wide', |
||||||
|
{ |
||||||
|
'text-green-600/50': project.submittedAt && project.repositoryUrl, |
||||||
|
'text-yellow-600': !project.submittedAt || !project.repositoryUrl, |
||||||
|
}, |
||||||
|
)} |
||||||
|
> |
||||||
|
{project.submittedAt && project.repositoryUrl ? 'Done' : ''} |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
<div className="relative z-10 flex items-center gap-2 text-xs text-slate-400"> |
||||||
|
{project.submittedAt && project.repositoryUrl && ( |
||||||
|
<span className="flex items-center gap-1"> |
||||||
|
<ThumbsUp className="h-3 w-3" /> |
||||||
|
{project.upvotes} |
||||||
|
</span> |
||||||
|
)} |
||||||
|
{project.startedAt && ( |
||||||
|
<span>Started {getRelativeTimeString(project.startedAt)}</span> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
|
||||||
|
<div className="absolute inset-0 rounded-md bg-gradient-to-br from-slate-800/50 via-transparent to-transparent" /> |
||||||
|
{project.submittedAt && project.repositoryUrl && ( |
||||||
|
<div className="absolute inset-0 rounded-md bg-gradient-to-br from-green-950/20 via-transparent to-transparent" /> |
||||||
|
)} |
||||||
|
</a> |
||||||
|
); |
||||||
|
}
|
@ -0,0 +1,74 @@ |
|||||||
|
import { cn } from '../../lib/classname.ts'; |
||||||
|
import type { ResourceType } from '../../lib/resource-progress.ts'; |
||||||
|
import { MarkFavorite } from '../FeaturedItems/MarkFavorite.tsx'; |
||||||
|
|
||||||
|
type ProgressRoadmapProps = { |
||||||
|
url: string; |
||||||
|
percentageDone: number; |
||||||
|
allowFavorite?: boolean; |
||||||
|
|
||||||
|
resourceId: string; |
||||||
|
resourceType: ResourceType; |
||||||
|
resourceTitle: string; |
||||||
|
isFavorite?: boolean; |
||||||
|
|
||||||
|
isTrackable?: boolean; |
||||||
|
isNew?: boolean; |
||||||
|
}; |
||||||
|
|
||||||
|
export function HeroRoadmap(props: ProgressRoadmapProps) { |
||||||
|
const { |
||||||
|
url, |
||||||
|
percentageDone, |
||||||
|
resourceType, |
||||||
|
resourceId, |
||||||
|
resourceTitle, |
||||||
|
isFavorite, |
||||||
|
allowFavorite = true, |
||||||
|
isTrackable = true, |
||||||
|
isNew = false, |
||||||
|
} = props; |
||||||
|
|
||||||
|
return ( |
||||||
|
<a |
||||||
|
href={url} |
||||||
|
className={cn( |
||||||
|
'relative flex flex-col overflow-hidden rounded-md border p-3 text-sm text-slate-400 hover:text-slate-300', |
||||||
|
{ |
||||||
|
'border-slate-800 bg-slate-900 hover:border-slate-600': isTrackable, |
||||||
|
'border-slate-700/50 bg-slate-800/50 hover:border-slate-600/70': |
||||||
|
!isTrackable, |
||||||
|
}, |
||||||
|
)} |
||||||
|
> |
||||||
|
<span title={resourceTitle} className="relative z-20 truncate"> |
||||||
|
{resourceTitle} |
||||||
|
</span> |
||||||
|
|
||||||
|
{isTrackable && ( |
||||||
|
<span |
||||||
|
className="absolute bottom-0 left-0 top-0 z-10 bg-[#172a3a]" |
||||||
|
style={{ width: `${percentageDone}%` }} |
||||||
|
></span> |
||||||
|
)} |
||||||
|
|
||||||
|
{allowFavorite && ( |
||||||
|
<MarkFavorite |
||||||
|
resourceId={resourceId} |
||||||
|
resourceType={resourceType} |
||||||
|
favorite={isFavorite} |
||||||
|
/> |
||||||
|
)} |
||||||
|
|
||||||
|
{isNew && ( |
||||||
|
<span className="absolute bottom-1.5 right-2 flex items-center rounded-br rounded-tl text-xs font-medium text-purple-300"> |
||||||
|
<span className="mr-1.5 flex h-2 w-2"> |
||||||
|
<span className="absolute inline-flex h-2 w-2 animate-ping rounded-full bg-purple-400 opacity-75" /> |
||||||
|
<span className="relative inline-flex h-2 w-2 rounded-full bg-purple-500" /> |
||||||
|
</span> |
||||||
|
New |
||||||
|
</span> |
||||||
|
)} |
||||||
|
</a> |
||||||
|
); |
||||||
|
}
|
@ -0,0 +1,28 @@ |
|||||||
|
import type { ReactNode } from 'react'; |
||||||
|
import { Spinner } from '../ReactIcons/Spinner.tsx'; |
||||||
|
|
||||||
|
type HeroTitleProps = { |
||||||
|
icon: any; |
||||||
|
isLoading?: boolean; |
||||||
|
title: string | ReactNode; |
||||||
|
rightContent?: ReactNode; |
||||||
|
}; |
||||||
|
|
||||||
|
export function HeroTitle(props: HeroTitleProps) { |
||||||
|
const { isLoading = false, title, icon, rightContent } = props; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="flex items-center justify-between"> |
||||||
|
<p className="flex items-center text-sm text-gray-400"> |
||||||
|
{!isLoading && icon} |
||||||
|
{isLoading && ( |
||||||
|
<span className="mr-1.5"> |
||||||
|
<Spinner /> |
||||||
|
</span> |
||||||
|
)} |
||||||
|
{title} |
||||||
|
</p> |
||||||
|
<div>{rightContent}</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}
|
Loading…
Reference in new issue