Skip to content
Open
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
24 changes: 11 additions & 13 deletions src/TailBlazor.Tabs/ITab.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
using System;
using Microsoft.AspNetCore.Components;

namespace TailBlazor.Tabs
namespace TailBlazor.Tabs;

public interface ITab
{
public interface ITab
{
RenderFragment ChildContent { get; }
RenderFragment TitleContent { get; }
RenderFragment TabContent { get; }
int Id { get; }
string Title { get; }
string ActiveClass { get; set; }
string Class { get; set; }
void Activate();
}
RenderFragment? ChildContent { get; }
RenderFragment? TitleContent { get; }
RenderFragment? TabContent { get; }
int Id { get; }
string? Title { get; }
string? ActiveClass { get; set; }
string? Class { get; set; }
void Activate();
}
22 changes: 9 additions & 13 deletions src/TailBlazor.Tabs/TailBlazor.Tabs.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<PackageId>TailBlazor.Tabs</PackageId>
<RootNamespace>TailBlazor.Tabs</RootNamespace>
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<PackageId>TailBlazor.Tabs</PackageId>
<RootNamespace>TailBlazor.Tabs</RootNamespace>
<Version>1.1.5</Version>
<Authors>Taylor Watson</Authors>
<IsPackage>true</IsPackage>
Expand All @@ -24,13 +24,9 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>

<ItemGroup>
<SupportedPlatform Include="browser" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.1" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.0" />
</ItemGroup>

<ItemGroup>
<None Include="../../logo.png" Pack="true" PackagePath="">
Expand Down
55 changes: 33 additions & 22 deletions src/TailBlazor.Tabs/TailBlazorTab.razor
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@namespace TailBlazor.Tabs
@implements ITab

<span @onclick="@Activate" aria-label="@(AriaLabel)" @onclick:preventDefault
<span @onclick="ActivateAsync" aria-label="@AriaLabel" @onclick:preventDefault="true"
class="@(ContainerTabs.ActiveTab == this ? ActiveClass : Class)">
@if (TitleContent != null) {
@TitleContent
Expand All @@ -10,39 +10,50 @@
}
</span>

@code {

@functions {

[CascadingParameter] public TailBlazorTabs ContainerTabs { get; set; }
[Parameter] public string ActiveClass { get; set; }
[Parameter] public string Class { get; set; }
[Parameter] public string Title { get; set; }
[Parameter] public string AriaLabel { get; set; }
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public RenderFragment TitleContent { get; set; }
[Parameter] public RenderFragment TabContent { get; set; }
[CascadingParameter] public TailBlazorTabs ContainerTabs { get; set; } = default!;
[Parameter] public string? ActiveClass { get; set; }
[Parameter] public string? Class { get; set; }
[Parameter] public string? Title { get; set; }
[Parameter] public string? AriaLabel { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public RenderFragment? TitleContent { get; set; }
[Parameter] public RenderFragment? TabContent { get; set; }
[Parameter] public EventCallback<ITab> OnClickCallback { get; set; }

public int Id { get; private set; }

protected override void OnInitialized()
{
if (String.IsNullOrEmpty(Class))
Class = ContainerTabs.DefaultTabClass;
if (String.IsNullOrEmpty(ActiveClass))
ActiveClass = ContainerTabs.DefaultTabActiveClass;
if (String.IsNullOrEmpty(AriaLabel))
AriaLabel = Title;

Id = ContainerTabs.Tabs.Count();
Class ??= ContainerTabs.DefaultTabClass;
ActiveClass ??= ContainerTabs.DefaultTabActiveClass;
AriaLabel ??= Title ?? string.Empty;

Id = ContainerTabs.Tabs.Count;
// make sure at least one tab is active
ContainerTabs.AddTab(this);
}

public void Activate()
{
ContainerTabs.SetActivateTab(this);
OnClickCallback.InvokeAsync(this.ContainerTabs.ActiveTab);
StateHasChanged();
_ = NotifyActivationAsync();
}

private async Task ActivateAsync()
{
ContainerTabs.SetActivateTab(this);
await NotifyActivationAsync();
}

private async Task NotifyActivationAsync()
{
if (OnClickCallback.HasDelegate)
{
await OnClickCallback.InvokeAsync(ContainerTabs.ActiveTab);
}

await InvokeAsync(StateHasChanged);
}
}
68 changes: 44 additions & 24 deletions src/TailBlazor.Tabs/TailBlazorTabs.razor
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
@namespace TailBlazor.Tabs

<CascadingValue Value=this>
<div class="@(NavWrapperClass)">
<nav class="@Class" aria-label="@(AriaLabel)">
<CascadingValue Value="this">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Badge Cascade passes string literal instead of component instance

The <CascadingValue> declaration now sets Value="this". Adding quotes turns the expression into the literal string "this", so no TailBlazorTabs instance is provided to descendants. As a result TailBlazorTab’s [CascadingParameter] remains null, and OnInitialized will throw when it tries to access ContainerTabs.DefaultTabClass, breaking tab initialization. The attribute should remain Value=this (without quotes) so the component instance is cascaded.

Useful? React with 👍 / 👎.

<div class="@NavWrapperClass">
<nav class="@Class" aria-label="@AriaLabel">
@ChildContent
</nav>
</div>
</CascadingValue>

<div class="@(TabsWrapperClass)">
<div class="@TabsWrapperClass">
@(ActiveTab?.TabContent ?? ActiveTab?.ChildContent)
</div>

@code {

[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string NavWrapperClass { get; set; }
[Parameter] public string TabsWrapperClass { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public string? NavWrapperClass { get; set; } = string.Empty;
[Parameter] public string? TabsWrapperClass { get; set; } = string.Empty;
[Parameter] public string Class { get; set; } = "flex space-x-8 w-full";
[Parameter] public string AriaLabel { get; set; } = "Tabs";
[Parameter] public EventCallback<ITab> OnClickCallback { get; set; }
[Parameter] public EventCallback<int> SelectedTabChanged { get; set; }
[Parameter] public EventCallback<int> SelectedTabChanged { get; set; }
[Parameter] public string DefaultTabClass { get; set; } = "px-3 py-2 font-medium text-sm rounded-md cursor-pointer text-gray-500 hover:text-gray-700";
[Parameter] public string DefaultTabActiveClass { get; set; } = "px-3 py-2 font-medium text-sm rounded-md cursor-pointer bg-gray-300 text-gray-700";
[Parameter] public int SelectedTab {
Expand All @@ -38,17 +38,17 @@
}
}

private int _selectedTabId;
public ITab ActiveTab { get; private set; }
public List<ITab> Tabs { get; private set; } = new List<ITab>();
private int _selectedTabId = -1;
public ITab? ActiveTab { get; private set; }
public List<ITab> Tabs { get; } = new();

public void AddTab(ITab tab)
{
Tabs.Add(tab);
if (ActiveTab == null)
{
SetActivateTab(tab);
}
Tabs.Add(tab);
}

public void RemoveTab(ITab tab)
Expand All @@ -60,25 +60,45 @@
Tabs.Remove(tab);
}

public void SetActivateTab(ITab tab, bool forceTabActivate = false)
public void SetActivateTab(ITab? tab, bool forceTabActivate = false)
{
if (tab is null)
{
if (ActiveTab is null)
{
return;
}

if (ActiveTab == tab) return;
ActiveTab = tab;
SelectedTab = tab.Id;
try
ActiveTab = null;
_selectedTabId = -1;
InvokeAsync(StateHasChanged);
return;
}

if (ActiveTab == tab && !forceTabActivate)
{
return;
}

if (forceTabActivate)
ActiveTab.Activate();
var selectionChanged = _selectedTabId != tab.Id;
ActiveTab = tab;
_selectedTabId = tab.Id;

OnClickCallback.InvokeAsync(tab);
// Cannot process pending renders after the renderer has been disposed? try/catch hides this but gotta fix
InvokeAsync(() => { StateHasChanged(); });
if (selectionChanged && SelectedTabChanged.HasDelegate)
{
_ = SelectedTabChanged.InvokeAsync(tab.Id);
}
catch (Exception exception)

if (forceTabActivate)
{
Console.WriteLine(exception.Message);
tab.Activate();
}

if (OnClickCallback.HasDelegate)
{
_ = OnClickCallback.InvokeAsync(tab);
}

InvokeAsync(StateHasChanged);
}
}
4 changes: 3 additions & 1 deletion src/TailBlazor.Tabs/_Imports.razor
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web
@using System.Linq
@using System.Threading.Tasks