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

717 lines
22 KiB
Markdown

# Table
Data table component with sorting, pagination, zebra stripes, pin rows, and custom cell rendering.
## Tag
`Table`
## Props
| Prop | Type | Default | Description |
| :----------- | :-------------------------------------- | :--------------- | :----------------------------------------------- |
| `items` | `Array \| Signal<Array>` | `[]` | Data array to display |
| `columns` | `Array<{label: string, key?: string, render?: function, class?: string, footer?: string}>` | `[]` | Column definitions |
| `keyFn` | `function` | `(item, idx) => idx` | Unique key function for rows |
| `zebra` | `boolean \| Signal<boolean>` | `false` | Enable zebra striping |
| `pinRows` | `boolean \| Signal<boolean>` | `false` | Pin header rows on scroll |
| `empty` | `string \| VNode` | `'No data'` | Content to show when no data |
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
## Live Examples
### Basic Table
<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 users = [
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', role: 'Editor' }
];
return Table({
items: users,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Email', key: 'email' },
{ label: 'Role', key: 'role' }
]
});
};
Mount(BasicDemo, '#demo-basic');
```
### With Zebra Stripes
<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-zebra" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ZebraDemo = () => {
const products = [
{ id: 1, name: 'Laptop', price: '$999', stock: 15 },
{ id: 2, name: 'Mouse', price: '$29', stock: 42 },
{ id: 3, name: 'Keyboard', price: '$79', stock: 28 },
{ id: 4, name: 'Monitor', price: '$299', stock: 12 }
];
return Table({
items: products,
columns: [
{ label: 'Product', key: 'name' },
{ label: 'Price', key: 'price' },
{ label: 'Stock', key: 'stock' }
],
zebra: true
});
};
Mount(ZebraDemo, '#demo-zebra');
```
### With Custom Cell Rendering
<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-custom" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const CustomDemo = () => {
const orders = [
{ id: 101, customer: 'Alice', amount: 250, status: 'completed' },
{ id: 102, customer: 'Bob', amount: 89, status: 'pending' },
{ id: 103, customer: 'Charlie', amount: 450, status: 'shipped' }
];
return Table({
items: orders,
columns: [
{ label: 'Order ID', key: 'id' },
{ label: 'Customer', key: 'customer' },
{
label: 'Amount',
key: 'amount',
render: (item) => `$${item.amount}`
},
{
label: 'Status',
key: 'status',
render: (item) => {
const statusClass = {
completed: 'badge badge-success',
pending: 'badge badge-warning',
shipped: 'badge badge-info'
};
return Span({ class: statusClass[item.status] }, item.status);
}
}
],
zebra: true
});
};
Mount(CustomDemo, '#demo-custom');
```
### With Footers
<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-footer" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const FooterDemo = () => {
const sales = [
{ month: 'January', revenue: 12500, expenses: 8900 },
{ month: 'February', revenue: 14200, expenses: 9200 },
{ month: 'March', revenue: 16800, expenses: 10100 }
];
const totalRevenue = sales.reduce((sum, item) => sum + item.revenue, 0);
const totalExpenses = sales.reduce((sum, item) => sum + item.expenses, 0);
return Table({
items: sales,
columns: [
{ label: 'Month', key: 'month' },
{
label: 'Revenue',
key: 'revenue',
render: (item) => `$${item.revenue.toLocaleString()}`,
footer: `Total: $${totalRevenue.toLocaleString()}`
},
{
label: 'Expenses',
key: 'expenses',
render: (item) => `$${item.expenses.toLocaleString()}`,
footer: `Total: $${totalExpenses.toLocaleString()}`
},
{
label: 'Profit',
render: (item) => `$${(item.revenue - item.expenses).toLocaleString()}`,
footer: `$${(totalRevenue - totalExpenses).toLocaleString()}`
}
],
zebra: true
});
};
Mount(FooterDemo, '#demo-footer');
```
### Empty State
<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-empty" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const EmptyDemo = () => {
const emptyList = [];
return Table({
items: emptyList,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' }
],
empty: Div({ class: 'flex flex-col items-center gap-2' }, [
Icons.iconInfo,
Span({}, 'No records found')
])
});
};
Mount(EmptyDemo, '#demo-empty');
```
### Reactive Data
<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 filter = $('all');
const tasks = $([
{ id: 1, title: 'Complete documentation', completed: true },
{ id: 2, title: 'Review pull requests', completed: false },
{ id: 3, title: 'Deploy to production', completed: false },
{ id: 4, title: 'Update dependencies', completed: true }
]);
const filteredTasks = () => {
if (filter() === 'completed') {
return tasks().filter(t => t.completed);
} else if (filter() === 'pending') {
return tasks().filter(t => !t.completed);
}
return tasks();
};
const addTask = () => {
const newId = Math.max(...tasks().map(t => t.id), 0) + 1;
tasks([...tasks(), { id: newId, title: `Task ${newId}`, completed: false }]);
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex gap-2' }, [
Button({
class: 'btn btn-sm',
onclick: () => filter('all')
}, 'All'),
Button({
class: 'btn btn-sm',
onclick: () => filter('completed')
}, 'Completed'),
Button({
class: 'btn btn-sm',
onclick: () => filter('pending')
}, 'Pending'),
Button({
class: 'btn btn-sm btn-primary',
onclick: addTask
}, 'Add Task')
]),
Table({
items: filteredTasks,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Title', key: 'title' },
{
label: 'Status',
render: (item) => item.completed
? Span({ class: 'badge badge-success' }, '✓ Done')
: Span({ class: 'badge badge-warning' }, '○ Pending')
}
],
zebra: true
})
]);
};
Mount(ReactiveDemo, '#demo-reactive');
```
### With Actions
<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-actions" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
</div>
</div>
```javascript
const ActionsDemo = () => {
const users = $([
{ id: 1, name: 'John Doe', email: 'john@example.com', active: true },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', active: false },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', active: true }
]);
const deleteUser = (id) => {
users(users().filter(u => u.id !== id));
Toast('User deleted', 'alert-info', 2000);
};
const toggleActive = (id) => {
users(users().map(u =>
u.id === id ? { ...u, active: !u.active } : u
));
};
return Table({
items: users,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Email', key: 'email' },
{
label: 'Status',
render: (item) => item.active
? Span({ class: 'badge badge-success' }, 'Active')
: Span({ class: 'badge badge-ghost' }, 'Inactive')
},
{
label: 'Actions',
render: (item) => Div({ class: 'flex gap-1' }, [
Button({
class: 'btn btn-xs btn-ghost',
onclick: () => toggleActive(item.id)
}, item.active ? 'Deactivate' : 'Activate'),
Button({
class: 'btn btn-xs btn-error',
onclick: () => deleteUser(item.id)
}, 'Delete')
])
}
],
zebra: true
});
};
Mount(ActionsDemo, '#demo-actions');
```
### 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 data = [
{ id: 1, name: 'Item 1', value: 100 },
{ id: 2, name: 'Item 2', value: 200 },
{ id: 3, name: 'Item 3', value: 300 }
];
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'text-sm font-bold' }, 'Default Table'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
]
}),
Div({ class: 'text-sm font-bold mt-4' }, 'Zebra Stripes'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
],
zebra: true
}),
Div({ class: 'text-sm font-bold mt-4' }, 'Compact Table'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
],
class: 'table-compact'
})
]);
};
Mount(VariantsDemo, '#demo-variants');
```
<script>
(function() {
const initTableExamples = () => {
// 1. Basic Table
const basicTarget = document.querySelector('#demo-basic');
if (basicTarget && !basicTarget.hasChildNodes()) {
const BasicDemo = () => {
const users = [
{ id: 1, name: 'John Doe', email: 'john@example.com', role: 'Admin' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', role: 'Editor' }
];
return Table({
items: users,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Email', key: 'email' },
{ label: 'Role', key: 'role' }
]
});
};
Mount(BasicDemo, basicTarget);
}
// 2. With Zebra Stripes
const zebraTarget = document.querySelector('#demo-zebra');
if (zebraTarget && !zebraTarget.hasChildNodes()) {
const ZebraDemo = () => {
const products = [
{ id: 1, name: 'Laptop', price: '$999', stock: 15 },
{ id: 2, name: 'Mouse', price: '$29', stock: 42 },
{ id: 3, name: 'Keyboard', price: '$79', stock: 28 },
{ id: 4, name: 'Monitor', price: '$299', stock: 12 }
];
return Table({
items: products,
columns: [
{ label: 'Product', key: 'name' },
{ label: 'Price', key: 'price' },
{ label: 'Stock', key: 'stock' }
],
zebra: true
});
};
Mount(ZebraDemo, zebraTarget);
}
// 3. With Custom Cell Rendering
const customTarget = document.querySelector('#demo-custom');
if (customTarget && !customTarget.hasChildNodes()) {
const CustomDemo = () => {
const orders = [
{ id: 101, customer: 'Alice', amount: 250, status: 'completed' },
{ id: 102, customer: 'Bob', amount: 89, status: 'pending' },
{ id: 103, customer: 'Charlie', amount: 450, status: 'shipped' }
];
return Table({
items: orders,
columns: [
{ label: 'Order ID', key: 'id' },
{ label: 'Customer', key: 'customer' },
{
label: 'Amount',
key: 'amount',
render: (item) => `$${item.amount}`
},
{
label: 'Status',
key: 'status',
render: (item) => {
const statusClass = {
completed: 'badge badge-success',
pending: 'badge badge-warning',
shipped: 'badge badge-info'
};
return Span({ class: statusClass[item.status] }, item.status);
}
}
],
zebra: true
});
};
Mount(CustomDemo, customTarget);
}
// 4. With Footers
const footerTarget = document.querySelector('#demo-footer');
if (footerTarget && !footerTarget.hasChildNodes()) {
const FooterDemo = () => {
const sales = [
{ month: 'January', revenue: 12500, expenses: 8900 },
{ month: 'February', revenue: 14200, expenses: 9200 },
{ month: 'March', revenue: 16800, expenses: 10100 }
];
const totalRevenue = sales.reduce((sum, item) => sum + item.revenue, 0);
const totalExpenses = sales.reduce((sum, item) => sum + item.expenses, 0);
return Table({
items: sales,
columns: [
{ label: 'Month', key: 'month' },
{
label: 'Revenue',
key: 'revenue',
render: (item) => `$${item.revenue.toLocaleString()}`,
footer: `Total: $${totalRevenue.toLocaleString()}`
},
{
label: 'Expenses',
key: 'expenses',
render: (item) => `$${item.expenses.toLocaleString()}`,
footer: `Total: $${totalExpenses.toLocaleString()}`
},
{
label: 'Profit',
render: (item) => `$${(item.revenue - item.expenses).toLocaleString()}`,
footer: `$${(totalRevenue - totalExpenses).toLocaleString()}`
}
],
zebra: true
});
};
Mount(FooterDemo, footerTarget);
}
// 5. Empty State
const emptyTarget = document.querySelector('#demo-empty');
if (emptyTarget && !emptyTarget.hasChildNodes()) {
const EmptyDemo = () => {
const emptyList = [];
return Table({
items: emptyList,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' }
],
empty: Div({ class: 'flex flex-col items-center gap-2' }, [
Icons.iconInfo,
Span({}, 'No records found')
])
});
};
Mount(EmptyDemo, emptyTarget);
}
// 6. Reactive Data
const reactiveTarget = document.querySelector('#demo-reactive');
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
const ReactiveDemo = () => {
const filter = $('all');
const tasks = $([
{ id: 1, title: 'Complete documentation', completed: true },
{ id: 2, title: 'Review pull requests', completed: false },
{ id: 3, title: 'Deploy to production', completed: false },
{ id: 4, title: 'Update dependencies', completed: true }
]);
const filteredTasks = () => {
if (filter() === 'completed') {
return tasks().filter(t => t.completed);
} else if (filter() === 'pending') {
return tasks().filter(t => !t.completed);
}
return tasks();
};
const addTask = () => {
const newId = Math.max(...tasks().map(t => t.id), 0) + 1;
tasks([...tasks(), { id: newId, title: `Task ${newId}`, completed: false }]);
};
return Div({ class: 'flex flex-col gap-4' }, [
Div({ class: 'flex gap-2' }, [
Button({
class: 'btn btn-sm',
onclick: () => filter('all')
}, 'All'),
Button({
class: 'btn btn-sm',
onclick: () => filter('completed')
}, 'Completed'),
Button({
class: 'btn btn-sm',
onclick: () => filter('pending')
}, 'Pending'),
Button({
class: 'btn btn-sm btn-primary',
onclick: addTask
}, 'Add Task')
]),
Table({
items: filteredTasks,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Title', key: 'title' },
{
label: 'Status',
render: (item) => item.completed
? Span({ class: 'badge badge-success' }, '✓ Done')
: Span({ class: 'badge badge-warning' }, '○ Pending')
}
],
zebra: true
})
]);
};
Mount(ReactiveDemo, reactiveTarget);
}
// 7. With Actions
const actionsTarget = document.querySelector('#demo-actions');
if (actionsTarget && !actionsTarget.hasChildNodes()) {
const ActionsDemo = () => {
const users = $([
{ id: 1, name: 'John Doe', email: 'john@example.com', active: true },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com', active: false },
{ id: 3, name: 'Bob Johnson', email: 'bob@example.com', active: true }
]);
const deleteUser = (id) => {
users(users().filter(u => u.id !== id));
Toast('User deleted', 'alert-info', 2000);
};
const toggleActive = (id) => {
users(users().map(u =>
u.id === id ? { ...u, active: !u.active } : u
));
};
return Table({
items: users,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Email', key: 'email' },
{
label: 'Status',
render: (item) => item.active
? Span({ class: 'badge badge-success' }, 'Active')
: Span({ class: 'badge badge-ghost' }, 'Inactive')
},
{
label: 'Actions',
render: (item) => Div({ class: 'flex gap-1' }, [
Button({
class: 'btn btn-xs btn-ghost',
onclick: () => toggleActive(item.id)
}, item.active ? 'Deactivate' : 'Activate'),
Button({
class: 'btn btn-xs btn-error',
onclick: () => deleteUser(item.id)
}, 'Delete')
])
}
],
zebra: true
});
};
Mount(ActionsDemo, actionsTarget);
}
// 8. All Variants
const variantsTarget = document.querySelector('#demo-variants');
if (variantsTarget && !variantsTarget.hasChildNodes()) {
const VariantsDemo = () => {
const data = [
{ id: 1, name: 'Item 1', value: 100 },
{ id: 2, name: 'Item 2', value: 200 },
{ id: 3, name: 'Item 3', value: 300 }
];
return Div({ class: 'flex flex-col gap-6' }, [
Div({ class: 'text-sm font-bold' }, 'Default Table'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
]
}),
Div({ class: 'text-sm font-bold mt-4' }, 'Zebra Stripes'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
],
zebra: true
}),
Div({ class: 'text-sm font-bold mt-4' }, 'Compact Table'),
Table({
items: data,
columns: [
{ label: 'ID', key: 'id' },
{ label: 'Name', key: 'name' },
{ label: 'Value', key: 'value' }
],
class: 'table-compact'
})
]);
};
Mount(VariantsDemo, variantsTarget);
}
};
initTableExamples();
if (window.$docsify) {
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
hook.doneEach(initTableExamples);
});
}
})();
</script>