426 lines
12 KiB
Markdown
426 lines
12 KiB
Markdown
# 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](https://daisyui.com/components/list) – Full reference for CSS classes.
|
||
|
||
## Live Examples
|
||
|
||
### Basic List
|
||
|
||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||
<div id="demo-basic" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
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
|
||
|
||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||
<div id="demo-header" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
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
|
||
|
||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||
<div id="demo-icons" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
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
|
||
|
||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||
<div id="demo-badges" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
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
|
||
|
||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||
<div id="demo-interactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
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)
|
||
|
||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||
<div id="demo-reactive" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
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
|
||
|
||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||
<div id="demo-avatar" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
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
|
||
|
||
<div class="card bg-base-200 border border-base-300 shadow-sm my-6">
|
||
<div class="card-body">
|
||
<h3 class="card-title text-sm uppercase opacity-50 mb-4">Live Demo</h3>
|
||
<div id="demo-variants" class="bg-base-100 p-6 rounded-xl border border-base-300 flex flex-col gap-6"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
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");
|
||
```
|