Rewrote all the docs - more noob friendly now.
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
+90
-108
@@ -1,72 +1,10 @@
|
||||
# Accordion
|
||||
|
||||
An expand/collapse panel list. Items are collapsed by default; one item can be pre-expanded at server render time. Client-side toggle is handled by `components.js`.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## HTML structure
|
||||
|
||||
```
|
||||
div.accordion-root[id] ← outer wrapper
|
||||
div.accordion-item ← one per item, border-b separator
|
||||
h3
|
||||
button.accordion-trigger ← clickable header; aria-expanded tracks state
|
||||
{title text}
|
||||
svg.accordion-chevron ← rotates 180° when open
|
||||
div.accordion-panel ← collapsible area; height/opacity driven by JS
|
||||
div.pb-4
|
||||
{content}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CSS mechanics
|
||||
|
||||
| Class / property | Effect |
|
||||
|---|---|
|
||||
| `overflow-hidden` on panel | Prevents content leaking outside the panel during animation |
|
||||
| `transition-all duration-200` on panel | Smooth height and opacity animation |
|
||||
| `height: 0; opacity: 0` (collapsed) | Starting state set server-side for closed items |
|
||||
| `height: auto; opacity: 1` (open) | Starting state for the pre-expanded item |
|
||||
| `accordion-chevron` + JS `rotate(180deg)` | Chevron rotates down when expanded |
|
||||
|
||||
---
|
||||
|
||||
## JavaScript (`initAccordion` in `components.js`)
|
||||
|
||||
Runs on `DOMContentLoaded` and on `htmx:afterSwap` so HTMX-swapped accordions are correctly initialized.
|
||||
|
||||
**Per-instance initialization:**
|
||||
|
||||
1. Guard `root._accInitialised` prevents double-binding after re-renders
|
||||
2. For each `.accordion-trigger`, attach a `click` listener:
|
||||
- Read current state from `aria-expanded`
|
||||
- If currently open → set `panel.style.height = "0"`, `opacity = "0"`, `aria-expanded = "false"`
|
||||
- If currently closed → set `panel.style.height = scrollHeight + "px"`, `opacity = "1"`, `aria-expanded = "true"`
|
||||
3. Rotate `.accordion-chevron` via `transform: rotate(180deg)` when open
|
||||
|
||||
---
|
||||
|
||||
## Constructor signature
|
||||
|
||||
```csharp
|
||||
public Accordion(
|
||||
string id,
|
||||
IEnumerable<(string Title, string Content)> items,
|
||||
int openIndex = -1)
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
|---|---|
|
||||
| `id` | Unique element id for the root `div` |
|
||||
| `items` | List of `(Title, Content)` tuples |
|
||||
| `openIndex` | Zero-based index of the pre-expanded item; `-1` = all closed |
|
||||
|
||||
---
|
||||
|
||||
## Usage examples
|
||||
|
||||
### All closed
|
||||
## Quick example
|
||||
|
||||
```csharp
|
||||
new Accordion(
|
||||
@@ -75,79 +13,123 @@ new Accordion(
|
||||
{
|
||||
("What is this?", "A fast HTMX app framework."),
|
||||
("Is it AOT-safe?", "Yes, fully."),
|
||||
("Do I need Node?", "Only to run the Tailwind build step."),
|
||||
("Do I need Node?", "Only for the Tailwind build step."),
|
||||
})
|
||||
```
|
||||
|
||||
### One pre-expanded
|
||||
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: "setup-steps",
|
||||
id: "faq",
|
||||
items: new[]
|
||||
{
|
||||
("Step 1 — Install", "Run <code>npm install</code> in the project folder."),
|
||||
("Step 2 — Configure", "Edit <code>appsettings.json</code> with your connection string."),
|
||||
("Step 3 — Run", "Use <code>dotnet run</code> to start the server."),
|
||||
("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)
|
||||
```
|
||||
|
||||
### HTML content in items
|
||||
> **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)
|
||||
> ```
|
||||
|
||||
```csharp
|
||||
new Accordion(
|
||||
id: "code-examples",
|
||||
items: new[]
|
||||
{
|
||||
("C# snippet", "<pre><code>var x = 1 + 1;</code></pre>"),
|
||||
("Tip", "<p>Use <strong>AOT-safe</strong> serialization patterns.</p>"),
|
||||
})
|
||||
```
|
||||
### Inside a page
|
||||
|
||||
---
|
||||
|
||||
## Tips and tricks
|
||||
|
||||
- `openIndex` only controls the initial server-rendered state. After the page loads, the user can open/close any item freely.
|
||||
- Item `Title` and `Content` strings are inserted as raw HTML — HTML-encode any user-supplied values before passing them in.
|
||||
- Multiple items can be opened simultaneously by the user — there is no "only one open at a time" constraint in the JS.
|
||||
- If you need to identify which accordion is which after a click, listen to the parent element and inspect `event.target.closest('.accordion-item')`.
|
||||
- The `id` must be unique on the page if you place more than one accordion.
|
||||
|
||||
---
|
||||
|
||||
## Complete page example
|
||||
|
||||
**`Templates/FaqPage.htmx`**
|
||||
```html
|
||||
<!-- Templates/FaqPage.htmx -->
|
||||
<div class="max-w-2xl mx-auto py-10">
|
||||
<h1 class="text-2xl font-bold mb-2">Frequently Asked Questions</h1>
|
||||
<p class="text-muted-foreground mb-8">Everything you need to know about BeepBoop.</p>
|
||||
<h1 class="text-2xl font-bold mb-6">Frequently Asked Questions</h1>
|
||||
$$FaqAccordion$$
|
||||
</div>
|
||||
```
|
||||
|
||||
**`Templates/FaqPage.htmx.cs`**
|
||||
```csharp
|
||||
namespace Htmx.ApiDemo.Templates;
|
||||
|
||||
// Templates/FaqPage.htmx.cs
|
||||
public sealed class FaqPage : FaqPageBase
|
||||
{
|
||||
private readonly IHtmxComponent _faq;
|
||||
private readonly IHtmxComponent _faqAccordion;
|
||||
|
||||
public FaqPage()
|
||||
{
|
||||
_faq = new Components.Accordion(
|
||||
_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 during development."),
|
||||
("Is MongoDB required?",
|
||||
"No — swap in any data store you prefer."),
|
||||
("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."),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user