aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/ui
diff options
context:
space:
mode:
authoraman333nolawz <amananudhiu@gmail.com>2026-03-02 21:49:13 +0530
committeraman333nolawz <amananudhiu@gmail.com>2026-03-02 21:49:13 +0530
commitd41eff9eccf66644c4c60f53cb516bf079937a28 (patch)
tree248a0863a3181369f943d678ec4273911d35efac /src/components/ui
Using astro-erudite for static site generation
Diffstat (limited to 'src/components/ui')
-rw-r--r--src/components/ui/avatar.tsx74
-rw-r--r--src/components/ui/badge.tsx46
-rw-r--r--src/components/ui/breadcrumb.tsx108
-rw-r--r--src/components/ui/button.tsx56
-rw-r--r--src/components/ui/pagination.tsx195
-rw-r--r--src/components/ui/scroll-area.tsx56
-rw-r--r--src/components/ui/separator.tsx26
7 files changed, 561 insertions, 0 deletions
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
new file mode 100644
index 0000000..dc92ac0
--- /dev/null
+++ b/src/components/ui/avatar.tsx
@@ -0,0 +1,74 @@
+import * as React from 'react'
+import { Avatar as AvatarPrimitive } from 'radix-ui'
+
+import { cn } from '@/lib/utils'
+
+function Avatar({
+ className,
+ ...props
+}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
+ return (
+ <AvatarPrimitive.Root
+ data-slot="avatar"
+ className={cn(
+ 'relative flex size-8 shrink-0 overflow-hidden rounded-full',
+ className,
+ )}
+ {...props}
+ />
+ )
+}
+
+function AvatarImage({
+ className,
+ ...props
+}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
+ return (
+ <AvatarPrimitive.Image
+ data-slot="avatar-image"
+ className={cn('aspect-square size-full', className)}
+ {...props}
+ />
+ )
+}
+
+function AvatarFallback({
+ className,
+ ...props
+}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
+ return (
+ <AvatarPrimitive.Fallback
+ data-slot="avatar-fallback"
+ className={cn(
+ 'bg-muted flex size-full items-center justify-center rounded-full',
+ className,
+ )}
+ {...props}
+ />
+ )
+}
+
+const AvatarComponent: React.FC<AvatarComponentProps> = ({
+ src,
+ alt,
+ fallback,
+ className,
+}) => {
+ return (
+ <Avatar className={className}>
+ <AvatarImage src={src} alt={alt} />
+ <AvatarFallback>{fallback}</AvatarFallback>
+ </Avatar>
+ )
+}
+
+export default AvatarComponent
+
+interface AvatarComponentProps {
+ src?: string
+ alt?: string
+ fallback: string
+ className?: string
+}
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx
new file mode 100644
index 0000000..84f36c6
--- /dev/null
+++ b/src/components/ui/badge.tsx
@@ -0,0 +1,46 @@
+import * as React from 'react'
+import { Slot as SlotPrimitive } from 'radix-ui'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+
+const badgeVariants = cva(
+ 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
+ {
+ variants: {
+ variant: {
+ default:
+ 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
+ muted:
+ 'border-transparent bg-muted text-foreground [a&]:hover:bg-muted/90',
+ destructive:
+ 'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70',
+ outline:
+ 'text-foreground [a&]:hover:bg-muted [a&]:hover:text-foreground',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ },
+ },
+)
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'span'> &
+ VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
+ const Comp = asChild ? SlotPrimitive.Root : 'span'
+
+ return (
+ <Comp
+ data-slot="badge"
+ className={cn(badgeVariants({ variant }), className)}
+ {...props}
+ />
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/src/components/ui/breadcrumb.tsx b/src/components/ui/breadcrumb.tsx
new file mode 100644
index 0000000..a639b0c
--- /dev/null
+++ b/src/components/ui/breadcrumb.tsx
@@ -0,0 +1,108 @@
+import * as React from 'react'
+import { Slot as SlotPrimitive } from 'radix-ui'
+import { cn } from '@/lib/utils'
+import { ChevronRight, MoreHorizontal } from 'lucide-react'
+
+function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) {
+ return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
+}
+
+function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
+ return (
+ <ol
+ data-slot="breadcrumb-list"
+ className={cn(
+ 'text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5',
+ className,
+ )}
+ {...props}
+ />
+ )
+}
+
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {
+ return (
+ <li
+ data-slot="breadcrumb-item"
+ className={cn('inline-flex items-center gap-1.5', className)}
+ {...props}
+ />
+ )
+}
+
+function BreadcrumbLink({
+ asChild,
+ className,
+ ...props
+}: React.ComponentProps<'a'> & {
+ asChild?: boolean
+}) {
+ const Comp = asChild ? SlotPrimitive.Root : 'a'
+
+ return (
+ <Comp
+ data-slot="breadcrumb-link"
+ className={cn('hover:text-foreground transition-colors', className)}
+ {...props}
+ />
+ )
+}
+
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
+ return (
+ <span
+ data-slot="breadcrumb-page"
+ role="link"
+ aria-disabled="true"
+ aria-current="page"
+ className={cn('text-foreground font-normal', className)}
+ {...props}
+ />
+ )
+}
+
+function BreadcrumbSeparator({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<'li'>) {
+ return (
+ <li
+ data-slot="breadcrumb-separator"
+ role="presentation"
+ aria-hidden="true"
+ className={cn('[&>svg]:size-3.5', className)}
+ {...props}
+ >
+ {children ?? <ChevronRight />}
+ </li>
+ )
+}
+
+function BreadcrumbEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<'span'>) {
+ return (
+ <span
+ data-slot="breadcrumb-ellipsis"
+ role="presentation"
+ aria-hidden="true"
+ className={cn('flex size-9 items-center justify-center', className)}
+ {...props}
+ >
+ <MoreHorizontal className="size-4" />
+ <span className="sr-only">More</span>
+ </span>
+ )
+}
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis,
+}
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000..00c0502
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,56 @@
+import * as React from 'react'
+import { Slot as SlotPrimitive } from 'radix-ui'
+import { cva, type VariantProps } from 'class-variance-authority'
+
+import { cn } from '@/lib/utils'
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
+ destructive:
+ 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
+ outline:
+ 'border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
+ muted: 'bg-muted text-foreground hover:bg-muted/80',
+ ghost: 'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50',
+ link: 'text-primary underline-offset-4 hover:underline',
+ },
+ size: {
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
+ sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
+ icon: 'size-9',
+ },
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default',
+ },
+ },
+)
+
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'button'> &
+ VariantProps<typeof buttonVariants> & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? SlotPrimitive.Root : 'button'
+
+ return (
+ <Comp
+ data-slot="button"
+ className={cn(buttonVariants({ variant, size, className }))}
+ {...props}
+ />
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx
new file mode 100644
index 0000000..ad0d40c
--- /dev/null
+++ b/src/components/ui/pagination.tsx
@@ -0,0 +1,195 @@
+import * as React from 'react'
+import {
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ MoreHorizontalIcon,
+} from 'lucide-react'
+
+import { cn } from '@/lib/utils'
+import { Button, buttonVariants } from '@/components/ui/button'
+
+function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
+ return (
+ <nav
+ role="navigation"
+ aria-label="pagination"
+ data-slot="pagination"
+ className={cn('mx-auto flex w-full justify-center', className)}
+ {...props}
+ />
+ )
+}
+
+function PaginationContent({
+ className,
+ ...props
+}: React.ComponentProps<'ul'>) {
+ return (
+ <ul
+ data-slot="pagination-content"
+ className={cn('flex flex-row items-center gap-1', className)}
+ {...props}
+ />
+ )
+}
+
+function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
+ return <li data-slot="pagination-item" {...props} />
+}
+
+type PaginationLinkProps = {
+ isActive?: boolean
+ isDisabled?: boolean
+} & Pick<React.ComponentProps<typeof Button>, 'size'> &
+ React.ComponentProps<'a'>
+
+function PaginationLink({
+ className,
+ isActive,
+ isDisabled,
+ size = 'icon',
+ ...props
+}: PaginationLinkProps) {
+ return (
+ <a
+ aria-current={isActive ? 'page' : undefined}
+ data-slot="pagination-link"
+ data-active={isActive}
+ data-disabled={isDisabled}
+ className={cn(
+ buttonVariants({
+ variant: isActive ? 'outline' : 'ghost',
+ size,
+ }),
+ isDisabled && 'pointer-events-none opacity-50',
+ className,
+ )}
+ {...props}
+ />
+ )
+}
+
+function PaginationPrevious({
+ className,
+ isDisabled,
+ ...props
+}: React.ComponentProps<typeof PaginationLink>) {
+ return (
+ <PaginationLink
+ aria-label="Go to previous page"
+ size="default"
+ className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
+ isDisabled={isDisabled}
+ {...props}
+ >
+ <ChevronLeftIcon />
+ <span className="hidden sm:block">Previous</span>
+ </PaginationLink>
+ )
+}
+
+function PaginationNext({
+ className,
+ isDisabled,
+ ...props
+}: React.ComponentProps<typeof PaginationLink>) {
+ return (
+ <PaginationLink
+ aria-label="Go to next page"
+ size="default"
+ className={cn('gap-1 px-2.5 sm:pr-2.5', className)}
+ isDisabled={isDisabled}
+ {...props}
+ >
+ <span className="hidden sm:block">Next</span>
+ <ChevronRightIcon />
+ </PaginationLink>
+ )
+}
+
+function PaginationEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<'span'>) {
+ return (
+ <span
+ aria-hidden
+ data-slot="pagination-ellipsis"
+ className={cn('flex size-9 items-center justify-center', className)}
+ {...props}
+ >
+ <MoreHorizontalIcon className="size-4" />
+ <span className="sr-only">More pages</span>
+ </span>
+ )
+}
+
+const PaginationComponent: React.FC<PaginationProps> = ({
+ currentPage,
+ totalPages,
+ baseUrl,
+}) => {
+ const pages = Array.from({ length: totalPages }, (_, i) => i + 1)
+
+ const getPageUrl = (page: number) => {
+ if (page === 1) return baseUrl
+ return `${baseUrl}${page}`
+ }
+
+ return (
+ <Pagination>
+ <PaginationContent className="flex-wrap">
+ <PaginationItem>
+ <PaginationPrevious
+ href={currentPage > 1 ? getPageUrl(currentPage - 1) : undefined}
+ isDisabled={currentPage === 1}
+ />
+ </PaginationItem>
+
+ {pages.map((page) => (
+ <PaginationItem key={page}>
+ <PaginationLink
+ href={getPageUrl(page)}
+ isActive={page === currentPage}
+ >
+ {page}
+ </PaginationLink>
+ </PaginationItem>
+ ))}
+
+ {totalPages > 5 && (
+ <PaginationItem>
+ <PaginationEllipsis />
+ </PaginationItem>
+ )}
+
+ <PaginationItem>
+ <PaginationNext
+ href={
+ currentPage < totalPages ? getPageUrl(currentPage + 1) : undefined
+ }
+ isDisabled={currentPage === totalPages}
+ />
+ </PaginationItem>
+ </PaginationContent>
+ </Pagination>
+ )
+}
+
+interface PaginationProps {
+ currentPage: number
+ totalPages: number
+ baseUrl: string
+}
+
+export default PaginationComponent
+
+export {
+ Pagination,
+ PaginationContent,
+ PaginationLink,
+ PaginationItem,
+ PaginationPrevious,
+ PaginationNext,
+ PaginationEllipsis,
+}
diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx
new file mode 100644
index 0000000..9e28547
--- /dev/null
+++ b/src/components/ui/scroll-area.tsx
@@ -0,0 +1,56 @@
+import * as React from 'react'
+import { ScrollArea as ScrollAreaPrimitive } from 'radix-ui'
+
+import { cn } from '@/lib/utils'
+
+function ScrollArea({
+ className,
+ children,
+ ...props
+}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
+ return (
+ <ScrollAreaPrimitive.Root
+ data-slot="scroll-area"
+ className={cn('relative', className)}
+ {...props}
+ >
+ <ScrollAreaPrimitive.Viewport
+ data-slot="scroll-area-viewport"
+ className="ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] focus-visible:ring-4 focus-visible:outline-1"
+ >
+ {children}
+ </ScrollAreaPrimitive.Viewport>
+ <ScrollBar />
+ <ScrollAreaPrimitive.Corner />
+ </ScrollAreaPrimitive.Root>
+ )
+}
+
+function ScrollBar({
+ className,
+ orientation = 'vertical',
+ ...props
+}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
+ return (
+ <ScrollAreaPrimitive.ScrollAreaScrollbar
+ data-slot="scroll-area-scrollbar"
+ orientation={orientation}
+ className={cn(
+ 'flex touch-none p-px transition-colors select-none',
+ orientation === 'vertical' &&
+ 'h-full w-2.5 border-l border-l-transparent',
+ orientation === 'horizontal' &&
+ 'h-2.5 flex-col border-t border-t-transparent',
+ className,
+ )}
+ {...props}
+ >
+ <ScrollAreaPrimitive.ScrollAreaThumb
+ data-slot="scroll-area-thumb"
+ className="bg-border relative flex-1 rounded-full"
+ />
+ </ScrollAreaPrimitive.ScrollAreaScrollbar>
+ )
+}
+
+export { ScrollArea, ScrollBar }
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
new file mode 100644
index 0000000..8624369
--- /dev/null
+++ b/src/components/ui/separator.tsx
@@ -0,0 +1,26 @@
+import * as React from 'react'
+import { Separator as SeparatorPrimitive } from 'radix-ui'
+
+import { cn } from '@/lib/utils'
+
+function Separator({
+ className,
+ orientation = 'horizontal',
+ decorative = true,
+ ...props
+}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
+ return (
+ <SeparatorPrimitive.Root
+ data-slot="separator-root"
+ decorative={decorative}
+ orientation={orientation}
+ className={cn(
+ 'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
+ className,
+ )}
+ {...props}
+ />
+ )
+}
+
+export { Separator }