Skip to content

Repository files navigation

Visit this link to see the live project

Search Github Users

This is a web app that allows you to search for users on Github.

Technologies Used

  • React
  • Vite
  • Tailwind CSS
  • Shadcn UI
  • Typescript
  • Github API
  • Appollo Client

Setup

npm create vite@latest
npm install
npm run dev

Tailwind CSS

npm install tailwindcss @tailwindcss/vite

index.css

@import "tailwindcss";

tsconfig.json

{
  "files": [],
  "references": [
    {
      "path": "./tsconfig.app.json"
    },
    {
      "path": "./tsconfig.node.json"
    }
  ],
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

tsconfig.app.json

{
  "compilerOptions": {
    // ...
    "paths": {
      "@/*": [
        "./src/*"
      ]
    }
    // ...
  }
}
npm install -D @types/node

vite.config.ts

import path from "path"
import tailwindcss from "@tailwindcss/vite"
import react from "@vitejs/plugin-react"
import { defineConfig } from "vite"

// https://vite.dev/config/
export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
})
npx shadcn@latest init
  • replace existing theme setup with custom shadcn theme:
npx shadcn@latest apply --preset b2BoWoOuW
  • add shadcn components
npx shadcn@latest add button card chart input label skeleton toast

App.tsx

import { Button } from "@/components/ui/button"
const App = () => {
  return (
    <div className="flex  items-center justify-center h-screen">
      <div className="flex gap-4">
        <Button>Click me</Button>
        <Button variant="outline" size="lg">
          Click me
        </Button>
        <Button variant="destructive" size="sm">
          Click me
        </Button>
      </div>
    </div>
  )
}
export default App
  • remove App.css
  • change title in index.html
<title>Search Github Users</title>

Structure

  • setup local state in App.tsx
  • create src/components/form/SearchForm.tsx
  • create src/components/user/UserProfile.tsx
  • render both components in App.tsx
  • pass userName and setUserName to SearchForm
  • pass userName to UserProfile

App.tsx

const [userName, setUserName] = useState("quincylarson")

src/components/form/SearchForm.tsx

type SearchFormProps = {
  userName: string
  setUserName: React.Dispatch<React.SetStateAction<string>>
}

const SearchForm = ({ userName, setUserName }: SearchFormProps) => {
  return <div>SearchForm</div>
}
export default SearchForm

src/components/user/UserProfile.tsx

type UserProfileProps = {
  userName: string
}

const UserProfile = ({ userName }: UserProfileProps) => {
  return <h1 className="text-2xl font-bold">{userName}</h1>
}
export default UserProfile

src/App.tsx

import { useState } from "react"
import SearchForm from "./components/form/SearchForm"
import UserProfile from "./components/user/UserProfile"

const App = () => {
  const [userName, setUserName] = useState("quincylarson")

  return (
    <main className="mx-auto max-w-6xl px-8 py-20">
      <SearchForm userName={userName} setUserName={setUserName} />
      <UserProfile userName={userName} />
    </main>
  )
}
export default App

Search Form

import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { type FormEvent } from "react"
import { useState } from "react"

type SearchFormProps = {
  userName: string
  setUserName: React.Dispatch<React.SetStateAction<string>>
}

const SearchForm = ({ userName, setUserName }: SearchFormProps) => {
  const [text, setText] = useState(userName)

  const handleSearch = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (text === "") {
      console.log("Please enter a username")
      return
    }
    setUserName(text)
  }

  return (
    <form
      onSubmit={handleSearch}
      className="flex items-center gap-x-2 w-full lg:w-1/3 mb-8"
    >
      <Label htmlFor="search" className="sr-only">
        Search
      </Label>
      <Input
        type="text"
        id="search"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Search Github User..."
        className="grow bg-background"
      />
      <Button type="submit">Search</Button>
    </form>
  )
}
export default SearchForm

Shadcn Toast

main.tsx

import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import "./index.css"
import App from "./App.tsx"
// import Toaster component
import { Toaster } from "@/components/ui/toaster"

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <App />
    <Toaster />
  </StrictMode>,
)

src/components/form/SearchForm.tsx

import { toast } from "sonner"

const SearchForm = ({ userName, setUserName }: SearchFormProps) => {
  const handleSearch = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    if (text === "") {
      toast.error("Please enter a valid username")
      return
    }
    setUserName(text)
  }

  return <form>...</form>
}
export default SearchForm

Graphql

GraphQL is a modern query language and runtime for APIs that allows clients to request specific data they need and nothing more. Unlike traditional REST APIs where you get fixed data from multiple endpoints, GraphQL provides a single endpoint where you can specify exactly what data you want to receive.

  • Schema: The blueprint that defines all available data types and operations in your API
  • Query: A request to read or fetch data (similar to GET in REST)
  • Mutation: A request to create, update, or delete data (similar to POST/PUT/DELETE in REST)
  • Fields: The individual pieces of data you can request (like user.name or post.title)
  • Arguments: Parameters you can pass to fields to filter or modify the results (like limit: 10)
  • Types: The different kinds of data objects available (like User, Post, Comment)
  • Nodes: Objects in a GraphQL schema that have a unique identifier, typically representing entities in your data model (like a specific user or post)

Practice API's

Github GraphQL Explorer

Github GraphQL Explorer

  • The explorer may not be available now.

Github Personal Access Token

Github

  • profile
  • settings
  • developer settings
  • personal access token
  • generate new token
  • create .env.local file
  • add token to .env.local file

.env.local

VITE_GITHUB_TOKEN=YOUR_TOKEN_HERE

Apollo Client

Apollo Client is a comprehensive state management library for JavaScript applications that helps you manage both local and remote data with GraphQL. It makes it easy to fetch, cache, and modify application data while automatically handling important concerns like tracking loading and error states. The library integrates especially well with React applications and provides features like automatic caching, optimistic UI updates, and error handling out of the box.

Apollo Client

npm install @apollo/client graphql rxjs
  • src/apolloClient.ts
// Core Apollo Client imports for GraphQL functionality
// ApolloClient: Main client class for making GraphQL requests
// InMemoryCache: Caching solution for storing query results
// HttpLink: Configures HTTP connection to GraphQL endpoint
// ApolloLink: Enables creation of middleware chain for request/response handling
import {
  ApolloClient,
  InMemoryCache,
  HttpLink,
  ApolloLink,
} from "@apollo/client"

// Error handling middleware for Apollo Client
// Provides detailed error information for both GraphQL and network errors
import { onError } from "@apollo/client/link/error"

// GitHub GraphQL API endpoint
const GITHUB_GRAPHQL_API = "https://api.github.com/graphql"

// Configure error handling middleware
// This will intercept and log any GraphQL or network errors
const errorLink = onError(({ graphQLErrors, networkError }) => {
  // Handle GraphQL-specific errors (e.g., validation, resolver errors)
  if (graphQLErrors) {
    graphQLErrors.forEach(({ message, locations, path }) => {
      console.error(
        `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
      )
    })
  }

  // Handle network-level errors (e.g., connection issues)
  if (networkError) {
    console.error(`[Network error]: ${networkError}`)
  }
})

// Configure HTTP connection to GitHub's GraphQL API
// Including authentication token from environment variables
const httpLink = new HttpLink({
  uri: GITHUB_GRAPHQL_API,
  headers: {
    Authorization: `Bearer ${import.meta.env.VITE_GITHUB_TOKEN}`, // GitHub Personal Access Token
  },
})

// Create the Apollo Link chain
// Order matters: errorLink will run before httpLink
const link = ApolloLink.from([errorLink, httpLink])

// Initialize Apollo Client with:
// - Configured link chain for network requests
// - In-memory cache for storing query results
const client = new ApolloClient({
  link,
  cache: new InMemoryCache(),
})

export default client

src/main.tsx

import { createRoot } from "react-dom/client"
import App from "./App.tsx"
import "./index.css"
import { Toaster } from "@/components/ui/toaster"
// Apollo Provider
import { ApolloProvider } from "@apollo/client"
import client from "./apolloClient"

createRoot(document.getElementById("root")!).render(
  <ApolloProvider client={client}>
    <App />
    <Toaster />
  </ApolloProvider>,
)

Query and Type

(https://docs.github.com/en/graphql/reference/users)

src/types.ts

export type LanguageEdge = {
  node: {
    name: string
  }
  size: number
}

export type Repository = {
  name: string
  description: string
  stargazerCount: number
  forkCount: number
  url: string
  languages: {
    edges: LanguageEdge[]
  }
}

export type User = {
  name: string
  avatarUrl: string
  bio: string
  url: string
  repositories: {
    totalCount: number
    nodes: Repository[]
  }
  followers: {
    totalCount: number
  }
  following: {
    totalCount: number
  }
  gists: {
    totalCount: number
  }
}
export type UserData = {
  user: User
}

src/queries.ts

import { gql, type TypedDocumentNode } from "@apollo/client"
import type { UserData } from "./types.ts"

export const GET_USER: TypedDocumentNode<UserData> = gql`
  query ($login: String!) {
    user(login: $login) {
      name
      avatarUrl
      bio
      url
      repositories(first: 100) {
        totalCount
        nodes {
          name
          description
          stargazerCount
          forkCount
          url
          languages(first: 5) {
            edges {
              node {
                name
              }
              size
            }
          }
        }
      }
      followers {
        totalCount
      }
      following {
        totalCount
      }
      gists {
        totalCount
      }
    }
  }
`

Query Hook

src/components/user/UserProfile.tsx

import { useQuery } from "@apollo/client"
import { GET_USER } from "@/queries"

type UserProfileProps = {
  userName: string
}

const UserProfile = ({ userName }: UserProfileProps) => {
  const { loading, error, data } = useQuery(GET_USER, {
    variables: { login: userName },
  })

  if (loading) return <div>Loading...</div>
  if (error) return <h2 className="text-xl">{error.message}</h2>
  if (!data) return <h2 className="text-xl">User Not Found.</h2>

  const {
    avatarUrl,
    name,
    bio,
    url,
    repositories,
    followers,
    following,
    gists,
  } = data.user

  return (
    <div>
      <h1>{bio}</h1>
    </div>
  )
}

export default UserProfile

User Card

src/components/user/UserCard.tsx

import { Button } from "@/components/ui/button"
import {
  Card,
  CardTitle,
  CardDescription,
  CardHeader,
} from "@/components/ui/card"

type UserCardProps = {
  avatarUrl: string
  name: string
  bio: string
  url: string
}
const UserCard = ({ avatarUrl, name, bio, url }: UserCardProps) => {
  return (
    <Card className="w-full lg:w-1/2 mb-8">
      <CardHeader className="flex gap-x-8 items-center">
        <img
          src={avatarUrl}
          alt={name}
          className="w-36 h-36  rounded object-cover"
        />
        <div className="flex flex-col gap-y-2">
          <CardTitle>{name || "Coding Addict"}</CardTitle>
          <CardDescription>
            {bio || "Passionate about coding and technology."}
          </CardDescription>
          <Button asChild size="sm" className="w-1/2 mt-2">
            <a href={url} target="_blank" rel="noreferrer">
              Follow
            </a>
          </Button>
        </div>
      </CardHeader>
    </Card>
  )
}
export default UserCard
  • UserProfile.tsx
return (
  <div>
    <UserCard avatarUrl={avatarUrl} name={name} bio={bio} url={url} />
  </div>
)

Stats Card

import { Card, CardTitle, CardDescription } from "../ui/card"

type StatsCardProps = {
  title: string
  count: number
}

function StatsCard({ title, count }: StatsCardProps) {
  return (
    <Card>
      <div className="flex flex-row justify-between items-center p-6">
        <CardTitle>{title}</CardTitle>
        <CardDescription>{count}</CardDescription>
      </div>
    </Card>
  )
}

export default StatsCard

Stats Container

import StatsCard from "./StatsCard"

type StatsContainerProps = {
  totalRepos: number
  followers: number
  following: number
  gists: number
}

const StatsContainer = (props: StatsContainerProps) => {
  const { totalRepos, followers, following, gists } = props

  return (
    <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-2 mb-8 ">
      <StatsCard title="Total Repositories" count={totalRepos} />
      <StatsCard title="Followers" count={followers} />
      <StatsCard title="Following" count={following} />
      <StatsCard title="Gists" count={gists} />
    </div>
  )
}
export default StatsContainer

UserProfile.tsx

return (
  <div>
    <UserCard avatarUrl={avatarUrl} name={name} bio={bio} url={url} />
    <StatsContainer
      totalRepos={repositories.totalCount}
      followers={followers.totalCount}
      following={following.totalCount}
      gists={gists.totalCount}
    />
  </div>
)

Util Functions

And once we are done with the Stats container, we can start working on the charts, but since charts will need very specific data, first we will need to create some util functions to help us generate such data.

src/utils.ts

import { Repository } from "./types"

/**
 * Calculates the top 5 most forked repositories
 * @param repositories Array of repository data from GitHub API
 * @returns Array of objects containing repository names and their fork counts
 * Example return: [{ repo: "react", count: 1000 }, { repo: "vue", count: 500 }]
 */
export const calculateMostForkedRepos = (
  repositories: Repository[],
): { repo: string; count: number }[] => {
  if (repositories.length === 0) {
    return []
  }

  // Transform repository data into simplified objects containing only name and fork count
  const forkedRepos = repositories
    .map((repo) => ({
      repo: repo.name, // Extract repository name
      count: repo.forkCount, // Extract number of forks
    }))
    .sort((a, b) => b.count - a.count) // Sort by fork count in descending order
    .slice(0, 5) // Take only the top 5 repositories

  return forkedRepos
}

/**
 * Calculates the top 5 most starred repositories
 * @param repositories Array of repository data from GitHub API
 * @returns Array of objects containing repository names and their star counts
 * Example return: [{ repo: "tensorflow", stars: 5000 }, { repo: "linux", stars: 4000 }]
 */
export const calculateMostStarredRepos = (
  repositories: Repository[],
): { repo: string; stars: number }[] => {
  if (repositories.length === 0) {
    return []
  }

  // Transform repository data into simplified objects containing only name and star count
  const starredRepos = repositories
    .map((repo) => ({
      repo: repo.name, // Extract repository name
      stars: repo.stargazerCount, // Extract number of stars (stargazers)
    }))
    .sort((a, b) => b.stars - a.stars) // Sort by star count in descending order
    .slice(0, 5) // Take only the top 5 repositories

  return starredRepos
}

/**
 * Calculates the top 5 most used programming languages across all repositories
 * @param repositories Array of repository data from GitHub API
 * @returns Array of objects containing language names and their occurrence count
 * Example return: [{ language: "JavaScript", count: 10 }, { language: "Python", count: 7 }]
 */

export const calculatePopularLanguages = (
  repositories: Repository[],
): { language: string; count: number }[] => {
  // Return empty array if no repositories are provided
  if (repositories.length === 0) {
    return []
  }

  // Initialize a map to track how many times each language appears
  // Example: { "JavaScript": 5, "Python": 3, "TypeScript": 2 }
  const languageMap: { [key: string]: number } = {}

  repositories.forEach((repo) => {
    // Skip repositories with no languages
    if (repo.languages.edges.length === 0) {
      return
    }

    // Iterate through each language in the repository
    // languages.edges comes from GitHub's GraphQL API structure
    repo.languages.edges.forEach((language) => {
      const { name } = language.node
      // Increment the count for this language, initializing to 1 if it's the first occurrence
      languageMap[name] = (languageMap[name] || 0) + 1
    })
  })

  // If no languages were found in any repository, return empty array
  if (Object.keys(languageMap).length === 0) {
    return []
  }

  // Convert the language map into an array of objects and sort them
  return (
    Object.entries(languageMap)
      // Convert entries into array of [language, count] pairs
      .sort(([, a], [, b]) => b - a) // Sort by count in descending order
      .slice(0, 5) // Take only the top 5 languages
      .map(([language, count]) => ({ language, count }))
  ) // Transform into required object format
}

Charts

  • components/charts/UsedLanguages.tsx
  • components/charts/PopularRepos.tsx
  • components/charts/ForkedRepos.tsx

src/components/charts/ForkedRepos.tsx

import type { Repository } from "@/types"
import { calculateMostForkedRepos } from "@/utils"

const ForkedRepos = ({ repositories }: { repositories: Repository[] }) => {
  // Calculate most forked repositories and return array of {repo: string, count: number}
  const mostForkedRepos = calculateMostForkedRepos(repositories)
  console.log(mostForkedRepos)

  return <div>ForkedRepos</div>
}

export default ForkedRepos

src/components/charts/PopularRepos.tsx

import type { Repository } from "@/types"
import { calculateMostStarredRepos } from "@/utils"

const PopularRepos = ({ repositories }: { repositories: Repository[] }) => {
  const popularRepos = calculateMostStarredRepos(repositories)
  console.log(popularRepos)

  return <div>PopularRepos</div>
}

export default PopularRepos

src/components/charts/UsedLanguages.tsx

import type { Repository } from "@/types"
import { calculateMostUsedLanguages } from "@/utils"

const UsedLanguages = ({ repositories }: { repositories: Repository[] }) => {
  const mostUsedLanguages = calculateMostUsedLanguages(repositories)
  console.log(mostUsedLanguages)

  return <div>UsedLanguages</div>
}

export default UsedLanguages

UserProfile.tsx

{
  repositories.totalCount > 0 && (
    <div className="grid md:grid-cols-2 gap-4">
      <UsedLanguages repositories={repositories.nodes} />
      <PopularRepos repositories={repositories.nodes} />
      <ForkedRepos repositories={repositories.nodes} />
    </div>
  )
}

Used Languages

  • add chart shadcn ui (which also installs recharts library)
npx shadcn@latest add chart

components/charts/UsedLanguages.tsx

import { type Repository } from "@/types"
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
import {
  ChartConfig,
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
} from "@/components/ui/chart"
import { calculatePopularLanguages } from "@/utils"

const UsedLanguages = ({ repositories }: { repositories: Repository[] }) => {
  // Calculate popular languages
  //  [{language: string, count: number}]
  const popularLanguages = calculatePopularLanguages(repositories)

  // Configuration for the chart's styling and labels
  // color sets the color of the bars

  const chartConfig = {
    language: {
      label: "Language",
      color: "#2563eb",
    },
  } satisfies ChartConfig
  return (
    <div>
      <h2 className="text-2xl font-semibold text-center mb-4">
        Used Languages
      </h2>
      {/* ChartContainer handles responsive sizing and theme variables */}
      <ChartContainer config={chartConfig} className="h-100 w-full">
        {/* BarChart is the main container for the bar chart visualization */}
        {/* accessibilityLayer adds ARIA labels for better screen reader support */}
        <BarChart accessibilityLayer data={popularLanguages}>
          {/* CartesianGrid adds horizontal guide lines */}
          <CartesianGrid vertical={false} />

          {/* XAxis configures the horizontal axis showing language names */}
          <XAxis
            dataKey="language"
            tickLine={false} // Removes tick marks
            tickMargin={10} // Adds spacing between labels and axis
          />

          {/* YAxis configures the vertical axis showing count values */}
          <YAxis dataKey="count" />

          {/* ChartTooltip shows details when hovering over bars */}
          <ChartTooltip content={<ChartTooltipContent />} />

          {/* Bar component defines how each data point is rendered */}
          {/* Uses CSS variable for color and adds rounded corners */}
          <Bar dataKey="count" fill="var(--color-language)" radius={4} />
        </BarChart>
      </ChartContainer>
    </div>
  )
}

export default UsedLanguages

Popular Repos

components/charts/PopularRepos.tsx

import { type Repository } from "@/types"
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
import {
  ChartConfig,
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
} from "@/components/ui/chart"
import { calculateMostStarredRepos } from "@/utils"

const PopularRepos = ({ repositories }: { repositories: Repository[] }) => {
  // Calculate most starred repositories and return array of {repo: string, stars: number}
  const popularRepos = calculateMostStarredRepos(repositories)

  // Configuration for the chart's styling and labels
  const chartConfig = {
    repo: {
      label: "Repository",
      color: "#e11c47", // Red color for the bars
    },
  } satisfies ChartConfig

  return (
    <div>
      <h2 className="text-2xl font-semibold text-center mb-4">Popular Repos</h2>
      {/* ChartContainer: Custom wrapper component that handles responsive sizing and theme */}
      <ChartContainer config={chartConfig} className="h-100 w-full">
        {/* BarChart: Main chart component from recharts */}
        {/* accessibilityLayer adds ARIA labels for better screen reader support */}
        <BarChart accessibilityLayer data={popularRepos}>
          {/* CartesianGrid: Adds horizontal guide lines (vertical disabled) */}
          <CartesianGrid vertical={false} />

          {/* XAxis: Horizontal axis showing repository names */}
          {/* tickFormatter truncates long repository names to 10 characters */}
          <XAxis
            dataKey="repo"
            tickLine={false}
            tickMargin={10}
            tickFormatter={(value) => value.slice(0, 10)}
          />

          {/* YAxis: Vertical axis showing star counts */}
          <YAxis dataKey="stars" />

          {/* ChartTooltip: Custom tooltip component that appears on hover */}
          {/* ChartTooltipContent: Renders the actual content inside the tooltip */}
          <ChartTooltip content={<ChartTooltipContent />} />

          {/* Bar: The actual bar elements of the chart */}
          {/* fill uses CSS variable for consistent theming */}
          {/* radius adds rounded corners to the bars */}
          <Bar dataKey="stars" fill="var(--color-repo)" radius={4} />
        </BarChart>
      </ChartContainer>
    </div>
  )
}

export default PopularRepos

Forked Repos

components/charts/ForkedRepos.tsx

import { type Repository } from "@/types"
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"
import {
  ChartConfig,
  ChartContainer,
  ChartTooltip,
  ChartTooltipContent,
} from "@/components/ui/chart"
import { calculateMostForkedRepos } from "@/utils"

const ForkedRepos = ({ repositories }: { repositories: Repository[] }) => {
  // Calculate most forked repositories and return array of {repo: string, count: number}
  const mostForkedRepos = calculateMostForkedRepos(repositories)

  // Define chart configuration for styling and labels
  const chartConfig = {
    repo: {
      label: "Repository",
      color: "#facd12",
    },
  } satisfies ChartConfig

  return (
    <div>
      <h2 className="text-2xl font-semibold text-center mb-4">Forked Repos</h2>
      {/* ChartContainer handles responsive sizing and theme variables */}
      <ChartContainer config={chartConfig} className="h-100 w-full">
        {/* BarChart is the main container for the bar chart visualization */}
        {/* accessibilityLayer adds ARIA labels for better screen reader support */}
        <BarChart accessibilityLayer data={mostForkedRepos}>
          {/* CartesianGrid adds background gridlines, vertical lines disabled */}
          <CartesianGrid vertical={false} />

          {/* XAxis configures the horizontal axis */}
          <XAxis
            dataKey="repo" // Uses 'repo' property from data for labels
            tickLine={true} // Shows small lines at each tick mark
            tickMargin={10} // Space between tick line and label
            axisLine={false} // Hides the main axis line
            tickFormatter={(value) => value.slice(0, 10)} // Truncates long repo names
          />

          {/* YAxis configures the vertical axis, showing fork counts */}
          <YAxis dataKey="count" />

          {/* ChartTooltip shows details when hovering over bars */}
          <ChartTooltip content={<ChartTooltipContent />} />

          {/* Bar component defines the actual bars in the chart */}
          {/* Uses CSS variable for color and rounded corners (radius) */}
          <Bar dataKey="count" fill="var(--color-repo)" radius={4} />
        </BarChart>
      </ChartContainer>
    </div>
  )
}

export default ForkedRepos

Loading

src/components/user/Loading.tsx

import { Skeleton } from "@/components/ui/skeleton"

/**
 * Loading component that displays placeholder content while data is being fetched
 * Uses shadcn/ui's Skeleton component to create loading animations
 */
const Loading = () => {
  return (
    <div>
      {/* Large header skeleton
          - h-[194px]: Fixed height of 194px
          - w-full: Full width on mobile
          - lg:w-1/2: Half width on large screens
          - mb-8: Bottom margin of 2rem */}
      <Skeleton className="h-[194px] w-full lg:w-1/2 mb-8 rounded " />

      {/* Grid container for smaller skeletons
          - grid-cols-1: Single column on mobile
          - lg:grid-cols-2: 2 columns on large screens
          - xl:grid-cols-4: 4 columns on extra large screens
          - gap-2: Small gap between grid items */}
      <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-4 gap-2 mb-8">
        {/* Four identical skeleton items
            - h-[70px]: Fixed height of 70px
            - rounded: Rounded corners */}
        <Skeleton className=" h-[70px] rounded" />
        <Skeleton className=" h-[70px] rounded" />
        <Skeleton className=" h-[70px] rounded" />
        <Skeleton className=" h-[70px] rounded" />
      </div>
    </div>
  )
}

export default Loading

UserProfile.tsx

if (loading) return <Loading />

About

A project for searching github users which uses github grapql api and react with typescript and tailwindcss.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages