Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion docs/components/toolbar.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ The Toolbar component provides a navigation bar for grouping and displaying icon

Each `BitToolbarItem` can be used as a navigation link (via `Href`), a click handler (via `OnClick`), or both at the same time. When both are provided, `OnClick` takes precedence in interactive rendering while `Href` remains available for browser-native behaviors such as right-click → Open in new tab. In static SSR rendering, `Href` is the only navigation mechanism.

A `BitToolbarDropdownItem` renders a [`BitDropdown`](./dropdown.md) activator styled as a toolbar item, letting a single toolbar entry expand into a menu of `BitDropdownItem` choices.

> **`BitToolbarDropdownItem` requires interactive rendering.** It renders a `BitDropdown` internally, and the dropdown's open/closed state is managed purely in C# (`@onclick` / `@onkeydown`) with no JavaScript fallback. Under static SSR the activator button cannot be toggled, so the menu can never open. Only use `BitToolbarDropdownItem` in pages or components running under an interactive render mode (Server, WebAssembly, or Auto). `BitToolbarItem` (with `Href` and/or `OnClick`) remains fully SSR-compatible.

## Components

| Component | Description |
|-----------|-------------|
| `BitToolbar` | Root container that renders a `<nav>` with an inner `<ul>` and cascades itself to child items |
| `BitToolbarItem` | Individual action item rendered as an `<li>` with an icon and a label |
| `BitToolbarDropdownItem` | Individual action item rendered as an `<li>` containing a `BitDropdown` activator; expands into a menu of `BitDropdownItem` choices. **Requires interactive rendering** |
| `BitToolbarDivider` | Visual and semantic separator between items |

## BitToolbar Parameters
Expand All @@ -43,12 +48,28 @@ Each `BitToolbarItem` can be used as a navigation link (via `Href`), a click han
| `Active` | `bool` | ✗ | `false` | When `true`, applies the active style to the item |
| `Disabled` | `bool` | ✗ | `false` | When `true`, disables the item and adds `aria-disabled="true"` |
| `BadgeCount` | `int?` | ✗ | `null` | A numeric badge count shown on the item; hidden when `null` or `0` |
| `BadgeLabel` | `string?` | ✗ | `null` | A text label shown next to the badge; displayed in different positions depending on `Size` |
| `BadgeLabel` | `string?` | ✗ | `null` | A visually hidden accessible label that provides context for the badge count; its markup position varies with the toolbar `Size` |
| `OnClick` | `EventCallback` | ✗ | - | Primary interactive callback invoked when the item is clicked. Takes precedence over `Href` navigation in interactive rendering. Not invoked during static (SSR) rendering — provide `Href` as a navigation fallback for SSR contexts. |
| `Id` | `string?` | ✗ | `null` | Sets the `id` HTML attribute on the root element |
| `CssClass` | `string?` | ✗ | `null` | Additional CSS classes to apply to the item |
| `AdditionalAttributes` | `IDictionary<string, object>?` | ✗ | - | Additional HTML attributes forwarded to the root element |

## BitToolbarDropdownItem Parameters

> **Requires interactive rendering.** See the note above — this component only functions under Server, WebAssembly, or Auto render modes.

| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `Id` | `string` | ✓ | - | Unique identifier for the item, passed as the `BitDropdown.ActivatorId` and used to associate the activator button with its menu for accessibility |
| `Label` | `string` | ✓ | - | The visible label text for the item, also passed as the `BitDropdown.ActivatorLabel` |
| `IconName` | `string` | ✓ | - | The Bootstrap Italia icon name to display on the activator button |
| `ChildContent` | `RenderFragment` | ✓ | - | One or more `BitDropdownItem` components rendered inside the dropdown menu |
| `Active` | `bool` | ✗ | `false` | When `true`, applies the active style to the item |
| `Disabled` | `bool` | ✗ | `false` | When `true`, disables the item and adds `aria-disabled="true"` |
| `BadgeCount` | `int?` | ✗ | `null` | A numeric badge count shown on the item; hidden when `null` or `0` |
| `BadgeLabel` | `string?` | ✗ | `null` | A visually hidden accessible label that provides context for the badge count; its markup position varies with the toolbar `Size` |
| `AdditionalAttributes` | `IDictionary<string, object>?` | ✗ | - | Additional HTML attributes forwarded to the activator button |

## Used Enumerations

### ToolbarSize
Expand Down Expand Up @@ -188,3 +209,24 @@ When both `Href` and `OnClick` are provided, `OnClick` handles the primary click
<BitToolbarItem IconName="it-home" Label="Home" Href="/" />
</BitToolbar>
```

### With a dropdown item (interactive rendering required)

`BitToolbarDropdownItem` expands a toolbar entry into a `BitDropdown` menu. Because the menu's open/closed state is managed in C# with no JavaScript fallback, this only works in an interactive render mode — add `@rendermode InteractiveServer` (or `InteractiveWebAssembly`/`InteractiveAuto`) to the page or component, or host it inside an already-interactive parent.

```razor
@rendermode InteractiveServer

<BitToolbar>
<BitToolbarItem IconName="it-home" Label="Home" Href="/" />
<BitToolbarDropdownItem Id="toolbar-user-menu" IconName="it-user" Label="Account">
<BitDropdownItem Href="/profile"><span>Profile</span></BitDropdownItem>
<BitDropdownItem Href="/settings"><span>Settings</span></BitDropdownItem>
<BitDropdownItem OnClick="HandleSignOut"><span>Sign out</span></BitDropdownItem>
</BitToolbarDropdownItem>
</BitToolbar>

@code {
private void HandleSignOut() { /* ... */ }
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@
Label="Stampa"
Active="@(_activeToolbarItem == "stampa")"
OnClick="StampaAsync" />
<BitToolbarDropdownItem IconName="@Icons.ItFiles"
Label="Esporta"
Id="pratiche-esporta-dropdown">
<BitDropdownItem OnClick="@(() => EsportaAsync("PDF"))"><span>Esporta in PDF</span></BitDropdownItem>
<BitDropdownItem OnClick="@(() => EsportaAsync("Excel"))"><span>Esporta in Excel</span></BitDropdownItem>
<BitDropdownItem OnClick="@(() => EsportaAsync("CSV"))"><span>Esporta in CSV</span></BitDropdownItem>
</BitToolbarDropdownItem>
<BitToolbarDivider />
<BitToolbarItem IconName="@Icons.ItSettings"
Label="Impostazioni"
Expand Down Expand Up @@ -204,4 +211,11 @@ else
_activeToolbarItem = _activeToolbarItem == "impostazioni" ? null : "impostazioni";
return Task.CompletedTask;
}

private Task EsportaAsync(string formato)
{
_activeToolbarItem = null;
// In un'applicazione reale, qui verrebbe avviato il download delle pratiche nel formato scelto.
return Task.CompletedTask;
}
}
39 changes: 39 additions & 0 deletions src/BitBlazor/Components/Toolbar/BitToolbarDropdownItem.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@namespace BitBlazor.Components

@inherits BitToolbarItemBase

<li>
<BitDropdown ActivatorId="@Id" ActivatorLabel="@Label">
<ActivatorTemplate>
<button class="@ComputeActivatorCssClass()" type="button" id="@context.ActivatorId" @ref="context.ActivatorRef" @attributes="ComputeActivatorAttributes(context)" disabled="@Disabled" @onclick="@(() => HandleActivatorClick(context))" @onkeydown="@(args => HandleActivatorKeyDownAsync(context, args))">
@if (HasBadge)
{
<div class="badge-wrapper">
<span class="toolbar-badge">
@if (IsToolbarSizeDefault)
{
@BadgeCount
}
</span>
@if (Parent.Size is ToolbarSize.Medium or ToolbarSize.Small && HasBadgeLabel)
{
@RenderBadgeLabel()
}
</div>
}

<BitIcon IconName="@IconName" />
<span class="@ComputeLabelCssClass()">
@context.ActivatorLabel
@if (Parent.Size is ToolbarSize.Default && HasBadgeLabel)
{
@RenderBadgeLabel()
}
</span>
</button>
</ActivatorTemplate>
<ChildContent>
@ChildContent
</ChildContent>
</BitDropdown>
</li>
76 changes: 76 additions & 0 deletions src/BitBlazor/Components/Toolbar/BitToolbarDropdownItem.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using BitBlazor.Core;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

namespace BitBlazor.Components;

/// <summary>
/// Represents a dropdown item that can be used within a <see cref="BitToolbar"/> component.
/// It provides a way to include dropdown functionality in a toolbar, allowing users to select from a list of options or actions.
/// </summary>
Comment thread
albx marked this conversation as resolved.
/// <remarks>
/// Rendered with <see cref="BitDropdown"/>, which requires an interactive render mode (Server, WebAssembly, or Auto) to open and close.
/// Under static SSR (no circuit/WASM runtime attached) the activator cannot be toggled, so the dropdown menu can never be opened.
/// </remarks>
public partial class BitToolbarDropdownItem : BitToolbarItemBase
{
/// <summary>
/// Gets or sets the content to be rendered inside the dropdown item.
/// </summary>
[Parameter]
[EditorRequired]
public RenderFragment ChildContent { get; set; }

/// <summary>
/// Gets or sets the unique identifier for the dropdown item.
/// This ID is used to associate the item with its corresponding dropdown menu for accessibility purposes.
/// </summary>
[Parameter]
[EditorRequired]
public string Id { get; set; } = string.Empty;

private string ComputeActivatorCssClass()
{
var builder = new CssClassBuilder("btn", "btn-dropdown", "dropdown-toggle");

if (Disabled)
{
builder.Add("disabled");
}

return builder.Build();
}

// Merges the ActivatorContext's dropdown-managed attributes with the aria-disabled state set by SetDisabled().
private IDictionary<string, object> ComputeActivatorAttributes(ActivatorContext context)
{
var attributes = new Dictionary<string, object>(context.Attributes);

foreach (var attribute in AdditionalAttributes)
{
attributes[attribute.Key] = attribute.Value;
}

return attributes;
}

private void HandleActivatorClick(ActivatorContext context)
{
if (Disabled)
{
return;
}

context.ToggleDropdown();
}

private async Task HandleActivatorKeyDownAsync(ActivatorContext context, KeyboardEventArgs args)
{
if (Disabled)
{
return;
}

await context.HandleKeyDownAsync(args);
}
}
6 changes: 2 additions & 4 deletions src/BitBlazor/Components/Toolbar/BitToolbarItem.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@namespace BitBlazor.Components

@inherits BitToolbarItemBase

<li>
<a href="@Href" class="@ComputeLinkCssClass()" disabled="@Disabled" @attributes="AdditionalAttributes" @onclick="ClickAsync" @onclick:preventDefault="@RendererInfo.IsInteractive" @onkeydown="OnKeyDownAsync">
@if (HasBadge)
Expand Down Expand Up @@ -27,7 +29,3 @@
</span>
</a>
</li>

@code {
private RenderFragment RenderBadgeLabel() => @<span class="visually-hidden">@BadgeLabel</span>;
}
101 changes: 2 additions & 99 deletions src/BitBlazor/Components/Toolbar/BitToolbarItem.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,11 @@ namespace BitBlazor.Components;
/// <summary>
/// Represents a toolbar item component that can be used within a <see cref="BitToolbar"/> component.
/// </summary>
public partial class BitToolbarItem
public partial class BitToolbarItem : BitToolbarItemBase
{
[CascadingParameter]
BitToolbar Parent { get; set; } = default!;

[Inject]
private NavigationManager NavigationManager { get; set; } = default!;

/// <summary>
/// Gets or sets the label for the toolbar item.
/// </summary>
[Parameter]
[EditorRequired]
public string Label { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the name of the icon to be displayed for the toolbar item.
/// </summary>
[Parameter]
[EditorRequired]
public string IconName { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the URL that the toolbar item should link to.
/// In SSR rendering, the browser follows this URL directly on click.
Expand All @@ -39,30 +22,6 @@ public partial class BitToolbarItem
[Parameter]
public string? Href { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the toolbar item is active. When set to true, the item will be styled as active.
/// </summary>
[Parameter]
public bool Active { get; set; }

/// <summary>
/// Gets or sets whether the toolbar item is disabled. When set to true, the item will be styled as disabled and will not respond to user interactions.
/// </summary>
[Parameter]
public bool Disabled { get; set; }

/// <summary>
/// Gets or sets the count to be displayed as a badge on the toolbar item. If set, a badge will be shown with the specified count.
/// </summary>
[Parameter]
public int? BadgeCount { get; set; }

/// <summary>
/// Gets or sets the label for the badge on the toolbar item. This label can provide additional context for the badge count.
/// </summary>
[Parameter]
public string? BadgeLabel { get; set; }

/// <summary>
/// Gets or sets the primary interactive callback, invoked when the toolbar item is clicked.
/// When set, it takes precedence over <see cref="Href"/> navigation in interactive rendering.
Expand All @@ -71,51 +30,10 @@ public partial class BitToolbarItem
[Parameter]
public EventCallback OnClick { get; set; }

/// <summary>
/// Gets or sets additional attributes that do not match any of the explicitly defined parameters.
/// </summary>
[Parameter(CaptureUnmatchedValues = true)]
public IDictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();

private bool HasBadgeNumber => BadgeCount.HasValue && BadgeCount.Value > 0;

private bool HasBadgeLabel => !string.IsNullOrWhiteSpace(BadgeLabel);

private bool HasBadge => HasBadgeNumber || HasBadgeLabel;

private bool IsToolbarSizeDefault => Parent.Size is ToolbarSize.Default;

/// <inheritdoc/>
protected override void OnInitialized()
{
if (Parent is null)
{
throw new InvalidOperationException("BitToolbarItem component must be used inside a BitToolbar component");
}
}

/// <inheritdoc/>
protected override void OnParametersSet()
{
SetDisabled();
}

private void SetDisabled()
{
if (Disabled)
{
AdditionalAttributes["aria-disabled"] = "true";
}
else
{
AdditionalAttributes.Remove("aria-disabled");
}
}

private string ComputeLinkCssClass()
{
var builder = new CssClassBuilder();

if (Active)
{
builder.Add("active");
Expand All @@ -129,21 +47,6 @@ private string ComputeLinkCssClass()
return builder.Build();
}

private string ComputeLabelCssClass()
{
var builder = new CssClassBuilder();

var labelClass = Parent.Size switch
{
ToolbarSize.Medium or ToolbarSize.Small => "visually-hidden",
_ => "toolbar-label"
};
builder.Add(labelClass);


return builder.Build();
}

private async Task ClickAsync()
{
if (Disabled)
Expand Down
Loading
Loading