mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-30 04:30:01 +02:00
Add roadmaps page
This commit is contained in:
@@ -21,7 +21,7 @@ const discordInfo = await getDiscordInfo();
|
||||
</p>
|
||||
|
||||
<div
|
||||
class='mt-5 grid grid-cols-1 justify-between gap-2 divide-x-0 sm:my-11 sm:grid-cols-3 sm:gap-0 sm:divide-x'
|
||||
class='mt-5 grid grid-cols-1 justify-between gap-2 divide-x-0 sm:my-11 sm:grid-cols-3 sm:gap-0 sm:divide-x mb-4 sm:mb-0'
|
||||
>
|
||||
<OpenSourceStat text='GitHub Stars' value={starCount} />
|
||||
<OpenSourceStat text='Registered Users' value={'850k'} />
|
||||
|
@@ -14,7 +14,7 @@ const isDiscordMembers = text.toLowerCase() === 'discord members';
|
||||
---
|
||||
|
||||
<div
|
||||
class='flex items-start sm:items-center justify-start flex-col sm:justify-center sm:gap-0 gap-2 sm:bg-transparent bg-gray-200 sm:rounded-none rounded-xl p-4'
|
||||
class='flex items-start sm:items-center justify-start flex-col sm:justify-center sm:gap-0 gap-2 sm:bg-transparent sm:rounded-none rounded-xl p-0 sm:p-4 mb-3 sm:mb-0'
|
||||
>
|
||||
{
|
||||
isGitHubStars && (
|
||||
|
28
src/components/Roadmaps/CategoryFilterButton.tsx
Normal file
28
src/components/Roadmaps/CategoryFilterButton.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { cn } from '../../lib/classname.ts';
|
||||
|
||||
type CategoryFilterButtonProps = {
|
||||
category: string;
|
||||
selected: boolean;
|
||||
onClick: () => void;
|
||||
};
|
||||
|
||||
export function CategoryFilterButton(props: CategoryFilterButtonProps) {
|
||||
const { category, selected, onClick } = props;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={cn(
|
||||
'border-b bg-gradient-to-l py-1.5 pr-3 text-center text-sm text-gray-500 hover:from-white hover:text-gray-900 sm:text-right',
|
||||
{
|
||||
'from-white font-semibold text-gray-900':
|
||||
selected && category !== 'All Roadmaps',
|
||||
'font-semibold text-gray-900': selected && category === 'All Roadmaps',
|
||||
},
|
||||
)}
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
>
|
||||
{category}
|
||||
</button>
|
||||
);
|
||||
}
|
@@ -1,5 +1,8 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { cn } from '../../lib/classname.ts';
|
||||
import { Filter, X } from 'lucide-react';
|
||||
import { CategoryFilterButton } from './CategoryFilterButton.tsx';
|
||||
import { useOutsideClick } from '../../hooks/use-outside-click.ts';
|
||||
|
||||
const groupNames = [
|
||||
'Absolute Beginners',
|
||||
@@ -387,6 +390,14 @@ export function RoadmapsPage() {
|
||||
const [activeGroup, setActiveGroup] = useState<AllowGroupNames>('');
|
||||
const [visibleGroups, setVisibleGroups] = useState<GroupType[]>(allGroups);
|
||||
|
||||
const [isFilterOpen, setIsFilterOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFilterOpen) {
|
||||
document?.getElementById('filter-button')?.focus();
|
||||
}
|
||||
}, [isFilterOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeGroup) {
|
||||
setVisibleGroups(allGroups);
|
||||
@@ -421,46 +432,65 @@ export function RoadmapsPage() {
|
||||
|
||||
return (
|
||||
<div className="border-t bg-gray-100">
|
||||
<div className="container flex flex-row gap-4">
|
||||
<div className="w-[180px] border-r bg-gradient-to-l from-gray-100 pt-6">
|
||||
<div className="sticky top-10 pb-20">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsFilterOpen(!isFilterOpen);
|
||||
}}
|
||||
id="filter-button"
|
||||
className={cn(
|
||||
'-mt-1 flex w-full items-center justify-center bg-gray-300 py-2 text-sm text-black focus:shadow-none focus:outline-0 sm:hidden',
|
||||
{
|
||||
'mb-3': !isFilterOpen,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{!isFilterOpen && <Filter size={13} className="mr-1" />}
|
||||
{isFilterOpen && <X size={13} className="mr-1" />}
|
||||
Categories
|
||||
</button>
|
||||
<div className="container relative flex flex-col gap-4 sm:flex-row">
|
||||
<div
|
||||
className={cn(
|
||||
'hidden w-full flex-col from-gray-100 sm:w-[180px] sm:border-r sm:bg-gradient-to-l sm:pt-6',
|
||||
{
|
||||
'hidden sm:flex': !isFilterOpen,
|
||||
'z-50 flex': isFilterOpen,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<div className="absolute top-0 -mx-4 w-full bg-white pb-0 shadow-xl sm:sticky sm:top-10 sm:mx-0 sm:bg-transparent sm:pb-20 sm:shadow-none">
|
||||
<div className="grid grid-cols-1">
|
||||
<button
|
||||
onClick={() => setActiveGroup('')}
|
||||
className={cn('border-b py-1.5 pr-3 text-right text-sm', {
|
||||
'font-bold text-gray-900': activeGroup === '',
|
||||
})}
|
||||
>
|
||||
{' '}
|
||||
All Roadmaps
|
||||
</button>
|
||||
<CategoryFilterButton
|
||||
onClick={() => {
|
||||
setActiveGroup('');
|
||||
setIsFilterOpen(false);
|
||||
}}
|
||||
category={'All Roadmaps'}
|
||||
selected={activeGroup === ''}
|
||||
/>
|
||||
|
||||
{groups.map((group) => (
|
||||
<button
|
||||
<CategoryFilterButton
|
||||
key={group.group}
|
||||
className={cn(
|
||||
'border-b bg-gradient-to-l py-1.5 pr-3 text-right text-sm text-gray-500 hover:from-white hover:text-gray-900',
|
||||
{
|
||||
'from-white font-semibold text-gray-900':
|
||||
activeGroup === group.group,
|
||||
},
|
||||
)}
|
||||
type="button"
|
||||
onClick={() => setActiveGroup(group.group)}
|
||||
>
|
||||
{group.group}
|
||||
</button>
|
||||
onClick={() => {
|
||||
setActiveGroup(group.group);
|
||||
setIsFilterOpen(false);
|
||||
}}
|
||||
category={group.group}
|
||||
selected={activeGroup === group.group}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-grow flex-col gap-6 pb-20 pt-8">
|
||||
<div className="flex flex-grow flex-col gap-6 pb-20 pt-2 sm:pt-8">
|
||||
{visibleGroups.map((group) => (
|
||||
<div key={`${group.group}-${group.roadmaps.length}`}>
|
||||
<h2 className="mb-2 text-xs uppercase tracking-wide text-gray-400">
|
||||
{group.group}
|
||||
</h2>
|
||||
|
||||
<div className="grid grid-cols-3 gap-1.5">
|
||||
<div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2 md:grid-cols-3">
|
||||
{group.roadmaps.map((roadmap) => (
|
||||
<a
|
||||
key={roadmap.link}
|
||||
|
@@ -1,21 +1,21 @@
|
||||
export function RoadmapsPageHeader() {
|
||||
return (
|
||||
<div className="bg-white py-12">
|
||||
<div className="bg-white py-3 sm:py-12">
|
||||
<div className="container">
|
||||
<div className="flex flex-col items-center bg-white">
|
||||
<h1 className="text-5xl font-bold">Developer Roadmaps</h1>
|
||||
<p className="my-3 text-lg">
|
||||
Browse ever-growing list of up-to-date, community driven roadmaps
|
||||
<div className="flex flex-col items-start sm:items-center bg-white">
|
||||
<h1 className="text-2xl sm:text-5xl font-bold">Developer Roadmaps</h1>
|
||||
<p className="mb-3 mt-1 sm:my-3 text-sm sm:text-lg">
|
||||
Browse the ever-growing list of up-to-date, community driven roadmaps
|
||||
</p>
|
||||
<p className="flex flex-row gap-3">
|
||||
<p className="flex flex-col sm:flex-row gap-1.5 sm:gap-3 w-full sm:w-auto mb-3 sm:mb-0">
|
||||
<a
|
||||
className="inline-block rounded-md bg-black px-3.5 py-1.5 text-base text-white"
|
||||
className="inline-block rounded-md bg-black px-3.5 py-2 sm:py-1.5 text-sm sm:text-base text-white"
|
||||
href="#"
|
||||
>
|
||||
Draw your own roadmap
|
||||
</a>
|
||||
<a
|
||||
className="inline-block rounded-md bg-gray-300 px-3.5 py-1.5 text-base text-black"
|
||||
className="inline-block rounded-md bg-gray-300 px-3.5 py-2 sm:py-1.5 text-sm sm:text-base text-black"
|
||||
href="#"
|
||||
>
|
||||
Generate Roadmap with AI
|
||||
|
Reference in New Issue
Block a user