Documentations added

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-05-04 19:57:48 +05:00
parent 40a7d9018c
commit ee8797c142
35 changed files with 6655 additions and 0 deletions
+234
View File
@@ -0,0 +1,234 @@
# Select
A styled `<select>` dropdown. Supports a pre-selected value, optional label, and optional description text. HTMX attributes can be added.
---
## HTML structure
```
div.flex.flex-col.gap-1.5
label[for={id}].text-sm.font-medium ← omitted when label is empty
{label}
select[id, name, class, $$HxAttrs$$]
option[value, $$Selected$$] ← one per option; selected="selected" when matched
{display}
p.text-sm.text-muted-foreground ← omitted when description is empty
{description}
```
---
## CSS mechanics
| Class | Effect |
|---|---|
| `flex h-10 w-full rounded-md border border-input bg-background` | Full-width 40px select field |
| `px-3 py-2 text-sm` | Inner padding and text size |
| `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring` | Keyboard focus ring |
| `disabled:cursor-not-allowed disabled:opacity-50` | Disabled state |
| `bg-background` | Ensures the dropdown matches the page background in dark mode |
---
## Constructor signature
```csharp
public Select(
string id,
IEnumerable<(string Value, string Display)> options,
string? selectedValue = null,
string name = "",
string label = "",
string description = "",
string extraClasses = "",
string hxAttrs = "")
```
| Parameter | Description |
|---|---|
| `id` | Element id and label `for` target |
| `options` | List of `(Value, Display)` tuples |
| `selectedValue` | Pre-selected option value; `null` = no pre-selection (first option shown) |
| `name` | Form field name |
| `label` | Optional visible label |
| `description` | Optional helper text below the field |
| `extraClasses` | Additional Tailwind classes on the `<select>` element |
| `hxAttrs` | Verbatim HTMX / data attributes |
---
## Usage examples
### Country selector
```csharp
new Select(
id: "country",
name: "country",
label: "Country",
options: new[]
{
("us", "United States"),
("gb", "United Kingdom"),
("ca", "Canada"),
("au", "Australia"),
},
selectedValue: "us")
```
### Dynamic options from data
```csharp
var options = categories.Select(c => (c.Slug, c.Name));
new Select(
id: "category",
name: "category",
label: "Category",
options: options,
selectedValue: existingCategory)
```
### HTMX on-change reload
```csharp
new Select(
id: "region",
name: "region",
label: "Region",
options: regions,
hxAttrs: """hx-get="/cities" hx-target="#city-select" hx-trigger="change" hx-include="[name='region']"""")
```
### Reading in a form handler
```csharp
public record Command([property: FromForm] string Country);
// command.Country == "us" | "gb" | "ca" | "au"
```
### Placeholder option (no pre-selection)
```csharp
new Select(
id: "role",
name: "role",
label: "Role",
options: new[]
{
("", "— Select a role —"),
("admin", "Administrator"),
("user", "Regular user"),
},
selectedValue: "")
```
---
## Tips and tricks
- Pass an empty-value placeholder as the first option (`("", "Select…")`) to force the user to make an explicit selection.
- `selectedValue` comparison is exact — make sure the value you pass matches one of the `Value` strings in `options`.
- `hxAttrs` is verbatim — you can add `multiple`, `size`, `disabled`, `autocomplete`, or any other native attribute here.
- To conditionally disable individual options, build the raw `<select>` HTML manually or subclass the component.
- To conditionally disable individual options, build the raw `<select>` HTML manually or subclass the component.
---
## Complete page example
**`Templates/FilterProductsPage.htmx`**
```html
<div class="max-w-4xl mx-auto py-10">
<h1 class="text-2xl font-bold mb-6">Products</h1>
<form class="flex gap-4 mb-8 items-end"
hx-get="/products/filter"
hx-target="#product-list"
hx-trigger="change">
$$CategorySelect$$
$$SortSelect$$
</form>
<div id="product-list">
$$ProductTable$$
</div>
</div>
```
**`Templates/FilterProductsPage.htmx.cs`**
```csharp
namespace Htmx.ApiDemo.Templates;
public sealed class FilterProductsPage : FilterProductsPageBase
{
private readonly IHtmxComponent _category;
private readonly IHtmxComponent _sort;
private readonly IHtmxComponent _table;
public FilterProductsPage(
IEnumerable<Product> products,
string selectedCategory = "",
string selectedSort = "name-asc")
{
_category = new Components.Select(
id: "category",
name: "category",
label: "Category",
options: new[]
{
("", "All categories"),
("electronics","Electronics"),
("clothing", "Clothing"),
("books", "Books"),
},
selectedValue: selectedCategory);
_sort = new Components.Select(
id: "sort",
name: "sort",
label: "Sort by",
options: new[]
{
("name-asc", "Name AZ"),
("name-desc", "Name ZA"),
("price-asc", "Price: low to high"),
("price-desc", "Price: high to low"),
},
selectedValue: selectedSort);
_table = new Components.Table(
headers: new[] { "Name", "Category", "Price" },
rows: products.Select(p => new[]
{
System.Net.WebUtility.HtmlEncode(p.Name),
System.Net.WebUtility.HtmlEncode(p.Category),
$"${p.Price:F2}",
}));
}
protected override void RenderCategorySelect(HtmxRenderContext ctx) => _category.Render(ctx.Next());
protected override void RenderSortSelect(HtmxRenderContext ctx) => _sort.Render(ctx.Next());
protected override void RenderProductTable(HtmxRenderContext ctx) => _table.Render(ctx.Next());
}
```
**GET handler (full page + HTMX partial)**
```csharp
[Handler]
[MapGet("/products")]
public static partial class GetProductsHandler
{
public record Query(
[property: FromQuery] string Category = "",
[property: FromQuery] string Sort = "name-asc");
private static async Task<IResult> HandleAsync(
Query q, HttpContext ctx, ProductService products, CancellationToken ct)
{
var items = await products.FilterAsync(q.Category, q.Sort, ct);
return await ctx.WriteHtmxPage(
new FilterProductsPage(items, q.Category, q.Sort), title: "Products");
}
}
```