shadimage

Card

Hyper-rounded superellipse card with pastel surface options.

components/ui/card.tsx

import * as React from 'react'
import { motion, type HTMLMotionProps } from 'motion/react'
import { cn } from '@/lib/utils'

const cardVariants = {
  white: 'bg-rk-surface text-rk-ink',
  mint: 'bg-rk-mint text-rk-ink',
  peach: 'bg-rk-peach text-rk-ink',
  lavender: 'bg-rk-lavender text-rk-ink',
  sky: 'bg-rk-sky text-rk-ink',
  pink: 'bg-rk-pink text-rk-ink',
  yellow: 'bg-rk-primary text-rk-ink',
  orange: 'bg-rk-secondary text-rk-ink',
  dark: 'bg-rk-ink text-rk-canvas',
}

const shadowColorMap = {
  ink: 'rk-shadow',
  yellow: 'rk-shadow',
  orange: 'rk-shadow',
  violet: 'rk-shadow',
  mint: 'rk-shadow',
  pink: 'rk-shadow',
  sky: 'rk-shadow',
}

const shadowColorHexMap: Record<keyof typeof shadowColorMap, string> = {
  ink: '#18181b',
  yellow: '#FACC15',
  orange: '#FB923C',
  violet: '#A78BFA',
  mint: '#4ADE80',
  pink: '#F472B6',
  sky: '#38BDF8',
}

export interface CardProps
  extends Omit<React.HTMLAttributes<HTMLDivElement>, keyof HTMLMotionProps<'div'>>,
    Omit<HTMLMotionProps<'div'>, 'children'> {
  children?: React.ReactNode
  variant?: keyof typeof cardVariants
  hasShadow?: boolean
  isInteractive?: boolean
  shadowColor?: keyof typeof shadowColorMap
  shadowStyle?: 'hard' | 'soft' | 'none'
  badge?: React.ReactNode
}

const Card = React.forwardRef<HTMLDivElement, CardProps>(
  (
    {
      className,
      variant = 'white',
      hasShadow = true,
      isInteractive = false,
      shadowColor = 'ink',
      shadowStyle = 'hard',
      badge,
      children,
      ...props
    },
    ref
  ) => {
    const shadowClass =
      !hasShadow || shadowStyle === 'none'
        ? 'rk-shadow-none'
        : shadowStyle === 'soft'
        ? 'rk-shadow-soft'
        : 'rk-shadow'

    const shadowVar =
      shadowStyle === 'hard' && shadowColor !== 'ink'
        ? ({ '--rk-shadow-color': shadowColorHexMap[shadowColor] } as React.CSSProperties)
        : undefined

    return (
      <motion.div
        ref={ref}
        whileHover={
          isInteractive && shadowStyle !== 'none'
            ? {
                y: -4,
              }
            : undefined
        }
        transition={{ type: 'spring', stiffness: 400, damping: 25 }}
        style={shadowVar}
        className={cn(
          'rounded-[28px] rk-border p-6 sm:p-7 font-sans relative overflow-hidden transition-colors',
          cardVariants[variant],
          shadowClass,
          isInteractive && 'cursor-pointer',
          className
        )}
        {...props}
      >
        {badge && (
          <div className="absolute top-5 right-5 z-10">
            {typeof badge === 'string' ? (
              <span className="inline-flex items-center rounded-full border-2 border-rk-ink bg-rk-primary px-3 py-1 font-mono text-xs font-black text-rk-ink rk-shadow-xs">
                {badge}
              </span>
            ) : (
              badge
            )}
          </div>
        )}
        {children}
      </motion.div>
    )
  }
)
Card.displayName = 'Card'

const CardHeader = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn('flex flex-col space-y-1.5 mb-4 pr-14 sm:pr-16', className)}
    {...props}
  />
))
CardHeader.displayName = 'CardHeader'

const CardTitle = React.forwardRef<
  HTMLParagraphElement,
  React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
  <h3
    ref={ref}
    className={cn('font-display text-xl sm:text-2xl font-black tracking-tight text-inherit', className)}
    {...props}
  />
))
CardTitle.displayName = 'CardTitle'

const CardDescription = React.forwardRef<
  HTMLParagraphElement,
  React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
  <p
    ref={ref}
    className={cn('text-xs sm:text-sm font-bold opacity-80', className)}
    {...props}
  />
))
CardDescription.displayName = 'CardDescription'

const CardContent = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div ref={ref} className={cn('pt-0 space-y-3', className)} {...props} />
))
CardContent.displayName = 'CardContent'

const CardFooter = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn('flex items-center justify-between pt-4 mt-5 border-t-2 border-current/15', className)}
    {...props}
  />
))
CardFooter.displayName = 'CardFooter'

export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter }
Visual style
pastel

DependenciesBundle weight · light

clsxtailwind-merge