b530bb8c97
Co-authored-by: Copilot <copilot@github.com>
158 lines
4.8 KiB
Markdown
158 lines
4.8 KiB
Markdown
# Accordion
|
|
|
|
An expand/collapse panel list — like a FAQ section or a step-by-step guide where the user reveals each answer one at a time.
|
|
|
|
---
|
|
|
|
## Quick example
|
|
|
|
```csharp
|
|
new Accordion(
|
|
id: "faq",
|
|
items: new[]
|
|
{
|
|
("What is this?", "A fast HTMX app framework."),
|
|
("Is it AOT-safe?", "Yes, fully."),
|
|
("Do I need Node?", "Only for the Tailwind build step."),
|
|
})
|
|
```
|
|
|
|
That's it. Drop this into a page slot and you have a working FAQ section.
|
|
|
|
---
|
|
|
|
## All the options
|
|
|
|
```csharp
|
|
public Accordion(
|
|
string id,
|
|
IEnumerable<(string Title, string Content)> items,
|
|
int openIndex = -1)
|
|
```
|
|
|
|
| Parameter | What it does |
|
|
|---|---|
|
|
| `id` | A unique identifier for this accordion on the page. If you have two accordions on the same page, they need different ids. |
|
|
| `items` | The list of panels. Each item is a pair: the header text (`Title`) and the body content (`Content`). |
|
|
| `openIndex` | Which panel should start open. `0` = first panel, `1` = second, `-1` = all closed (default). |
|
|
|
|
---
|
|
|
|
## Real-world examples
|
|
|
|
### FAQ page with the first item pre-opened
|
|
|
|
```csharp
|
|
new Accordion(
|
|
id: "faq",
|
|
items: new[]
|
|
{
|
|
("How do I reset my password?", "Go to Settings → Security → Reset Password."),
|
|
("How do I cancel my account?", "Contact support from the Help page."),
|
|
("Where are my invoices?", "Under Billing in your account dashboard."),
|
|
},
|
|
openIndex: 0) // first answer visible on load
|
|
```
|
|
|
|
### Step-by-step guide with HTML content inside items
|
|
|
|
Item `Content` is rendered as raw HTML, so you can use markup inside it:
|
|
|
|
```csharp
|
|
new Accordion(
|
|
id: "setup-guide",
|
|
items: new[]
|
|
{
|
|
("Step 1 — Install dependencies",
|
|
"Run <code>npm install</code> inside the <code>Htmx.ApiDemo</code> folder."),
|
|
("Step 2 — Start MongoDB",
|
|
"<p>Start the MongoDB service, then confirm it is running on <code>localhost:27017</code>.</p>"),
|
|
("Step 3 — Run the app",
|
|
"Run <code>dotnet run --project Htmx.ApiDemo</code> and open <code>http://localhost:5120</code>."),
|
|
},
|
|
openIndex: 0)
|
|
```
|
|
|
|
> **Important:** `Title` and `Content` are inserted as raw HTML. If either value comes from user input or a database, HTML-encode it first:
|
|
> ```csharp
|
|
> System.Web.HttpUtility.HtmlEncode(userTitle)
|
|
> ```
|
|
|
|
### Inside a page
|
|
|
|
```html
|
|
<!-- Templates/FaqPage.htmx -->
|
|
<div class="max-w-2xl mx-auto py-10">
|
|
<h1 class="text-2xl font-bold mb-6">Frequently Asked Questions</h1>
|
|
$$FaqAccordion$$
|
|
</div>
|
|
```
|
|
|
|
```csharp
|
|
// Templates/FaqPage.htmx.cs
|
|
public sealed class FaqPage : FaqPageBase
|
|
{
|
|
private readonly IHtmxComponent _faqAccordion;
|
|
|
|
public FaqPage()
|
|
{
|
|
_faqAccordion = new Components.Accordion(
|
|
id: "faq",
|
|
items: new[]
|
|
{
|
|
("What is BeepBoop?", "A fast, AOT-safe HTMX web framework built on .NET 10."),
|
|
("Do I need Node.js?", "Only to run the Tailwind CSS build step."),
|
|
("Is MongoDB required?", "No — swap in any data store you prefer."),
|
|
});
|
|
}
|
|
|
|
protected override void RenderFaqAccordion(HtmxRenderContext ctx)
|
|
=> _faqAccordion.Render(ctx.Next());
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## How it works
|
|
|
|
The server renders all panels into the HTML. Closed panels are given `height: 0; opacity: 0` inline styles so they are invisible immediately — no layout flash. The JavaScript in `components.js` (`initAccordion`) then attaches click listeners.
|
|
|
|
When a panel is opened, JS reads the panel's `scrollHeight` (its natural height) and animates the inline `height` from `0` to that value alongside the opacity, giving a smooth slide-down. The chevron icon rotates 180° to point down when open.
|
|
|
|
If the page content is updated by HTMX, `htmx:afterSwap` re-runs the initialisation so newly swapped-in accordions also get click behaviour.
|
|
|
|
Users can open multiple panels simultaneously — there is no "only one open at a time" constraint.
|
|
|
|
---
|
|
|
|
## Tips
|
|
|
|
- The `id` must be unique if you place more than one accordion on a page.
|
|
- `openIndex` only controls the initial server-rendered state — the user can freely open or close any panel after that.
|
|
- To listen for accordion interactions from another script, add a `click` listener to the parent container and check `event.target.closest('.accordion-trigger')`.
|
|
("How do I deploy?",
|
|
"Run <code>dotnet publish -c Release</code> for a native AOT binary."),
|
|
});
|
|
}
|
|
|
|
protected override void RenderFaqAccordion(HtmxRenderContext ctx)
|
|
=> _faq.Render(ctx.Next());
|
|
}
|
|
```
|
|
|
|
**`Templates/FaqPage.htmx.cs` — GET handler**
|
|
```csharp
|
|
[Handler]
|
|
[MapGet("/faq")]
|
|
public static partial class GetFaqHandler
|
|
{
|
|
public record Query();
|
|
|
|
private static Task<IResult> HandleAsync(
|
|
Query _,
|
|
HttpContext ctx,
|
|
CancellationToken ct)
|
|
=> ctx.WriteHtmxPage(new FaqPage(), title: "FAQ");
|
|
}
|
|
```
|