553 lines
20 KiB
Markdown
553 lines
20 KiB
Markdown
# Timeline
|
|
|
|
Timeline component for displaying chronological events, steps, or progress with customizable icons and content.
|
|
|
|
## Tag
|
|
|
|
`Timeline`
|
|
|
|
## Props
|
|
|
|
| Prop | Type | Default | Description |
|
|
| :----------- | :-------------------------------------- | :--------------- | :----------------------------------------------- |
|
|
| `items` | `Array<TimelineItem> \| Signal` | `[]` | Timeline items to display |
|
|
| `vertical` | `boolean \| Signal<boolean>` | `true` | Vertical or horizontal orientation |
|
|
| `compact` | `boolean \| Signal<boolean>` | `false` | Compact mode with less padding |
|
|
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
|
|
|
|
### TimelineItem Structure
|
|
|
|
| Property | Type | Description |
|
|
| :---------- | :--------------------------- | :----------------------------------------------- |
|
|
| `title` | `string \| VNode \| Signal` | Event title or main text |
|
|
| `detail` | `string \| VNode \| Signal` | Additional details or description |
|
|
| `icon` | `string \| VNode` | Custom icon (overrides type) |
|
|
| `type` | `string` | Type: 'success', 'warning', 'error', 'info' |
|
|
| `completed` | `boolean` | Whether event is completed (affects connector) |
|
|
|
|
## Live Examples
|
|
|
|
### Basic Timeline
|
|
|
|
<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 events = [
|
|
{ title: 'Project Started', detail: 'Initial planning and setup', type: 'info', completed: true },
|
|
{ title: 'Design Phase', detail: 'UI/UX design completed', type: 'success', completed: true },
|
|
{ title: 'Development', detail: 'Core features implemented', type: 'warning', completed: false },
|
|
{ title: 'Testing', detail: 'Quality assurance', type: 'info', completed: false },
|
|
{ title: 'Launch', detail: 'Production deployment', type: 'success', completed: false }
|
|
];
|
|
|
|
return Timeline({ items: events });
|
|
};
|
|
Mount(BasicDemo, '#demo-basic');
|
|
```
|
|
|
|
### Horizontal Timeline
|
|
|
|
<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-horizontal" class="bg-base-100 p-6 rounded-xl border border-base-300 overflow-x-auto"></div>
|
|
</div>
|
|
</div>
|
|
|
|
```javascript
|
|
const HorizontalDemo = () => {
|
|
const steps = [
|
|
{ title: 'Step 1', detail: 'Requirements', type: 'success', completed: true },
|
|
{ title: 'Step 2', detail: 'Design', type: 'success', completed: true },
|
|
{ title: 'Step 3', detail: 'Development', type: 'warning', completed: false },
|
|
{ title: 'Step 4', detail: 'Testing', type: 'info', completed: false },
|
|
{ title: 'Step 5', detail: 'Deploy', type: 'info', completed: false }
|
|
];
|
|
|
|
return Timeline({
|
|
items: steps,
|
|
vertical: false,
|
|
class: 'min-w-[600px]'
|
|
});
|
|
};
|
|
Mount(HorizontalDemo, '#demo-horizontal');
|
|
```
|
|
|
|
### Compact Timeline
|
|
|
|
<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-compact" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
|
</div>
|
|
</div>
|
|
|
|
```javascript
|
|
const CompactDemo = () => {
|
|
const activities = [
|
|
{ title: 'User login', detail: '10:30 AM', type: 'success', completed: true },
|
|
{ title: 'Viewed dashboard', detail: '10:32 AM', type: 'info', completed: true },
|
|
{ title: 'Updated profile', detail: '10:45 AM', type: 'success', completed: true },
|
|
{ title: 'Made purchase', detail: '11:00 AM', type: 'warning', completed: false }
|
|
];
|
|
|
|
return Timeline({
|
|
items: activities,
|
|
compact: true
|
|
});
|
|
};
|
|
Mount(CompactDemo, '#demo-compact');
|
|
```
|
|
|
|
### Custom 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 milestones = [
|
|
{ title: 'Kickoff', detail: 'Project kickoff meeting', icon: Icons.iconInfo, completed: true },
|
|
{ title: 'MVP', detail: 'Minimum viable product', icon: Icons.iconSuccess, completed: true },
|
|
{ title: 'Beta', detail: 'Beta release', icon: Icons.iconWarning, completed: false },
|
|
{ title: 'Launch', detail: 'Public launch', icon: Icons.iconShow, completed: false }
|
|
];
|
|
|
|
return Timeline({ items: milestones });
|
|
};
|
|
Mount(IconsDemo, '#demo-icons');
|
|
```
|
|
|
|
### Reactive Timeline
|
|
|
|
<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 currentStep = $(2);
|
|
const steps = [
|
|
{ title: 'Order Placed', detail: 'Your order has been confirmed', type: 'success', completed: true },
|
|
{ title: 'Processing', detail: 'Payment verified, preparing shipment', type: 'success', completed: currentStep() > 1 },
|
|
{ title: 'Shipped', detail: 'Package on the way', type: 'warning', completed: currentStep() > 2 },
|
|
{ title: 'Delivered', detail: 'Arriving soon', type: 'info', completed: currentStep() > 3 }
|
|
];
|
|
|
|
const items = () => steps.map((step, idx) => ({
|
|
...step,
|
|
completed: idx < currentStep()
|
|
}));
|
|
|
|
const nextStep = () => {
|
|
if (currentStep() < steps.length) {
|
|
currentStep(currentStep() + 1);
|
|
Toast(`Step ${currentStep()}: ${steps[currentStep() - 1].title}`, 'alert-info', 1500);
|
|
}
|
|
};
|
|
|
|
const reset = () => {
|
|
currentStep(0);
|
|
Toast('Order tracking reset', 'alert-warning', 1500);
|
|
};
|
|
|
|
return Div({ class: 'flex flex-col gap-4' }, [
|
|
Timeline({ items: items }),
|
|
Div({ class: 'flex gap-2 justify-center mt-4' }, [
|
|
Button({
|
|
class: 'btn btn-primary btn-sm',
|
|
onclick: nextStep,
|
|
disabled: () => currentStep() >= steps.length
|
|
}, 'Next Step'),
|
|
Button({
|
|
class: 'btn btn-ghost btn-sm',
|
|
onclick: reset
|
|
}, 'Reset')
|
|
])
|
|
]);
|
|
};
|
|
Mount(ReactiveDemo, '#demo-reactive');
|
|
```
|
|
|
|
### Order Status Tracker
|
|
|
|
<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-order" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
|
</div>
|
|
</div>
|
|
|
|
```javascript
|
|
const OrderDemo = () => {
|
|
const status = $('shipped');
|
|
|
|
const statusMap = {
|
|
pending: { title: 'Order Pending', detail: 'Awaiting confirmation', completed: false, type: 'warning' },
|
|
confirmed: { title: 'Order Confirmed', detail: 'Payment received', completed: false, type: 'info' },
|
|
processing: { title: 'Processing', detail: 'Preparing your order', completed: false, type: 'info' },
|
|
shipped: { title: 'Shipped', detail: 'Package in transit', completed: false, type: 'info' },
|
|
delivered: { title: 'Delivered', detail: 'Order completed', completed: false, type: 'success' }
|
|
};
|
|
|
|
const statusOrder = ['pending', 'confirmed', 'processing', 'shipped', 'delivered'];
|
|
const currentIndex = statusOrder.indexOf(status());
|
|
|
|
const items = statusOrder.map((key, idx) => ({
|
|
...statusMap[key],
|
|
completed: idx < currentIndex
|
|
}));
|
|
|
|
return Div({ class: 'flex flex-col gap-4' }, [
|
|
Timeline({ items, compact: true }),
|
|
Div({ class: 'flex gap-2 justify-center flex-wrap mt-4' }, statusOrder.map(s =>
|
|
Button({
|
|
class: `btn btn-xs ${status() === s ? 'btn-primary' : 'btn-ghost'}`,
|
|
onclick: () => status(s)
|
|
}, statusMap[s].title)
|
|
))
|
|
]);
|
|
};
|
|
Mount(OrderDemo, '#demo-order');
|
|
```
|
|
|
|
### Company History
|
|
|
|
<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-history" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
|
</div>
|
|
</div>
|
|
|
|
```javascript
|
|
const HistoryDemo = () => {
|
|
const milestones = [
|
|
{
|
|
title: '2015 - Founded',
|
|
detail: 'Company started with 3 employees in a small office',
|
|
type: 'success',
|
|
completed: true
|
|
},
|
|
{
|
|
title: '2017 - First Product',
|
|
detail: 'Launched our first software product to market',
|
|
type: 'success',
|
|
completed: true
|
|
},
|
|
{
|
|
title: '2019 - Series A',
|
|
detail: 'Raised $5M in funding, expanded team to 50',
|
|
type: 'success',
|
|
completed: true
|
|
},
|
|
{
|
|
title: '2022 - Global Expansion',
|
|
detail: 'Opened offices in Europe and Asia',
|
|
type: 'info',
|
|
completed: true
|
|
},
|
|
{
|
|
title: '2024 - AI Integration',
|
|
detail: 'Launched AI-powered features',
|
|
type: 'warning',
|
|
completed: false
|
|
},
|
|
{
|
|
title: '2026 - Future Goals',
|
|
detail: 'Aiming for market leadership',
|
|
type: 'info',
|
|
completed: false
|
|
}
|
|
];
|
|
|
|
return Timeline({ items: milestones });
|
|
};
|
|
Mount(HistoryDemo, '#demo-history');
|
|
```
|
|
|
|
### 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-8"></div>
|
|
</div>
|
|
</div>
|
|
|
|
```javascript
|
|
const VariantsDemo = () => {
|
|
const sampleItems = [
|
|
{ title: 'Event 1', detail: 'Description here', type: 'success', completed: true },
|
|
{ title: 'Event 2', detail: 'Description here', type: 'warning', completed: false },
|
|
{ title: 'Event 3', detail: 'Description here', type: 'info', completed: false }
|
|
];
|
|
|
|
return Div({ class: 'flex flex-col gap-8' }, [
|
|
Div({ class: 'text-sm font-bold' }, 'Vertical Timeline'),
|
|
Timeline({ items: sampleItems }),
|
|
|
|
Div({ class: 'text-sm font-bold mt-4' }, 'Horizontal Timeline'),
|
|
Timeline({ items: sampleItems, vertical: false, class: 'min-w-[500px]' }),
|
|
|
|
Div({ class: 'text-sm font-bold mt-4' }, 'Compact Timeline'),
|
|
Timeline({ items: sampleItems, compact: true })
|
|
]);
|
|
};
|
|
Mount(VariantsDemo, '#demo-variants');
|
|
```
|
|
|
|
<script>
|
|
(function() {
|
|
const initTimelineExamples = () => {
|
|
|
|
// 1. Basic Timeline
|
|
const basicTarget = document.querySelector('#demo-basic');
|
|
if (basicTarget && !basicTarget.hasChildNodes()) {
|
|
const BasicDemo = () => {
|
|
const events = [
|
|
{ title: 'Project Started', detail: 'Initial planning and setup', type: 'info', completed: true },
|
|
{ title: 'Design Phase', detail: 'UI/UX design completed', type: 'success', completed: true },
|
|
{ title: 'Development', detail: 'Core features implemented', type: 'warning', completed: false },
|
|
{ title: 'Testing', detail: 'Quality assurance', type: 'info', completed: false },
|
|
{ title: 'Launch', detail: 'Production deployment', type: 'success', completed: false }
|
|
];
|
|
|
|
return Timeline({ items: events });
|
|
};
|
|
Mount(BasicDemo, basicTarget);
|
|
}
|
|
|
|
// 2. Horizontal Timeline
|
|
const horizontalTarget = document.querySelector('#demo-horizontal');
|
|
if (horizontalTarget && !horizontalTarget.hasChildNodes()) {
|
|
const HorizontalDemo = () => {
|
|
const steps = [
|
|
{ title: 'Step 1', detail: 'Requirements', type: 'success', completed: true },
|
|
{ title: 'Step 2', detail: 'Design', type: 'success', completed: true },
|
|
{ title: 'Step 3', detail: 'Development', type: 'warning', completed: false },
|
|
{ title: 'Step 4', detail: 'Testing', type: 'info', completed: false },
|
|
{ title: 'Step 5', detail: 'Deploy', type: 'info', completed: false }
|
|
];
|
|
|
|
return Timeline({
|
|
items: steps,
|
|
vertical: false,
|
|
class: 'min-w-[600px]'
|
|
});
|
|
};
|
|
Mount(HorizontalDemo, horizontalTarget);
|
|
}
|
|
|
|
// 3. Compact Timeline
|
|
const compactTarget = document.querySelector('#demo-compact');
|
|
if (compactTarget && !compactTarget.hasChildNodes()) {
|
|
const CompactDemo = () => {
|
|
const activities = [
|
|
{ title: 'User login', detail: '10:30 AM', type: 'success', completed: true },
|
|
{ title: 'Viewed dashboard', detail: '10:32 AM', type: 'info', completed: true },
|
|
{ title: 'Updated profile', detail: '10:45 AM', type: 'success', completed: true },
|
|
{ title: 'Made purchase', detail: '11:00 AM', type: 'warning', completed: false }
|
|
];
|
|
|
|
return Timeline({
|
|
items: activities,
|
|
compact: true
|
|
});
|
|
};
|
|
Mount(CompactDemo, compactTarget);
|
|
}
|
|
|
|
// 4. Custom Icons
|
|
const iconsTarget = document.querySelector('#demo-icons');
|
|
if (iconsTarget && !iconsTarget.hasChildNodes()) {
|
|
const IconsDemo = () => {
|
|
const milestones = [
|
|
{ title: 'Kickoff', detail: 'Project kickoff meeting', icon: Icons.iconInfo, completed: true },
|
|
{ title: 'MVP', detail: 'Minimum viable product', icon: Icons.iconSuccess, completed: true },
|
|
{ title: 'Beta', detail: 'Beta release', icon: Icons.iconWarning, completed: false },
|
|
{ title: 'Launch', detail: 'Public launch', icon: Icons.iconShow, completed: false }
|
|
];
|
|
|
|
return Timeline({ items: milestones });
|
|
};
|
|
Mount(IconsDemo, iconsTarget);
|
|
}
|
|
|
|
// 5. Reactive Timeline
|
|
const reactiveTarget = document.querySelector('#demo-reactive');
|
|
if (reactiveTarget && !reactiveTarget.hasChildNodes()) {
|
|
const ReactiveDemo = () => {
|
|
const currentStep = $(2);
|
|
const steps = [
|
|
{ title: 'Order Placed', detail: 'Your order has been confirmed', type: 'success', completed: true },
|
|
{ title: 'Processing', detail: 'Payment verified, preparing shipment', type: 'success', completed: currentStep() > 1 },
|
|
{ title: 'Shipped', detail: 'Package on the way', type: 'warning', completed: currentStep() > 2 },
|
|
{ title: 'Delivered', detail: 'Arriving soon', type: 'info', completed: currentStep() > 3 }
|
|
];
|
|
|
|
const items = () => steps.map((step, idx) => ({
|
|
...step,
|
|
completed: idx < currentStep()
|
|
}));
|
|
|
|
const nextStep = () => {
|
|
if (currentStep() < steps.length) {
|
|
currentStep(currentStep() + 1);
|
|
Toast(`Step ${currentStep()}: ${steps[currentStep() - 1].title}`, 'alert-info', 1500);
|
|
}
|
|
};
|
|
|
|
const reset = () => {
|
|
currentStep(0);
|
|
Toast('Order tracking reset', 'alert-warning', 1500);
|
|
};
|
|
|
|
return Div({ class: 'flex flex-col gap-4' }, [
|
|
Timeline({ items: items }),
|
|
Div({ class: 'flex gap-2 justify-center mt-4' }, [
|
|
Button({
|
|
class: 'btn btn-primary btn-sm',
|
|
onclick: nextStep,
|
|
disabled: () => currentStep() >= steps.length
|
|
}, 'Next Step'),
|
|
Button({
|
|
class: 'btn btn-ghost btn-sm',
|
|
onclick: reset
|
|
}, 'Reset')
|
|
])
|
|
]);
|
|
};
|
|
Mount(ReactiveDemo, reactiveTarget);
|
|
}
|
|
|
|
// 6. Order Status Tracker
|
|
const orderTarget = document.querySelector('#demo-order');
|
|
if (orderTarget && !orderTarget.hasChildNodes()) {
|
|
const OrderDemo = () => {
|
|
const status = $('shipped');
|
|
|
|
const statusMap = {
|
|
pending: { title: 'Order Pending', detail: 'Awaiting confirmation', completed: false, type: 'warning' },
|
|
confirmed: { title: 'Order Confirmed', detail: 'Payment received', completed: false, type: 'info' },
|
|
processing: { title: 'Processing', detail: 'Preparing your order', completed: false, type: 'info' },
|
|
shipped: { title: 'Shipped', detail: 'Package in transit', completed: false, type: 'info' },
|
|
delivered: { title: 'Delivered', detail: 'Order completed', completed: false, type: 'success' }
|
|
};
|
|
|
|
const statusOrder = ['pending', 'confirmed', 'processing', 'shipped', 'delivered'];
|
|
const currentIndex = statusOrder.indexOf(status());
|
|
|
|
const items = statusOrder.map((key, idx) => ({
|
|
...statusMap[key],
|
|
completed: idx < currentIndex
|
|
}));
|
|
|
|
return Div({ class: 'flex flex-col gap-4' }, [
|
|
Timeline({ items, compact: true }),
|
|
Div({ class: 'flex gap-2 justify-center flex-wrap mt-4' }, statusOrder.map(s =>
|
|
Button({
|
|
class: `btn btn-xs ${status() === s ? 'btn-primary' : 'btn-ghost'}`,
|
|
onclick: () => status(s)
|
|
}, statusMap[s].title)
|
|
))
|
|
]);
|
|
};
|
|
Mount(OrderDemo, orderTarget);
|
|
}
|
|
|
|
// 7. Company History
|
|
const historyTarget = document.querySelector('#demo-history');
|
|
if (historyTarget && !historyTarget.hasChildNodes()) {
|
|
const HistoryDemo = () => {
|
|
const milestones = [
|
|
{
|
|
title: '2015 - Founded',
|
|
detail: 'Company started with 3 employees in a small office',
|
|
type: 'success',
|
|
completed: true
|
|
},
|
|
{
|
|
title: '2017 - First Product',
|
|
detail: 'Launched our first software product to market',
|
|
type: 'success',
|
|
completed: true
|
|
},
|
|
{
|
|
title: '2019 - Series A',
|
|
detail: 'Raised $5M in funding, expanded team to 50',
|
|
type: 'success',
|
|
completed: true
|
|
},
|
|
{
|
|
title: '2022 - Global Expansion',
|
|
detail: 'Opened offices in Europe and Asia',
|
|
type: 'info',
|
|
completed: true
|
|
},
|
|
{
|
|
title: '2024 - AI Integration',
|
|
detail: 'Launched AI-powered features',
|
|
type: 'warning',
|
|
completed: false
|
|
},
|
|
{
|
|
title: '2026 - Future Goals',
|
|
detail: 'Aiming for market leadership',
|
|
type: 'info',
|
|
completed: false
|
|
}
|
|
];
|
|
|
|
return Timeline({ items: milestones });
|
|
};
|
|
Mount(HistoryDemo, historyTarget);
|
|
}
|
|
|
|
// 8. All Variants
|
|
const variantsTarget = document.querySelector('#demo-variants');
|
|
if (variantsTarget && !variantsTarget.hasChildNodes()) {
|
|
const VariantsDemo = () => {
|
|
const sampleItems = [
|
|
{ title: 'Event 1', detail: 'Description here', type: 'success', completed: true },
|
|
{ title: 'Event 2', detail: 'Description here', type: 'warning', completed: false },
|
|
{ title: 'Event 3', detail: 'Description here', type: 'info', completed: false }
|
|
];
|
|
|
|
return Div({ class: 'flex flex-col gap-8' }, [
|
|
Div({ class: 'text-sm font-bold' }, 'Vertical Timeline'),
|
|
Timeline({ items: sampleItems }),
|
|
|
|
Div({ class: 'text-sm font-bold mt-4' }, 'Horizontal Timeline'),
|
|
Timeline({ items: sampleItems, vertical: false, class: 'min-w-[500px]' }),
|
|
|
|
Div({ class: 'text-sm font-bold mt-4' }, 'Compact Timeline'),
|
|
Timeline({ items: sampleItems, compact: true })
|
|
]);
|
|
};
|
|
Mount(VariantsDemo, variantsTarget);
|
|
}
|
|
};
|
|
|
|
initTimelineExamples();
|
|
|
|
if (window.$docsify) {
|
|
window.$docsify.plugins = [].concat(window.$docsify.plugins || [], (hook) => {
|
|
hook.doneEach(initTimelineExamples);
|
|
});
|
|
}
|
|
})();
|
|
</script> |