Files
sigpro-ui/docs/components/list.md
2026-03-31 23:41:51 +02:00

22 KiB

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) - 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)

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

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;
  
  return Div({ class: 'flex flex-col gap-4' }, [
    Div({ class: 'flex gap-2' }, [
      Input({
        placeholder: 'Add new task...',
        value: newTodo,
        class: 'flex-1',
        oninput: (e) => newTodo(e.target.value),
        onkeypress: (e) => e.key === 'Enter' && addTodo()
      }),
      Button({ class: 'btn btn-primary', onclick: addTodo }, 'Add')
    ]),
    List({
      items: todos,
      render: (todo) => Div({ class: `flex items-center gap-3 p-2 border-b border-base-300 hover:bg-base-200 ${todo.done ? 'opacity-60' : ''}` }, [
        Checkbox({
          value: todo.done,
          onclick: () => toggleTodo(todo.id)
        }),
        Span({ 
          class: `flex-1 ${todo.done ? 'line-through' : ''}`,
          onclick: () => toggleTodo(todo.id)
        }, todo.text),
        Button({ 
          class: 'btn btn-ghost btn-xs btn-circle',
          onclick: () => deleteTodo(todo.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');
<script> (function() { const initListExamples = () => { // 1. Basic List const basicTarget = document.querySelector('#demo-basic'); if (basicTarget && !basicTarget.hasChildNodes()) { 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, basicTarget); } // 2. With Header const headerTarget = document.querySelector('#demo-header'); if (headerTarget && !headerTarget.hasChildNodes()) { 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, headerTarget); } // 3. With Icons const iconsTarget = document.querySelector('#demo-icons'); if (iconsTarget && !iconsTarget.hasChildNodes()) { 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, iconsTarget); } // 4. With Badges const badgesTarget = document.querySelector('#demo-badges'); if (badgesTarget && !badgesTarget.hasChildNodes()) { 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, badgesTarget); } // 5. Interactive List const interactiveTarget = document.querySelector('#demo-interactive'); if (interactiveTarget && !interactiveTarget.hasChildNodes()) { 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, interactiveTarget); } // 6. Reactive List const reactiveTarget = document.querySelector('#demo-reactive'); if (reactiveTarget && !reactiveTarget.hasChildNodes()) { 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; return Div({ class: 'flex flex-col gap-4' }, [ Div({ class: 'flex gap-2' }, [ Input({ placeholder: 'Add new task...', value: newTodo, class: 'flex-1', oninput: (e) => newTodo(e.target.value), onkeypress: (e) => e.key === 'Enter' && addTodo() }), Button({ class: 'btn btn-primary', onclick: addTodo }, 'Add') ]), List({ items: todos, render: (todo) => Div({ class: `flex items-center gap-3 p-2 border-b border-base-300 hover:bg-base-200 ${todo.done ? 'opacity-60' : ''}` }, [ Checkbox({ value: todo.done, onclick: () => toggleTodo(todo.id) }), Span({ class: `flex-1 ${todo.done ? 'line-through' : ''}`, onclick: () => toggleTodo(todo.id) }, todo.text), Button({ class: 'btn btn-ghost btn-xs btn-circle', onclick: () => deleteTodo(todo.id) }, '✕') ]) }), Div({ class: 'text-sm opacity-70 mt-2' }, () => `${pendingCount()} tasks remaining`) ]); }; $mount(ReactiveDemo, reactiveTarget); } // 7. Avatar List const avatarTarget = document.querySelector('#demo-avatar'); if (avatarTarget && !avatarTarget.hasChildNodes()) { 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, avatarTarget); } // 8. All Variants const variantsTarget = document.querySelector('#demo-variants'); if (variantsTarget && !variantsTarget.hasChildNodes()) { 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, variantsTarget); } }; initListExamples(); if (window.$docsify) { window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => { hook.doneEach(initListExamples); }); } })(); </script>