diff options
| author | aman333nolawz <amananudhiu@gmail.com> | 2026-03-02 21:49:13 +0530 |
|---|---|---|
| committer | aman333nolawz <amananudhiu@gmail.com> | 2026-03-02 21:49:13 +0530 |
| commit | d41eff9eccf66644c4c60f53cb516bf079937a28 (patch) | |
| tree | 248a0863a3181369f943d678ec4273911d35efac /src/components/ui | |
Using astro-erudite for static site generation
Diffstat (limited to 'src/components/ui')
| -rw-r--r-- | src/components/ui/avatar.tsx | 74 | ||||
| -rw-r--r-- | src/components/ui/badge.tsx | 46 | ||||
| -rw-r--r-- | src/components/ui/breadcrumb.tsx | 108 | ||||
| -rw-r--r-- | src/components/ui/button.tsx | 56 | ||||
| -rw-r--r-- | src/components/ui/pagination.tsx | 195 | ||||
| -rw-r--r-- | src/components/ui/scroll-area.tsx | 56 | ||||
| -rw-r--r-- | src/components/ui/separator.tsx | 26 |
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 } |
