400 lines
13 KiB
Markdown
400 lines
13 KiB
Markdown
# Accordion
|
||
|
||
Collapsible accordion component for organizing content into expandable sections.
|
||
|
||
## Tag
|
||
|
||
`Accordion`
|
||
|
||
## Props
|
||
|
||
| Prop | Type | Default | Description |
|
||
| :--- | :--- | :--- | :--- |
|
||
| `title` | `string \| VNode \| Signal` | Required | Accordion section title |
|
||
| `open` | `boolean \| Signal<boolean>` | `false` | Whether the accordion is expanded |
|
||
| `name` | `string` | `-` | Group name for radio-style accordions (only one open at a time) |
|
||
| `class` | `string` | `''` | Additional CSS classes (DaisyUI + Tailwind) |
|
||
| `children` | `VNode \| Array<VNode>` | Required | Content to display when expanded |
|
||
|
||
## Styling
|
||
|
||
Accordion supports all **daisyUI Collapse classes**:
|
||
|
||
| Category | Keywords | Description |
|
||
| :--- | :--- | :--- |
|
||
| Type | `collapse-arrow`, `collapse-plus`, `collapse-minus` | Expand indicator style |
|
||
| Color | `collapse-primary`, `collapse-secondary`, `collapse-accent` | Color variants |
|
||
| Background | `bg-base-100`, `bg-base-200` | Background colors |
|
||
|
||
> For further details, check the [daisyUI Collapse Documentation](https://daisyui.com/components/collapse) – Full reference for CSS classes.
|
||
|
||
## Live Examples
|
||
|
||
### Basic Accordion
|
||
|
||
<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 open1 = $(false);
|
||
const open2 = $(false);
|
||
const open3 = $(false);
|
||
|
||
return Div({ class: 'flex flex-col gap-2' }, [
|
||
Accordion({
|
||
title: 'Section 1',
|
||
open: open1,
|
||
onclick: () => open1(!open1())
|
||
}, [
|
||
Div({ class: 'p-2' }, 'Content for section 1. This is a basic accordion section.')
|
||
]),
|
||
Accordion({
|
||
title: 'Section 2',
|
||
open: open2,
|
||
onclick: () => open2(!open2())
|
||
}, [
|
||
Div({ class: 'p-2' }, 'Content for section 2. You can put any content here.')
|
||
]),
|
||
Accordion({
|
||
title: 'Section 3',
|
||
open: open3,
|
||
onclick: () => open3(!open3())
|
||
}, [
|
||
Div({ class: 'p-2' }, 'Content for section 3. Accordions are great for FAQs.')
|
||
])
|
||
]);
|
||
};
|
||
Mount(BasicDemo, '#demo-basic');
|
||
```
|
||
|
||
### Group Accordion (Radio Style)
|
||
|
||
<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-group" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
const GroupDemo = () => {
|
||
const openSection = $('section1');
|
||
|
||
return Div({ class: 'flex flex-col gap-2' }, [
|
||
Accordion({
|
||
title: 'Section 1',
|
||
name: 'group',
|
||
open: () => openSection() === 'section1',
|
||
onclick: () => openSection('section1')
|
||
}, [
|
||
Div({ class: 'p-2' }, 'Content for section 1. Only one section can be open at a time.')
|
||
]),
|
||
Accordion({
|
||
title: 'Section 2',
|
||
name: 'group',
|
||
open: () => openSection() === 'section2',
|
||
onclick: () => openSection('section2')
|
||
}, [
|
||
Div({ class: 'p-2' }, 'Content for section 2. Opening this will close section 1.')
|
||
]),
|
||
Accordion({
|
||
title: 'Section 3',
|
||
name: 'group',
|
||
open: () => openSection() === 'section3',
|
||
onclick: () => openSection('section3')
|
||
}, [
|
||
Div({ class: 'p-2' }, 'Content for section 3. This is useful for FAQ sections.')
|
||
])
|
||
]);
|
||
};
|
||
Mount(GroupDemo, '#demo-group');
|
||
```
|
||
|
||
### FAQ Accordion
|
||
|
||
<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-faq" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
const FaqDemo = () => {
|
||
const openFaq = $('faq1');
|
||
|
||
const faqs = [
|
||
{ id: 'faq1', question: 'What is this component?', answer: 'This is an accordion component built with DaisyUI and Tailwind CSS for creating collapsible content sections.' },
|
||
{ id: 'faq2', question: 'How do I use it?', answer: 'Simply import the Accordion component and pass title and children props. Use the open prop to control expansion.' },
|
||
{ id: 'faq3', question: 'Can I have multiple open?', answer: 'Yes! By default, accordions can be opened independently. Use the name prop to create groups where only one can be open.' },
|
||
{ id: 'faq4', question: 'Is it accessible?', answer: 'Yes, the accordion uses proper ARIA attributes and keyboard navigation support.' }
|
||
];
|
||
|
||
return Div({ class: 'flex flex-col gap-2' }, faqs.map(faq =>
|
||
Accordion({
|
||
title: faq.question,
|
||
name: 'faq-group',
|
||
open: () => openFaq() === faq.id,
|
||
onclick: () => openFaq(openFaq() === faq.id ? '' : faq.id)
|
||
}, [
|
||
Div({ class: 'p-2 text-sm' }, faq.answer)
|
||
])
|
||
));
|
||
};
|
||
Mount(FaqDemo, '#demo-faq');
|
||
```
|
||
|
||
### With Rich 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-rich" class="bg-base-100 p-6 rounded-xl border border-base-300"></div>
|
||
</div>
|
||
</div>
|
||
|
||
```javascript
|
||
const RichDemo = () => {
|
||
const open1 = $(true);
|
||
const open2 = $(false);
|
||
|
||
return Div({ class: 'flex flex-col gap-2' }, [
|
||
Accordion({
|
||
title: Span({ class: 'flex items-center gap-2' }, ['📊', 'Statistics']),
|
||
open: open1,
|
||
onclick: () => open1(!open1())
|
||
}, [
|
||
Div({ class: 'p-2' }, [
|
||
Div({ class: 'grid grid-cols-2 gap-4' }, [
|
||
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
|
||
Div({ class: 'stat-title' }, 'Users'),
|
||
Div({ class: 'stat-value text-lg' }, '1,234')
|
||
]),
|
||
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
|
||
Div({ class: 'stat-title' }, 'Revenue'),
|
||
Div({ class: 'stat-value text-lg' }, '$45,678')
|
||
]),
|
||
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
|
||
Div({ class: 'stat-title' }, 'Growth'),
|
||
Div({ class: 'stat-value text-lg text-success' }, '+23%')
|
||
]),
|
||
Div({ class: 'stat bg-base-100 rounded-lg p-3' }, [
|
||
Div({ class: 'stat-title' }, 'Active'),
|
||
Div({ class: 'stat-value text-lg' }, '89%')
|
||
])
|
||
])
|
||
])
|
||
]),
|
||
Accordion({
|
||
title: Span({ class: 'flex items-center gap-2' }, ['👥', 'Team Members']),
|
||
open: open2,
|
||
onclick: () => open2(!open2())
|
||
}, [
|
||
Div({ class: 'p-2 space-y-2' }, [
|
||
Div({ class: 'flex items-center gap-3 p-2 hover:bg-base-100 rounded-lg' }, [
|
||
Div({ class: 'avatar placeholder' }, [
|
||
Div({ class: 'bg-primary text-primary-content rounded-full w-10 h-10 flex items-center justify-center' }, 'JD')
|
||
]),
|
||
Div({ class: 'flex-1' }, [
|
||
Div({ class: 'font-medium' }, 'John Doe'),
|
||
Div({ class: 'text-sm opacity-70' }, 'Developer')
|
||
])
|
||
]),
|
||
Div({ class: 'flex items-center gap-3 p-2 hover:bg-base-100 rounded-lg' }, [
|
||
Div({ class: 'avatar placeholder' }, [
|
||
Div({ class: 'bg-secondary text-secondary-content rounded-full w-10 h-10 flex items-center justify-center' }, 'JS')
|
||
]),
|
||
Div({ class: 'flex-1' }, [
|
||
Div({ class: 'font-medium' }, 'Jane Smith'),
|
||
Div({ class: 'text-sm opacity-70' }, 'Designer')
|
||
])
|
||
])
|
||
])
|
||
])
|
||
]);
|
||
};
|
||
Mount(RichDemo, '#demo-rich');
|
||
```
|
||
|
||
### Form Accordion
|
||
|
||
<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 FormAccordion = () => {
|
||
const openStep = $('step1');
|
||
const formData = $({
|
||
name: '',
|
||
email: '',
|
||
address: '',
|
||
payment: 'credit'
|
||
});
|
||
|
||
const updateField = (field, value) => {
|
||
formData({ ...formData(), [field]: value });
|
||
};
|
||
|
||
const nextStep = () => {
|
||
if (openStep() === 'step1') openStep('step2');
|
||
else if (openStep() === 'step2') openStep('step3');
|
||
};
|
||
|
||
const prevStep = () => {
|
||
if (openStep() === 'step2') openStep('step1');
|
||
else if (openStep() === 'step3') openStep('step2');
|
||
};
|
||
|
||
const handleSubmit = () => {
|
||
Toast('Form submitted!', 'alert-success', 2000);
|
||
console.log(formData());
|
||
};
|
||
|
||
return Div({ class: 'flex flex-col gap-2' }, [
|
||
Accordion({
|
||
title: Span({ class: 'flex items-center gap-2' }, ['1️⃣', 'Personal Information']),
|
||
name: 'form-steps',
|
||
open: () => openStep() === 'step1',
|
||
onclick: () => openStep('step1')
|
||
}, [
|
||
Div({ class: 'p-4 space-y-4' }, [
|
||
Input({
|
||
label: 'Full 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)
|
||
}),
|
||
Div({ class: 'flex justify-end mt-2' }, [
|
||
Button({
|
||
class: 'btn btn-primary btn-sm',
|
||
onclick: nextStep,
|
||
disabled: () => !formData().name || !formData().email
|
||
}, 'Next →')
|
||
])
|
||
])
|
||
]),
|
||
Accordion({
|
||
title: Span({ class: 'flex items-center gap-2' }, ['2️⃣', 'Address']),
|
||
name: 'form-steps',
|
||
open: () => openStep() === 'step2',
|
||
onclick: () => openStep('step2')
|
||
}, [
|
||
Div({ class: 'p-4 space-y-4' }, [
|
||
Input({
|
||
label: 'Address',
|
||
value: () => formData().address,
|
||
placeholder: 'Street address',
|
||
oninput: (e) => updateField('address', e.target.value)
|
||
}),
|
||
Div({ class: 'flex justify-between mt-2' }, [
|
||
Button({ class: 'btn btn-ghost btn-sm', onclick: prevStep }, '← Back'),
|
||
Button({
|
||
class: 'btn btn-primary btn-sm',
|
||
onclick: nextStep
|
||
}, 'Next →')
|
||
])
|
||
])
|
||
]),
|
||
Accordion({
|
||
title: Span({ class: 'flex items-center gap-2' }, ['3️⃣', 'Payment']),
|
||
name: 'form-steps',
|
||
open: () => openStep() === 'step3',
|
||
onclick: () => openStep('step3')
|
||
}, [
|
||
Div({ class: 'p-4 space-y-4' }, [
|
||
Div({ class: 'flex flex-col gap-2' }, [
|
||
Radio({
|
||
label: 'Credit Card',
|
||
value: () => formData().payment,
|
||
radioValue: 'credit',
|
||
onclick: () => updateField('payment', 'credit')
|
||
}),
|
||
Radio({
|
||
label: 'PayPal',
|
||
value: () => formData().payment,
|
||
radioValue: 'paypal',
|
||
onclick: () => updateField('payment', 'paypal')
|
||
}),
|
||
Radio({
|
||
label: 'Bank Transfer',
|
||
value: () => formData().payment,
|
||
radioValue: 'bank',
|
||
onclick: () => updateField('payment', 'bank')
|
||
})
|
||
]),
|
||
Div({ class: 'flex justify-between mt-2' }, [
|
||
Button({ class: 'btn btn-ghost btn-sm', onclick: prevStep }, '← Back'),
|
||
Button({ class: 'btn btn-success btn-sm', onclick: handleSubmit }, 'Submit')
|
||
])
|
||
])
|
||
])
|
||
]);
|
||
};
|
||
Mount(FormAccordion, '#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 open1 = $(true);
|
||
const open2 = $(false);
|
||
const open3 = $(false);
|
||
|
||
return Div({ class: 'flex flex-col gap-4' }, [
|
||
Div({ class: 'text-sm font-bold' }, 'Default Accordion'),
|
||
Div({ class: 'flex flex-col gap-2' }, [
|
||
Accordion({ title: 'Default style', open: open1, onclick: () => open1(!open1()) }, [
|
||
Div({ class: 'p-2' }, 'Default accordion with standard styling.')
|
||
])
|
||
]),
|
||
|
||
Div({ class: 'text-sm font-bold mt-2' }, 'Custom Styling'),
|
||
Div({ class: 'flex flex-col gap-2' }, [
|
||
Accordion({
|
||
title: Span({ class: 'text-primary font-bold' }, 'Primary Title'),
|
||
open: open2,
|
||
onclick: () => open2(!open2()),
|
||
class: 'bg-primary/5 border-primary/20'
|
||
}, [
|
||
Div({ class: 'p-2' }, 'Accordion with custom styling and primary color.')
|
||
])
|
||
]),
|
||
|
||
Div({ class: 'text-sm font-bold mt-2' }, 'With Icons'),
|
||
Div({ class: 'flex flex-col gap-2' }, [
|
||
Accordion({
|
||
title: Span({ class: 'flex items-center gap-2' }, ['✨', 'Featured Content']),
|
||
open: open3,
|
||
onclick: () => open3(!open3())
|
||
}, [
|
||
Div({ class: 'p-2' }, 'Accordion with emoji icons in the title.')
|
||
])
|
||
])
|
||
]);
|
||
};
|
||
Mount(VariantsDemo, '#demo-variants');
|
||
``` |