Files
sigpro-ui/docs/components/list.md
2026-04-06 03:19:15 +02:00

12 KiB
Raw Permalink Blame History

List

List component with custom item rendering, headers, and reactive data binding.

Tag

List

Props

Prop Type Default Description
items Array | Signal<Array> [] Data array to display
header string | VNode | Signal - Optional header content
render function(item, index) Required Custom render function for each item
keyFn function(item, index) (item, idx) => idx Unique key function for items
class string '' Additional CSS classes (DaisyUI + Tailwind)

Styling

List supports all daisyUI List classes:

Category Keywords Description
Base list Base list styling
Variant list-row Row styling for list items
Background bg-base-100 Background color

For further details, check the daisyUI List Documentation Full reference for CSS classes.

Live Examples

Basic List

Live Demo

const BasicDemo = () => {
  const items = ["Apple", "Banana", "Orange", "Grape", "Mango"];

  return List({
    items: items,
    render: (item) =>
      Div({ class: "p-3 hover:bg-base-200 transition-colors" }, [
        Span({ class: "font-medium" }, item),
      ]),
  });
};
Mount(BasicDemo, "#demo-basic");

With Header

Live Demo

const HeaderDemo = () => {
  const users = [
    { name: "John Doe", email: "john@example.com", status: "active" },
    { name: "Jane Smith", email: "jane@example.com", status: "inactive" },
    { name: "Bob Johnson", email: "bob@example.com", status: "active" },
  ];

  return List({
    items: users,
    header: Div(
      { class: "p-3 bg-primary/10 font-bold border-b border-base-300" },
      "Active Users",
    ),
    render: (user) =>
      Div({ class: "p-3 border-b border-base-300 hover:bg-base-200" }, [
        Div({ class: "font-medium" }, user.name),
        Div({ class: "text-sm opacity-70" }, user.email),
        Span(
          {
            class: `badge badge-sm ${user.status === "active" ? "badge-success" : "badge-ghost"} mt-1`,
          },
          user.status,
        ),
      ]),
  });
};
Mount(HeaderDemo, "#demo-header");

With Icons

Live Demo

const IconsDemo = () => {
  const settings = [
    {
      icon: "🔊",
      label: "Sound",
      description: "Adjust volume and notifications",
    },
    { icon: "🌙", label: "Display", description: "Brightness and dark mode" },
    { icon: "🔒", label: "Privacy", description: "Security settings" },
    { icon: "🌐", label: "Network", description: "WiFi and connections" },
  ];

  return List({
    items: settings,
    render: (item) =>
      Div(
        {
          class:
            "flex gap-3 p-3 hover:bg-base-200 transition-colors cursor-pointer",
        },
        [
          Div({ class: "text-2xl" }, item.icon),
          Div({ class: "flex-1" }, [
            Div({ class: "font-medium" }, item.label),
            Div({ class: "text-sm opacity-60" }, item.description),
          ]),
          Span({ class: "opacity-40" }, "→"),
        ],
      ),
  });
};
Mount(IconsDemo, "#demo-icons");

With Badges

Live Demo

const BadgesDemo = () => {
  const notifications = [
    {
      id: 1,
      message: "New comment on your post",
      time: "5 min ago",
      unread: true,
    },
    {
      id: 2,
      message: "Your order has been shipped",
      time: "1 hour ago",
      unread: true,
    },
    {
      id: 3,
      message: "Welcome to the platform!",
      time: "2 days ago",
      unread: false,
    },
    {
      id: 4,
      message: "Weekly digest available",
      time: "3 days ago",
      unread: false,
    },
  ];

  return List({
    items: notifications,
    render: (item) =>
      Div(
        {
          class: `flex justify-between items-center p-3 border-b border-base-300 hover:bg-base-200 ${item.unread ? "bg-primary/5" : ""}`,
        },
        [
          Div({ class: "flex-1" }, [
            Div({ class: "font-medium" }, item.message),
            Div({ class: "text-xs opacity-50" }, item.time),
          ]),
          item.unread
            ? Span({ class: "badge badge-primary badge-sm" }, "New")
            : null,
        ],
      ),
  });
};
Mount(BadgesDemo, "#demo-badges");

Interactive List

Live Demo

const InteractiveDemo = () => {
  const selected = $(null);
  const items = [
    { id: 1, name: "Project Alpha", status: "In Progress" },
    { id: 2, name: "Project Beta", status: "Planning" },
    { id: 3, name: "Project Gamma", status: "Completed" },
    { id: 4, name: "Project Delta", status: "Review" },
  ];

  const statusColors = {
    "In Progress": "badge-warning",
    Planning: "badge-info",
    Completed: "badge-success",
    Review: "badge-accent",
  };

  return Div({ class: "flex flex-col gap-4" }, [
    List({
      items: items,
      render: (item) =>
        Div(
          {
            class: `p-3 cursor-pointer transition-all hover:bg-base-200 ${selected() === item.id ? "bg-primary/10 border-l-4 border-primary" : "border-l-4 border-transparent"}`,
            onclick: () => selected(item.id),
          },
          [
            Div({ class: "flex justify-between items-center" }, [
              Div({ class: "font-medium" }, item.name),
              Span(
                { class: `badge ${statusColors[item.status]}` },
                item.status,
              ),
            ]),
          ],
        ),
    }),
    () =>
      selected()
        ? Div(
            { class: "alert alert-info" },
            `Selected: ${items.find((i) => i.id === selected()).name}`,
          )
        : Div({ class: "alert alert-soft" }, "Select a project to see details"),
  ]);
};
Mount(InteractiveDemo, "#demo-interactive");

Reactive List (Todo App)

Live Demo

const ReactiveDemo = () => {
  const todos = $([
    { id: 1, text: 'Complete documentation', done: false },
    { id: 2, text: 'Review pull requests', done: false },
    { id: 3, text: 'Deploy to production', done: false }
  ]);
  
  const newTodo = $('');
  
  const addTodo = () => {
    if (newTodo().trim()) {
      const newId = Math.max(...todos().map(t => t.id), 0) + 1;
      todos([...todos(), { id: newId, text: newTodo(), done: false }]);
      newTodo('');
    }
  };
  
  const toggleTodo = (id) => {
    todos(todos().map(t => t.id === id ? { ...t, done: !t.done } : t));
  };
  
  const deleteTodo = (id) => {
    todos(todos().filter(t => t.id !== id));
  };
  
  const pendingCount = () => todos().filter(t => !t.done).length;
  Watch(()=> console.log(pendingCount()));
  return Div({ class: 'flex flex-col gap-4' }, [
    Div({ class: 'flex gap-2' }, [
      Input({
        placeholder: 'Add new task...',
        value: newTodo,
        oninput: (e) => newTodo(e.target.value),
        onkeypress: (e) => e.key === 'Enter' && addTodo()
      }),
      Button({ class: 'btn btn-primary', onclick: addTodo }, 'Add')
    ]),
    List({
      items: todos,
      render: (item) => {
        // Esta función busca siempre el estado actual del item dentro del signal
        const it = () => todos().find(t => t.id === item.id) || item;

        return Div({ 
          class: () => `flex items-center gap-3 p-2 border-b border-base-300 ${it().done ? 'opacity-60' : ''}` 
        }, [
          Checkbox({
            value: () => it().done,
            onclick: () => toggleTodo(item.id)
          }),
          Span({ 
            class: () => `flex-1 ${it().done ? 'line-through' : ''}`,
            onclick: () => toggleTodo(item.id)
          }, () => it().text),
          Button({ 
            class: 'btn btn-ghost btn-xs btn-circle',
            onclick: () => deleteTodo(item.id)
          }, '✕')
        ]);
      }
    }),
    Div({ class: 'text-sm opacity-70 mt-2' }, () => `${pendingCount()} tasks remaining`)
  ]);
};

Mount(ReactiveDemo, '#demo-reactive');

Avatar List

Live Demo

const AvatarDemo = () => {
  const contacts = [
    { name: "Alice Johnson", role: "Developer", avatar: "A", online: true },
    { name: "Bob Smith", role: "Designer", avatar: "B", online: false },
    { name: "Charlie Brown", role: "Manager", avatar: "C", online: true },
    { name: "Diana Prince", role: "QA Engineer", avatar: "D", online: false },
  ];

  return List({
    items: contacts,
    render: (contact) =>
      Div({ class: "flex gap-3 p-3 hover:bg-base-200 transition-colors" }, [
        Div(
          {
            class: `avatar ${contact.online ? "online" : "offline"}`,
            style: "width: 48px",
          },
          [
            Div(
              {
                class:
                  "rounded-full bg-primary text-primary-content flex items-center justify-center w-12 h-12 font-bold",
              },
              contact.avatar,
            ),
          ],
        ),
        Div({ class: "flex-1" }, [
          Div({ class: "font-medium" }, contact.name),
          Div({ class: "text-sm opacity-60" }, contact.role),
        ]),
        Div(
          {
            class: `badge badge-sm ${contact.online ? "badge-success" : "badge-ghost"}`,
          },
          contact.online ? "Online" : "Offline",
        ),
      ]),
  });
};
Mount(AvatarDemo, "#demo-avatar");

All Variants

Live Demo

const VariantsDemo = () => {
  const items = ["Item 1", "Item 2", "Item 3"];

  return Div({ class: "flex flex-col gap-6" }, [
    Div({ class: "text-sm font-bold" }, "Default List"),
    List({
      items: items,
      render: (item) => Div({ class: "p-2" }, item),
    }),

    Div({ class: "text-sm font-bold mt-2" }, "With Shadow"),
    List({
      items: items,
      render: (item) => Div({ class: "p-2" }, item),
      class: "shadow-lg",
    }),

    Div({ class: "text-sm font-bold mt-2" }, "Rounded Corners"),
    List({
      items: items,
      render: (item) => Div({ class: "p-2" }, item),
      class: "rounded-box overflow-hidden",
    }),
  ]);
};
Mount(VariantsDemo, "#demo-variants");