mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-25 00:21:28 +02:00
Move to separate files
This commit is contained in:
@@ -3,6 +3,6 @@
|
|||||||
"enabled": false
|
"enabled": false
|
||||||
},
|
},
|
||||||
"_variables": {
|
"_variables": {
|
||||||
"lastUpdateCheck": 1738019390029
|
"lastUpdateCheck": 1739229597159
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -17,11 +17,11 @@ import { $accountStreak, type StreakResponse } from '../../stores/streak';
|
|||||||
import type { PageType } from '../CommandMenu/CommandMenu';
|
import type { PageType } from '../CommandMenu/CommandMenu';
|
||||||
import {
|
import {
|
||||||
FavoriteRoadmaps,
|
FavoriteRoadmaps,
|
||||||
HeroRoadmap,
|
|
||||||
type AIRoadmapType,
|
type AIRoadmapType,
|
||||||
} from '../HeroSection/FavoriteRoadmaps.tsx';
|
} from '../HeroSection/FavoriteRoadmaps.tsx';
|
||||||
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
|
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
|
||||||
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
|
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
|
||||||
|
import { HeroRoadmap } from '../HeroSection/HeroRoadmap.tsx';
|
||||||
|
|
||||||
type UserDashboardResponse = {
|
type UserDashboardResponse = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -106,7 +106,7 @@ function DashboardStats(props: DashboardStatsProps) {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container flex items-center justify-between gap-2 pb-2 pt-6 text-sm text-slate-400 mb-3">
|
<div className="container mb-3 flex items-center justify-between gap-2 pb-2 pt-6 text-sm text-slate-400">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<DashboardStatItem
|
<DashboardStatItem
|
||||||
icon={Zap}
|
icon={Zap}
|
||||||
|
@@ -3,25 +3,18 @@ import {
|
|||||||
MapIcon,
|
MapIcon,
|
||||||
Plus,
|
Plus,
|
||||||
Sparkle,
|
Sparkle,
|
||||||
ThumbsUp,
|
|
||||||
ChevronDown,
|
|
||||||
ChevronUp,
|
|
||||||
Eye,
|
Eye,
|
||||||
EyeOff,
|
EyeOff,
|
||||||
CircleDashed,
|
|
||||||
Circle,
|
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import type { ReactNode } from 'react';
|
|
||||||
import type { ResourceType } from '../../lib/resource-progress.ts';
|
|
||||||
import { CreateRoadmapButton } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapButton.tsx';
|
|
||||||
import { MarkFavorite } from '../FeaturedItems/MarkFavorite.tsx';
|
|
||||||
import { CheckIcon } from '../ReactIcons/CheckIcon.tsx';
|
|
||||||
import { Spinner } from '../ReactIcons/Spinner.tsx';
|
|
||||||
import type { UserProgress } from '../TeamProgress/TeamProgressPage.tsx';
|
|
||||||
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions.tsx';
|
|
||||||
import { getRelativeTimeString } from '../../lib/date';
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { cn } from '../../lib/classname.ts';
|
import { cn } from '../../lib/classname.ts';
|
||||||
|
import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions.tsx';
|
||||||
|
import { CheckIcon } from '../ReactIcons/CheckIcon.tsx';
|
||||||
|
import type { UserProgress } from '../TeamProgress/TeamProgressPage.tsx';
|
||||||
|
import { HeroProject } from './HeroProject';
|
||||||
|
import { HeroRoadmap } from './HeroRoadmap';
|
||||||
|
import { HeroTitle } from './HeroTitle';
|
||||||
|
import { CreateRoadmapButton } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapButton.tsx';
|
||||||
|
|
||||||
export type AIRoadmapType = {
|
export type AIRoadmapType = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -29,103 +22,6 @@ export type AIRoadmapType = {
|
|||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type HeroTitleProps = {
|
|
||||||
icon: any;
|
|
||||||
isLoading?: boolean;
|
|
||||||
title: string | ReactNode;
|
|
||||||
rightContent?: ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type FavoriteRoadmapsProps = {
|
type FavoriteRoadmapsProps = {
|
||||||
progress: UserProgress[];
|
progress: UserProgress[];
|
||||||
projects: (ProjectStatusDocument & {
|
projects: (ProjectStatusDocument & {
|
||||||
@@ -137,56 +33,15 @@ type FavoriteRoadmapsProps = {
|
|||||||
isAllCollapsed: boolean;
|
isAllCollapsed: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
||||||
const { progress, isLoading, customRoadmaps, aiRoadmaps, projects, isAllCollapsed } = props;
|
const {
|
||||||
|
progress,
|
||||||
|
isLoading,
|
||||||
|
customRoadmaps,
|
||||||
|
aiRoadmaps,
|
||||||
|
projects,
|
||||||
|
isAllCollapsed,
|
||||||
|
} = props;
|
||||||
const [showCompleted, setShowCompleted] = useState(false);
|
const [showCompleted, setShowCompleted] = useState(false);
|
||||||
|
|
||||||
const completedProjects = projects.filter(
|
const completedProjects = projects.filter(
|
||||||
@@ -203,10 +58,12 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className={cn("", {
|
<div
|
||||||
"border-b border-b-slate-800/70 pt-5 pb-5": !isAllCollapsed,
|
className={cn('', {
|
||||||
"py-2": isAllCollapsed
|
'border-b border-b-slate-800/70 pb-5 pt-5': !isAllCollapsed,
|
||||||
})}>
|
'py-2': isAllCollapsed,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<HeroTitle
|
<HeroTitle
|
||||||
icon={
|
icon={
|
||||||
@@ -218,7 +75,7 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
|||||||
title="Your progress and bookmarks"
|
title="Your progress and bookmarks"
|
||||||
/>
|
/>
|
||||||
{!isLoading && progress.length > 0 && !isAllCollapsed && (
|
{!isLoading && progress.length > 0 && !isAllCollapsed && (
|
||||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3 mt-3">
|
<div className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
|
||||||
{progress.map((resource) => (
|
{progress.map((resource) => (
|
||||||
<HeroRoadmap
|
<HeroRoadmap
|
||||||
key={`${resource.resourceType}-${resource.resourceId}`}
|
key={`${resource.resourceType}-${resource.resourceId}`}
|
||||||
@@ -242,10 +99,12 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={cn("", {
|
<div
|
||||||
"border-b border-b-slate-800/70 pt-5 pb-5": !isAllCollapsed,
|
className={cn('', {
|
||||||
"py-2": isAllCollapsed
|
'border-b border-b-slate-800/70 pb-5 pt-5': !isAllCollapsed,
|
||||||
})}>
|
'py-2': isAllCollapsed,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<HeroTitle
|
<HeroTitle
|
||||||
icon={(<MapIcon className="mr-1.5 h-[14px] w-[14px]" />) as any}
|
icon={(<MapIcon className="mr-1.5 h-[14px] w-[14px]" />) as any}
|
||||||
@@ -253,7 +112,7 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
|||||||
title="Your custom roadmaps"
|
title="Your custom roadmaps"
|
||||||
/>
|
/>
|
||||||
{!isLoading && customRoadmaps.length > 0 && !isAllCollapsed && (
|
{!isLoading && customRoadmaps.length > 0 && !isAllCollapsed && (
|
||||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3 mt-3">
|
<div className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
|
||||||
{customRoadmaps.map((customRoadmap) => (
|
{customRoadmaps.map((customRoadmap) => (
|
||||||
<HeroRoadmap
|
<HeroRoadmap
|
||||||
key={customRoadmap.resourceId}
|
key={customRoadmap.resourceId}
|
||||||
@@ -275,10 +134,12 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={cn("", {
|
<div
|
||||||
"border-b border-b-slate-800/70 pt-5 pb-5": !isAllCollapsed,
|
className={cn('', {
|
||||||
"py-2": isAllCollapsed
|
'border-b border-b-slate-800/70 pb-5 pt-5': !isAllCollapsed,
|
||||||
})}>
|
'py-2': isAllCollapsed,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<HeroTitle
|
<HeroTitle
|
||||||
icon={(<Sparkle className="mr-1.5 h-[14px] w-[14px]" />) as any}
|
icon={(<Sparkle className="mr-1.5 h-[14px] w-[14px]" />) as any}
|
||||||
@@ -286,7 +147,7 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
|||||||
title="Your AI roadmaps"
|
title="Your AI roadmaps"
|
||||||
/>
|
/>
|
||||||
{!isLoading && aiRoadmaps.length > 0 && !isAllCollapsed && (
|
{!isLoading && aiRoadmaps.length > 0 && !isAllCollapsed && (
|
||||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3 mt-3">
|
<div className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
|
||||||
{aiRoadmaps.map((aiRoadmap) => (
|
{aiRoadmaps.map((aiRoadmap) => (
|
||||||
<HeroRoadmap
|
<HeroRoadmap
|
||||||
key={aiRoadmap.id}
|
key={aiRoadmap.id}
|
||||||
@@ -314,10 +175,12 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={cn("", {
|
<div
|
||||||
"border-b border-b-slate-800/70 pt-5 pb-5": !isAllCollapsed,
|
className={cn('', {
|
||||||
"py-2": isAllCollapsed
|
'border-b border-b-slate-800/70 pb-5 pt-5': !isAllCollapsed,
|
||||||
})}>
|
'py-2': isAllCollapsed,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<HeroTitle
|
<HeroTitle
|
||||||
icon={
|
icon={
|
||||||
@@ -342,7 +205,7 @@ export function FavoriteRoadmaps(props: FavoriteRoadmapsProps) {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{!isLoading && projectsToShow.length > 0 && !isAllCollapsed && (
|
{!isLoading && projectsToShow.length > 0 && !isAllCollapsed && (
|
||||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3 mt-3">
|
<div className="mt-3 grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3">
|
||||||
{projectsToShow.map((project) => (
|
{projectsToShow.map((project) => (
|
||||||
<HeroProject key={project._id} project={project} />
|
<HeroProject key={project._id} project={project} />
|
||||||
))}
|
))}
|
||||||
|
52
src/components/HeroSection/HeroProject.tsx
Normal file
52
src/components/HeroSection/HeroProject.tsx
Normal file
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
74
src/components/HeroSection/HeroRoadmap.tsx
Normal file
74
src/components/HeroSection/HeroRoadmap.tsx
Normal file
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
28
src/components/HeroSection/HeroTitle.tsx
Normal file
28
src/components/HeroSection/HeroTitle.tsx
Normal file
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
Reference in New Issue
Block a user