409 lines
12 KiB
Markdown
409 lines
12 KiB
Markdown
# Tabs
|
||
|
||
Tabs component for organizing content into separate panels with tab navigation.
|
||
|
||
## Tag
|
||
|
||
`Tabs`
|
||
|
||
## Props
|
||
|
||
| Prop | Type | Default | Description |
|
||
| :--- | :--- | :--- | :--- |
|
||
| `items` | `Array<TabItem> \| Signal<Array>` | `[]` | Array of tab items |
|
||
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
|
||
|
||
### TabItem Structure
|
||
|
||
| Property | Type | Description |
|
||
| :--- | :--- | :--- |
|
||
| `label` | `string \| VNode` | Tab button label |
|
||
| `content` | `VNode \| function` | Content to display when tab is active |
|
||
| `active` | `boolean \| Signal<boolean>` | Whether this tab is active (only one per group) |
|
||
| `disabled` | `boolean` | Whether tab is disabled |
|
||
| `tip` | `string` | Tooltip text for the tab |
|
||
| `onclick` | `function` | Click handler (optional, overrides default) |
|
||
|
||
## Styling
|
||
|
||
Tabs supports all **daisyUI Tabs classes**:
|
||
|
||
| Category | Keywords | Description |
|
||
| :--- | :--- | :--- |
|
||
| Base | `tabs` | Base tabs container |
|
||
| Variant | `tabs-box`, `tabs-lifted` | Tab style variants |
|
||
| Size | `tabs-xs`, `tabs-sm`, `tabs-md`, `tabs-lg` | Tab scale |
|
||
| State | `tab-active`, `tab-disabled` | Tab states |
|
||
|
||
> For further details, check the [daisyUI Tabs Documentation](https://daisyui.com/components/tabs) – Full reference for CSS classes.
|
||
|
||
### Example
|
||
|
||
```javascript
|
||
Tabs({
|
||
items: [
|
||
{ label: "Tab 1", content: "Content 1", active: true },
|
||
{ label: "Tab 2", content: "Content 2" }
|
||
],
|
||
class: "tabs-box"
|
||
});
|
||
```
|
||
|
||
## Live Examples
|
||
|
||
### Basic Tabs
|
||
|
||
<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 activeTab = $('tab1');
|
||
|
||
return Tabs({
|
||
items: [
|
||
{
|
||
label: 'Tab 1',
|
||
active: () => activeTab() === 'tab1',
|
||
onclick: () => activeTab('tab1'),
|
||
content: Div({ class: 'p-4' }, 'Content for Tab 1')
|
||
},
|
||
{
|
||
label: 'Tab 2',
|
||
active: () => activeTab() === 'tab2',
|
||
onclick: () => activeTab('tab2'),
|
||
content: Div({ class: 'p-4' }, 'Content for Tab 2')
|
||
},
|
||
{
|
||
label: 'Tab 3',
|
||
active: () => activeTab() === 'tab3',
|
||
onclick: () => activeTab('tab3'),
|
||
content: Div({ class: 'p-4' }, 'Content for Tab 3')
|
||
}
|
||
]
|
||
});
|
||
};
|
||
Mount(BasicDemo, '#demo-basic');
|
||
```
|
||
|
||
### 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 activeTab = $('home');
|
||
|
||
return Tabs({
|
||
items: [
|
||
{
|
||
label: Span({ class: 'flex items-center gap-2' }, ['🏠', 'Home']),
|
||
active: () => activeTab() === 'home',
|
||
onclick: () => activeTab('home'),
|
||
content: Div({ class: 'p-4' }, 'Welcome to the Home tab!')
|
||
},
|
||
{
|
||
label: Span({ class: 'flex items-center gap-2' }, ['⭐', 'Favorites']),
|
||
active: () => activeTab() === 'favorites',
|
||
onclick: () => activeTab('favorites'),
|
||
content: Div({ class: 'p-4' }, 'Your favorite items appear here.')
|
||
},
|
||
{
|
||
label: Span({ class: 'flex items-center gap-2' }, ['⚙️', 'Settings']),
|
||
active: () => activeTab() === 'settings',
|
||
onclick: () => activeTab('settings'),
|
||
content: Div({ class: 'p-4' }, 'Configure your preferences.')
|
||
}
|
||
]
|
||
});
|
||
};
|
||
Mount(IconsDemo, '#demo-icons');
|
||
```
|
||
|
||
### With Tooltips
|
||
|
||
<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-tooltips" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
const TooltipsDemo = () => {
|
||
const activeTab = $('profile');
|
||
|
||
return Tabs({
|
||
items: [
|
||
{
|
||
label: 'Profile',
|
||
tip: 'View your profile information',
|
||
active: () => activeTab() === 'profile',
|
||
onclick: () => activeTab('profile'),
|
||
content: Div({ class: 'p-4' }, 'Profile information here.')
|
||
},
|
||
{
|
||
label: 'Settings',
|
||
tip: 'Adjust your preferences',
|
||
active: () => activeTab() === 'settings',
|
||
onclick: () => activeTab('settings'),
|
||
content: Div({ class: 'p-4' }, 'Settings configuration.')
|
||
},
|
||
{
|
||
label: 'Notifications',
|
||
tip: 'Manage notifications',
|
||
active: () => activeTab() === 'notifications',
|
||
onclick: () => activeTab('notifications'),
|
||
content: Div({ class: 'p-4' }, 'Notification settings.')
|
||
}
|
||
]
|
||
});
|
||
};
|
||
Mount(TooltipsDemo, '#demo-tooltips');
|
||
```
|
||
|
||
### Disabled Tab
|
||
|
||
<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-disabled" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
const DisabledDemo = () => {
|
||
const activeTab = $('basic');
|
||
|
||
return Tabs({
|
||
items: [
|
||
{
|
||
label: 'Basic',
|
||
active: () => activeTab() === 'basic',
|
||
onclick: () => activeTab('basic'),
|
||
content: Div({ class: 'p-4' }, 'Basic features available.')
|
||
},
|
||
{
|
||
label: 'Premium',
|
||
disabled: true,
|
||
tip: 'Upgrade to access',
|
||
content: Div({ class: 'p-4' }, 'Premium content (locked)')
|
||
},
|
||
{
|
||
label: 'Pro',
|
||
disabled: true,
|
||
tip: 'Coming soon',
|
||
content: Div({ class: 'p-4' }, 'Pro features (coming soon)')
|
||
}
|
||
]
|
||
});
|
||
};
|
||
Mount(DisabledDemo, '#demo-disabled');
|
||
```
|
||
|
||
### Reactive Content
|
||
|
||
<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 activeTab = $('counter');
|
||
const count = $(0);
|
||
|
||
return Tabs({
|
||
items: [
|
||
{
|
||
label: 'Counter',
|
||
active: () => activeTab() === 'counter',
|
||
onclick: () => activeTab('counter'),
|
||
content: Div({ class: 'p-4 text-center' }, [
|
||
Div({ class: 'text-4xl font-bold mb-4' }, () => count()),
|
||
Button({
|
||
class: 'btn btn-primary',
|
||
onclick: () => count(count() + 1)
|
||
}, 'Increment')
|
||
])
|
||
},
|
||
{
|
||
label: 'Timer',
|
||
active: () => activeTab() === 'timer',
|
||
onclick: () => activeTab('timer'),
|
||
content: Div({ class: 'p-4' }, () => `Current time: ${new Date().toLocaleTimeString()}`)
|
||
},
|
||
{
|
||
label: 'Status',
|
||
active: () => activeTab() === 'status',
|
||
onclick: () => activeTab('status'),
|
||
content: Div({ class: 'p-4' }, () => `Counter value: ${count()}, Last updated: ${new Date().toLocaleTimeString()}`)
|
||
}
|
||
]
|
||
});
|
||
};
|
||
Mount(ReactiveDemo, '#demo-reactive');
|
||
```
|
||
|
||
### Form Tabs
|
||
|
||
<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-form" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
const FormTabs = () => {
|
||
const activeTab = $('personal');
|
||
const formData = $({
|
||
name: '',
|
||
email: '',
|
||
address: '',
|
||
city: '',
|
||
notifications: true,
|
||
newsletter: false
|
||
});
|
||
|
||
const updateField = (field, value) => {
|
||
formData({ ...formData(), [field]: value });
|
||
};
|
||
|
||
const handleSubmit = () => {
|
||
Toast('Form submitted!', 'alert-success', 2000);
|
||
console.log(formData());
|
||
};
|
||
|
||
return Div({ class: 'flex flex-col gap-4' }, [
|
||
Tabs({
|
||
items: [
|
||
{
|
||
label: 'Personal Info',
|
||
active: () => activeTab() === 'personal',
|
||
onclick: () => activeTab('personal'),
|
||
content: Div({ class: 'p-4 space-y-4' }, [
|
||
Input({
|
||
label: 'Name',
|
||
value: () => formData().name,
|
||
placeholder: 'Enter your name',
|
||
oninput: (e) => updateField('name', e.target.value)
|
||
}),
|
||
Input({
|
||
label: 'Email',
|
||
type: 'email',
|
||
value: () => formData().email,
|
||
placeholder: 'email@example.com',
|
||
oninput: (e) => updateField('email', e.target.value)
|
||
})
|
||
])
|
||
},
|
||
{
|
||
label: 'Address',
|
||
active: () => activeTab() === 'address',
|
||
onclick: () => activeTab('address'),
|
||
content: Div({ class: 'p-4 space-y-4' }, [
|
||
Input({
|
||
label: 'Address',
|
||
value: () => formData().address,
|
||
placeholder: 'Street address',
|
||
oninput: (e) => updateField('address', e.target.value)
|
||
}),
|
||
Input({
|
||
label: 'City',
|
||
value: () => formData().city,
|
||
placeholder: 'City',
|
||
oninput: (e) => updateField('city', e.target.value)
|
||
})
|
||
])
|
||
},
|
||
{
|
||
label: 'Preferences',
|
||
active: () => activeTab() === 'prefs',
|
||
onclick: () => activeTab('prefs'),
|
||
content: Div({ class: 'p-4 space-y-4' }, [
|
||
Checkbox({
|
||
label: 'Email notifications',
|
||
value: () => formData().notifications,
|
||
onclick: () => updateField('notifications', !formData().notifications)
|
||
}),
|
||
Checkbox({
|
||
label: 'Newsletter subscription',
|
||
value: () => formData().newsletter,
|
||
onclick: () => updateField('newsletter', !formData().newsletter)
|
||
})
|
||
])
|
||
}
|
||
]
|
||
}),
|
||
Div({ class: 'flex justify-end mt-4' }, [
|
||
Button({
|
||
class: 'btn btn-primary',
|
||
onclick: handleSubmit
|
||
}, 'Submit')
|
||
])
|
||
]);
|
||
};
|
||
Mount(FormTabs, '#demo-form');
|
||
```
|
||
|
||
### 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 active1 = $('tab1');
|
||
const active2 = $('tab1');
|
||
const active3 = $('tab1');
|
||
|
||
const createItems = (active) => [
|
||
{
|
||
label: 'Tab 1',
|
||
active: () => active() === 'tab1',
|
||
onclick: () => active('tab1'),
|
||
content: Div({ class: 'p-4' }, 'Content 1')
|
||
},
|
||
{
|
||
label: 'Tab 2',
|
||
active: () => active() === 'tab2',
|
||
onclick: () => active('tab2'),
|
||
content: Div({ class: 'p-4' }, 'Content 2')
|
||
},
|
||
{
|
||
label: 'Tab 3',
|
||
active: () => active() === 'tab3',
|
||
onclick: () => active('tab3'),
|
||
content: Div({ class: 'p-4' }, 'Content 3')
|
||
}
|
||
];
|
||
|
||
return Div({ class: 'flex flex-col gap-6' }, [
|
||
Div({ class: 'text-sm font-bold' }, 'Default Tabs'),
|
||
Tabs({ items: createItems(active1) }),
|
||
|
||
Div({ class: 'text-sm font-bold mt-4' }, 'Boxed Tabs'),
|
||
Tabs({ items: createItems(active2), class: 'tabs-box' }),
|
||
|
||
Div({ class: 'text-sm font-bold mt-4' }, 'Lifted Tabs'),
|
||
Tabs({ items: createItems(active3), class: 'tabs-lifted' })
|
||
]);
|
||
};
|
||
Mount(VariantsDemo, '#demo-variants');
|
||
``` |