shadimage
kapwa
componentlistsource: ok · hit

List

List component for displaying categorized items with optional action links. Perfect for service directories, document lists, and search results.

npx shadcn add "https://kapwa-two.vercel.app/r/list.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light

Registry dependencies

cardbutton

Files1 · source cached 10/14/2026, 1:27:28 PM

src/lib/kapwa/list/index.tsx
import { useState } from 'react';
import { Button } from '@kapwa/button';
import { Card, CardHeader, CardContent } from '@kapwa/card';
import { cn } from '@kapwa/utils';
import { ChevronDown, ExternalLink } from 'lucide-react';

type ListSectionItem = {
  id: number;
  title: string;
  category: string;
  description?: string;
};

type ListsSectionProps = {
  title: string;
  headerTitle: string;
  headerSubtitle?: string;
  externalLinkText?: string;
  listItems: ListSectionItem[];
};

const List = ({
  headerTitle,
  headerSubtitle,
  externalLinkText,
  listItems,
  title,
}: {
  title: string;
  headerTitle: string;
  headerSubtitle?: string;
  externalLinkText?: string;
  listItems: ListSectionItem[];
}) => {
  const [expandedId, setExpandedId] = useState<number | null>(null);

  const toggle = (id: number) => {
    setExpandedId(prev => (prev === id ? null : id));
  };

  return (
    <Card>
      <CardHeader>
        <h2 className='text-2xl font-semibold'>{headerTitle}</h2>
        {headerSubtitle && (
          <p className='text-[var(--color-kapwa-text-support)]'>
            {headerSubtitle}
          </p>
        )}
      </CardHeader>
      <CardContent>
        <div className='space-y-8'>
          <div>
            <h3 className='font-medium mb-4'>{title}</h3>
            <div className='space-y-4'>
              {listItems.map(item => {
                const isExpanded = expandedId === item.id;
                const descriptionId = `list-item-desc-${item.id}`;

                return (
                  <div
                    key={item.id}
                    className='rounded-lg border border-[var(--color-kapwa-border-weak)] bg-[var(--color-kapwa-bg-surface)] p-4 transition-colors hover:border-[var(--color-kapwa-border-brand)]'
                  >
                    {/* Title row: title + category badge + right-side action */}
                    <div className='flex items-start justify-between gap-4'>
                      <div className='min-w-0 flex-1'>
                        <h4 className='text-lg font-medium text-[var(--color-kapwa-text-strong)]'>
                          {item.title}
                        </h4>
                        <span className='mt-2 inline-block rounded-sm bg-[var(--color-kapwa-bg-gray-default)] px-2 py-1 text-xs font-medium text-[var(--color-kapwa-text-support)]'>
                          {item.category}
                        </span>
                      </div>
                      {externalLinkText && (
                        <Button variant='ghost' size='sm' className='flex-none'>
                          {externalLinkText}
                          <ExternalLink className='ml-2 h-4 w-4' />
                        </Button>
                      )}
                    </div>

                    {/* Collapsible description disclosure */}
                    {item.description && (
                      <div className='mt-3'>
                        <p
                          id={descriptionId}
                          className={cn(
                            'text-sm text-[var(--color-kapwa-text-support)]',
                            !isExpanded && 'line-clamp-3'
                          )}
                        >
                          {item.description}
                        </p>
                        <button
                          type='button'
                          aria-expanded={isExpanded}
                          aria-controls={descriptionId}
                          onClick={() => toggle(item.id)}
                          className='mt-2 inline-flex items-center gap-1 rounded text-sm font-medium text-[var(--color-kapwa-text-brand)] transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-kapwa-border-focus)] focus-visible:ring-offset-1'
                        >
                          {isExpanded ? 'Show less' : 'Show more'}
                          <ChevronDown
                            className={cn(
                              'h-4 w-4 transition-transform duration-150 ease-in-out',
                              isExpanded && 'rotate-180'
                            )}
                          />
                        </button>
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
          </div>
        </div>
      </CardContent>
    </Card>
  );
};

export { List };
export type { ListsSectionProps, ListSectionItem };

More from kapwa

Browse all components in this library →