Skip to content
Merged
1 change: 1 addition & 0 deletions BitBlazor.sln
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "components", "components",
docs\components\alert.md = docs\components\alert.md
docs\components\avatar.md = docs\components\avatar.md
docs\components\badge.md = docs\components\badge.md
docs\components\bottomnav.md = docs\components\bottomnav.md
docs\components\breadcrumb.md = docs\components\breadcrumb.md
docs\components\button-badge.md = docs\components\button-badge.md
docs\components\button.md = docs\components\button.md
Expand Down
5 changes: 5 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ Small and adaptable labels for adding information.
- Support for rounded shapes
- Various colors available

#### [BottomNav](components/bottomnav.md)
Mobile-optimized bottom navigation bar to show the current location or quick actions.
- Support for the customization of items, icons, alerts and badges
- Accessibility support

#### [Breadcrumb](components/breadcrumb.md)
Breadcrumb navigation component to display the current location within a hierarchy.
- Support for the customization of items and the separator
Expand Down
107 changes: 107 additions & 0 deletions docs/components/bottomnav.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# BitBottomNav

The `BitBottomNav` component renders a [mobile-optimized bottom navigation bar](https://italia.github.io/bootstrap-italia/docs/menu-di-navigazione/bottomnav/) to show the current location or quick actions. It supports icons, badges, alert indicators and accessibility-friendly labels for each item.

## Namespace

```csharp
BitBlazor.Components
```

## Description

BitBottomNav displays a horizontal list of navigation entries optimized for bottom placement (common on mobile apps). Each entry is a BitBottomNavItem that can render:
- a text,
- an icon (via BitIcon),
- an optional badge (text/number),
- an optional alert indicator (dot) when no badge is present,
- an active state applied via the IsActive flag.

Items are rendered as anchor elements (`<a>`) so they are keyboard-focusable and navigable. Visual styling is provided by the consuming app's stylesheet; the component emits semantic classes the CSS targets.

## Parameters

| Name | Type | Required | Default | Description |
|-----------|--------------------------------------|----------|---------|-----------------------------------------------------------------------------|
| Items | IReadOnlyList<BitBottomNavItem> | ✗ | `Enumerable.Empty<BitBottomNavItem>().ToList()` | The collection of items to render in the bottom navigation. |

## BitBottomNavItem

Each item in Items is a `BitBottomNavItem`. Main properties:

| Property | Type | Default | Description |
|------------|----------|---------------------|---------------------------------------------------------|
| `Text` | `string` | `string.Empty` | Visible label for the item. |
| `Icon` | `string?` | `null` | Optional icon name (rendered via `BitIcon`). |
| `Link` | `string?` | `null` | Optional URL for the item (<a href="...">). |
| `BadgeText` | `string?` | `null` | Optional badge text; when present renders a badge element. |
| `BadgeAriaLabel` | `string?` | `null` | Optional ARIA label for the badge/alert; rendered in a visually-hidden span. |
| `IsActive` | `bool` | `false` | If `true`, the item gets an active class (indicates current/selected item). |
| `IsAlertActive` | `bool` | `false` | When `true` and no `BadgeText` is set, a small alert indicator (dot) is rendered. |

Helper methods on `BitBottomNavItem`:
- `HasBadge()` — `true` when `BadgeText` is non-empty.
- `HasAriaLabel()` — `true` when (badge or alert) and `BadgeAriaLabel` is non-empty.
- `HasIcon()` — `true` when `Icon` is non-empty.

## Usage Examples

### Default

C# (prepare items):

```csharp
var items = new List<BitBottomNavItem>
{
new BitBottomNavItem { Text = "Messages", Link = "#", Icon = BitBlazor.Utilities.Icons.ItComment, BadgeText = "1", BadgeAriaLabel = "to read" },
new BitBottomNavItem { Text = "Images", Link = "#", Icon = BitBlazor.Utilities.Icons.ItCamera, BadgeText = "2", BadgeAriaLabel = "to view" },
new BitBottomNavItem { Text = "Documents", Link = "#", Icon = BitBlazor.Utilities.Icons.ItFile, BadgeText = "42", BadgeAriaLabel = "to examine" },
new BitBottomNavItem { Text = "Favorites", Link = "#", Icon = BitBlazor.Utilities.Icons.ItStarOutline, IsActive = true },
new BitBottomNavItem { Text = "Settings", Link = "#", Icon = BitBlazor.Utilities.Icons.ItSettings, IsAlertActive = true }
};
```

Razor (render component):

```razor
<BitBottomNav Items="@items" />
```

## Common scenarios:
- Numeric/text badge: set `BadgeText` = "7" and optionally `BadgeAriaLabel` = "7 unread messages".
- Alert indicator (dot): set `IsAlertActive` = true and leave `BadgeText` empty.
- Icon-only visual with accessible status: supply Icon, Text, and a `BadgeAriaLabel` if there is a badge or alert.

## Accessibility
- When a badge or alert is present and BadgeAriaLabel is provided, the label is rendered inside a visually-hidden span next to the visible label so assistive technologies receive the context (for example, "3 unread notifications").
- Items render as anchors (<a>), providing keyboard focus and native navigation behavior. Supply meaningful Link values.
- Provide concise and descriptive `Text` and `BadgeAriaLabel` strings to maximize clarity for screen reader users.

## Generated CSS Classes

- `bottom-nav` — root container on the <nav> element.
- `active` — applied to an item's <a> when IsActive is true.
- `badge-wrapper` — wrapper for badge or alert element.
- `bottom-nav-badge` — class for the badge text element.
- `bottom-nav-alert` — class for the small alert indicator (dot).
- `bottom-nav-label` — wrapper for the visible item text.
- `visually-hidden` — used for accessible text that should be hidden visually.

## Generated HTML Structure (approximate)

```html
<nav class="bottom-nav">
<ul>
<li><a href="#" class=""><div class="badge-wrapper"><span class="bottom-nav-badge">1</span></div><svg class="icon"><use href="/_content/BitBlazor/bootstrap-italia/svg/sprites.svg#it-comment"></use></svg><span class="bottom-nav-label">Messages <span class="visually-hidden">to read</span></span></a></li>
<li><a href="#" class=""><div class="badge-wrapper"><span class="bottom-nav-badge">2</span></div><svg class="icon"><use href="/_content/BitBlazor/bootstrap-italia/svg/sprites.svg#it-camera"></use></svg><span class="bottom-nav-label">Images <span class="visually-hidden">to view</span></span></a></li>
<li><a href="#" class=""><div class="badge-wrapper"><span class="bottom-nav-badge">42</span></div><svg class="icon"><use href="/_content/BitBlazor/bootstrap-italia/svg/sprites.svg#it-file"></use></svg><span class="bottom-nav-label">Documents <span class="visually-hidden">to examine</span></span></a></li>
<li><a href="#" class=""><svg class="icon"><use href="/_content/BitBlazor/bootstrap-italia/svg/sprites.svg#it-star-outline"></use></svg><span class="bottom-nav-label">Favorites</span></a></li>
<li><a href="#" class=""><svg class="icon"><use href="/_content/BitBlazor/bootstrap-italia/svg/sprites.svg#it-settings"></use></svg><span class="bottom-nav-label">Settings</span></a></li>
</ul>
</nav>
```

## Notes
- If Items is empty the component renders an empty list by default.
- The component does not enforce positional semantics for first/last items; the ordering and the active item are controlled by the provided Items and each item's `IsActive` flag.
- Visual appearance depends on the app's stylesheet. Ensure your project includes the BitBlazor CSS or defines styles for the classes listed above.
5 changes: 5 additions & 0 deletions docs/quick-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ This guide provides a quick overview of all BitBlazor components with basic exam
Rounded="true" />
```

### BottomNav
```razor
<BitBottomNav Items=@items" />
```

### Button
```razor
<!-- Simple button -->
Expand Down
17 changes: 17 additions & 0 deletions src/BitBlazor/Components/BottomNav/BitBottomNav.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@namespace BitBlazor.Components

@inherits BitComponentBase

<!-- BitBottomNav renders a bottom navigation component to display the current location in a mobile-optimized way.
The component supports customization of items, badges, alerts and accessibility features. -->

<nav class="@ComputeCssClasses()" @attributes="AdditionalAttributes">
<ul>
@foreach (var item in Items)
{
<li>
<BitBottomNavItem Item="item" />
</li>
}
</ul>
</nav>
28 changes: 28 additions & 0 deletions src/BitBlazor/Components/BottomNav/BitBottomNav.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using BitBlazor.Core;
using Microsoft.AspNetCore.Components;

namespace BitBlazor.Components;

/// <summary>
/// BitBottomNav renders a bottom navigation component to display the current location in a mobile-optimized way.
/// The component supports customization of items, badges, alerts and accessibility features.
/// </summary>
public partial class BitBottomNav : BitComponentBase
{
/// <summary>
/// Gets or sets the items shown on the bottom navigation.
/// </summary>
[Parameter]
public IReadOnlyList<BitBottomNavItem> Items { get; set; } = Enumerable.Empty<BitBottomNavItem>().ToList();

private string ComputeCssClasses()
{
var builder = new CssClassBuilder("bottom-nav");

AddCustomCssClass(builder);

return builder.Build();
}


}
51 changes: 51 additions & 0 deletions src/BitBlazor/Components/BottomNav/BitBottomNavItem.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
@namespace BitBlazor.Components
@if (item != null)
{
<a href="@item.Href" @onclick="ClickAsync" @onclick:preventDefault="@RendererInfo.IsInteractive" class="@(item.IsActive ? "active" : string.Empty)">
@if (item.HasBadge())
{
<div class="badge-wrapper"><span class="bottom-nav-badge">@item.BadgeText</span></div>
}
@if (item.IsAlertActive && !item.HasBadge())
{
<div class="badge-wrapper"><span class="bottom-nav-alert"></span></div>
}
@if (item.HasIcon())
{
<BitIcon IconName="@item.Icon"></BitIcon>
}
@if (item.HasAriaLabel())
{
<span class="bottom-nav-label">@item.Text <span class="visually-hidden">@item.BadgeAriaLabel</span></span>
}
else
{
<span class="bottom-nav-label">@item.Text</span>
}
</a>
}
@code{
[CascadingParameter]
BitBottomNav Parent { get; set; } = default!;

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

[Parameter]
public BitBottomNavItem item { get; set; } = default!;

private async Task ClickAsync()
{
//if (Disabled)
// return;

if (OnClick.HasDelegate)
{
await OnClick.InvokeAsync();
}
else if (Href is not null)
{
NavigationManager.NavigateTo(Href);
}
}
}
72 changes: 72 additions & 0 deletions src/BitBlazor/Components/BottomNav/BitBottomNavItem.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using Microsoft.AspNetCore.Components;

namespace BitBlazor.Components;

/// <summary>
/// Represents an item in a <see cref="BitBottomNav"/> navigation component.
/// </summary>
public partial class BitBottomNavItem
{
/// <summary>
/// Gets or sets the text to display for the item.
/// </summary>
public string Text { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the icon name to display before the item.
/// </summary>
public string? Icon { get; set; }

/// <summary>
/// Gets or sets the URL that the toolbar item should link to.
/// In SSR rendering, the browser follows this URL directly on click.
/// In interactive rendering, this URL is used as a navigation fallback when <see cref="OnClick"/> has no delegate,
/// and as a secondary browser behavior target (right-click, Ctrl+Click) when <see cref="OnClick"/> is set.
/// When both <see cref="Href"/> and <see cref="OnClick"/> are set, <see cref="OnClick"/> takes precedence for primary interaction.
/// </summary>
public string? Href { get; set; }

/// <summary>
/// Gets or sets the badge text.
/// </summary>
public string? BadgeText { get; set; }

/// <summary>
/// Gets or sets the ARIA label.
/// </summary>
public string? BadgeAriaLabel { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the item is active.
/// </summary>
public bool IsActive { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the item has an alert to display.
/// </summary>
public bool IsAlertActive { 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.
/// Not invoked during static (SSR) rendering — provide <see cref="Href"/> as a navigation fallback for SSR contexts.
/// </summary>
[Parameter]
public EventCallback OnClick { get; set; }

/// <summary>
/// Gets a value indicating whether the item has a badge to display.
/// </summary>
public bool HasBadge() => !string.IsNullOrEmpty(BadgeText);

/// <summary>
/// Gets a value indicating whether the item's badge has an ARIA label.
/// </summary>
public bool HasAriaLabel() => (HasBadge() || IsAlertActive) && !string.IsNullOrEmpty(BadgeAriaLabel);

/// <summary>
/// Gets a value indicating whether the item has an icon.
/// </summary>
/// <returns><code>true</code> if the item has an icon specified, <code>false</code> otherwise</returns>
public bool HasIcon() => !string.IsNullOrEmpty(Icon);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
@using BitBlazor.Stories.Layouts
@attribute [Stories("Components/BitBottomNav")]

<Stories TComponent="BitBottomNav" Layout="typeof(SimpleMobileViewPort)">
<Story Name="Default">
<Arguments>
</Arguments>
<Template>
<BitBottomNav Items="@_items.Take(3).ToList()" />
</Template>
</Story>

<Story Name="Example with four buttons">
<Arguments>
</Arguments>
<Template>
<BitBottomNav Items="@_items.Take(4).ToList()" />
</Template>
</Story>

<Story Name="Badges">
<Arguments>
</Arguments>
<Template>
<BitBottomNav Items="@_itemsWithBadges" />
</Template>
</Story>

<Story Name="Alerts">
<Arguments>
</Arguments>
<Template>
<BitBottomNav Items="@_itemsWithAlerts" />
</Template>
</Story>
</Stories>

@code
{
private List<BitBottomNavItem> _items = new()
{
new BitBottomNavItem { Text = "Messages", Href = "#", Icon = BitBlazor.Utilities.Icons.ItComment },
new BitBottomNavItem { Text = "Images", Href = "#", Icon = BitBlazor.Utilities.Icons.ItCamera },
new BitBottomNavItem { Text = "Documents", Href = "#", Icon = BitBlazor.Utilities.Icons.ItFile },
new BitBottomNavItem { Text = "Favorites", Href = "#", Icon = BitBlazor.Utilities.Icons.ItStarOutline },
new BitBottomNavItem { Text = "Settings", Href = "#", Icon = BitBlazor.Utilities.Icons.ItSettings }
};

private List<BitBottomNavItem> _itemsWithBadges = new()
{
new BitBottomNavItem { Text = "Messages", Href = "#", Icon = BitBlazor.Utilities.Icons.ItComment, BadgeText="1", BadgeAriaLabel="to read" },
new BitBottomNavItem { Text = "Images", Href = "#", Icon = BitBlazor.Utilities.Icons.ItCamera, BadgeText="2", BadgeAriaLabel="to view" },
new BitBottomNavItem { Text = "Documents", Href = "#", Icon = BitBlazor.Utilities.Icons.ItFile, BadgeText="42", BadgeAriaLabel="to examine" },
new BitBottomNavItem { Text = "Favorites", Href = "#", Icon = BitBlazor.Utilities.Icons.ItStarOutline },
new BitBottomNavItem { Text = "Settings", Href = "#", Icon = BitBlazor.Utilities.Icons.ItSettings }
};

private List<BitBottomNavItem> _itemsWithAlerts = new()
{
new BitBottomNavItem { Text = "Messages", Href = "#", Icon = BitBlazor.Utilities.Icons.ItComment, IsAlertActive=true, BadgeAriaLabel="There are 2 new messages" },
new BitBottomNavItem { Text = "Images", Href = "#", Icon = BitBlazor.Utilities.Icons.ItCamera },
new BitBottomNavItem { Text = "Documents", Href = "#", Icon = BitBlazor.Utilities.Icons.ItFile, IsAlertActive=true, BadgeAriaLabel="There are 42 new documents" },
new BitBottomNavItem { Text = "Favorites", Href = "#", Icon = BitBlazor.Utilities.Icons.ItStarOutline },
new BitBottomNavItem { Text = "Settings", Href = "#", Icon = BitBlazor.Utilities.Icons.ItSettings }
};
}
4 changes: 4 additions & 0 deletions stories/BitBlazor.Stories/Layouts/SimpleMobileViewPort.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@inherits LayoutComponentBase
<div class="bd-example">
@Body
</div>
Loading
Loading