Skip to content

[dev-v5] FluentTreeView #3802

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 35 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
957037a
Add first classes
dvoituron May 14, 2025
fb913c5
Add events
dvoituron May 14, 2025
ad8017f
Register the TreeView
dvoituron May 14, 2025
bb3cf69
Update the registering of TreeDefinition
dvoituron May 15, 2025
7084f5e
Add Expanded and Selected events
dvoituron May 15, 2025
5cac84a
Add SelecterdId and AsideTemplate
dvoituron May 16, 2025
71d008e
Add Ellipsis
dvoituron May 16, 2025
7f53c1d
Add Height
dvoituron May 16, 2025
1c53641
Add IconStart, IconEnd and IconAside
dvoituron May 16, 2025
686f9ef
Add Items (not completed)
dvoituron May 16, 2025
19d21a1
Update the Items
dvoituron May 16, 2025
2f98de3
Add SetExpandedAsync
dvoituron May 16, 2025
65e508c
Add doc
dvoituron May 16, 2025
fe48316
Add events
dvoituron May 17, 2025
7cefad6
Add new example (to updateà
dvoituron May 17, 2025
703dc30
Update
dvoituron May 19, 2025
9cecbf1
Add Unlimited example
dvoituron May 19, 2025
97d81cc
Add MultiSelection sample
dvoituron May 19, 2025
6fd5be0
Add Multiple SelectedItems
dvoituron May 19, 2025
d63633a
Add doc
dvoituron May 19, 2025
a7063c3
Update Multiple to SelectionMode
dvoituron May 19, 2025
87929ae
Remove the incorrect usage of InternalItem
dvoituron May 19, 2025
2e3583f
Update doc
dvoituron May 22, 2025
9740018
Add MultipleSelectionVisibility
dvoituron May 22, 2025
57bab3a
Add pointer-events: none; and Update the samples
dvoituron May 22, 2025
af1dae2
Add Migration page
dvoituron May 22, 2025
75bb50e
Use the SDK 9.0.203
dvoituron May 22, 2025
a9bd687
Add Unit Tests
dvoituron May 22, 2025
6e7806f
Add Unit Tests
dvoituron May 23, 2025
c6c568b
Add Unit Tests
dvoituron May 23, 2025
b42b746
Add unit Tests
dvoituron May 23, 2025
c2e4e23
Add unit Tests
dvoituron May 23, 2025
69a52a2
Add unit tests
dvoituron May 23, 2025
d5a3e27
Update doc
dvoituron May 23, 2025
eac509b
Update sln
dvoituron May 23, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/build-core-lib.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ jobs:
- name: Setup .NET 9.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
dotnet-version: 9.0.203 # Waiting a fix in the 9.0.300: https://github.com/dotnet/sdk/issues/49038
dotnet-quality: ga

# Build
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
@page "/treeview/debug"

<h1>TreeView Debug</h1>

<div>
SelectedId = @SelectedId;
</div>

<div>
Expanded1 = @Expanded1;
Expanded2 = @Expanded2;
Expanded3 = @Expanded3;
Expanded4 = @Expanded4;
</div>

<button onclick="Expanded(true)">Open</button>
<button onclick="Expanded(false)">Close</button>
<button @onclick="@(e => SelectedId = "test-3")">Select #3</button>

<FluentTreeView @bind-SelectedId="@SelectedId"
OnExpandedChanged="@(e => Console.WriteLine($"* Expanded: {e.Id}: {e.Expanded}"))"
OnSelectedChanged="@(e => Console.WriteLine($"* Selected: {e.Id}"))">

<FluentTreeItem Id="test-1"
Text="My item"
Height="70px"
@bind-Expanded="@Expanded1"
IconStart="@(new Icons.Regular.Size16.ShareScreenStart())"
IconEnd="@(new Icons.Regular.Size16.CallEnd())"
IconAside="@(new Icons.Regular.Size16.AddCircle())"
SelectedChanged="@(e => Console.WriteLine($". Selected: test-1: {e}"))">
<FluentTreeItem Height="unset">Sub Item</FluentTreeItem>
</FluentTreeItem>

<FluentTreeItem Id="test-2"
Text="CollapseOnly with really long tree-item content goes here. Lorem ipsum dolor sit amet."
IconStart="@(new Icons.Regular.Size16.ShareScreenStart())"
IconEnd="@(new Icons.Regular.Size16.CallEnd())"
IconAside="@(new Icons.Regular.Size16.AddCircle())"
IconCollapsed="@(new Icons.Regular.Size16.ArrowCircleRight())"
@bind-Expanded="@Expanded2"
SelectedChanged="@(e => Console.WriteLine($". Selected: test-2: {e}"))">
<FluentTreeItem>Sub Item</FluentTreeItem>
</FluentTreeItem>

<FluentTreeItem Id="test-3"
IconExpanded="@(new Icons.Regular.Size16.CopyArrowRight())"
@bind-Expanded="@Expanded3"
SelectedChanged="@(e => Console.WriteLine($". Selected: test-3: {e}"))">
ExpandOnly
<FluentTreeItem>Sub Item</FluentTreeItem>
</FluentTreeItem>

<FluentTreeItem Id="test-4"
Text="CollapseExpand"
IconCollapsed="@(new Icons.Regular.Size16.ArrowCircleRight())"
IconExpanded="@(new Icons.Regular.Size16.CopyArrowRight())"
@bind-Expanded="@Expanded4"
SelectedChanged="@(e => Console.WriteLine($". Selected: test-4: {e}"))">
<FluentTreeItem>Sub Item</FluentTreeItem>
</FluentTreeItem>
</FluentTreeView>

<FluentDivider>Other section</FluentDivider>

<script>

function Expanded(value) {
document.getElementById('test-1').expanded = value;
document.getElementById('test-2').expanded = value;
document.getElementById('test-3').expanded = value;
document.getElementById('test-4').expanded = value;
}
</script>

@code
{
string SelectedId = "test-2";
bool Expanded1 = true;
bool Expanded2 = false;
bool Expanded3 = true;
bool Expanded4 = false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// ------------------------------------------------------------------------
// MIT License - Copyright (c) Microsoft Corporation. All rights reserved.
// ------------------------------------------------------------------------

using Microsoft.FluentUI.AspNetCore.Components;
using FluentUI.Demo.SampleData;
using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons;

namespace FluentUI.Demo.Client.Documentation.Components.TreeView.Examples;

public static class SampleDataExtension
{
/// <summary>
/// Converts a collection of <see cref="People.Company"/> objects into a hierarchical collection
/// of <see cref="TreeViewItem"/> objects.
/// </summary>
public static IEnumerable<TreeViewItem> ToTreeViewItems(this IEnumerable<People.Company> organization, bool includeIcons = false)
{
return organization.Select(company => new TreeViewItem
{
IconStart = includeIcons ? new Icons.Regular.Size16.BuildingBank().WithColor(Color.Primary) : null,
Id = company.Id,
Text = company.Name,
Items = company.Departments.Select(dept => new TreeViewItem
{
IconStart = includeIcons ? new Icons.Regular.Size16.ContactCardGroup().WithColor(SystemColors.Palette.DarkOrangeForeground1) : null,
Id = dept.Id,
Text = dept.Name,
Items = dept.Employees.Select(emp => new TreeViewItem
{
IconStart = includeIcons ? new Icons.Regular.Size16.PersonVoice().WithColor(SystemColors.Palette.ForestBorderActive) : null,
Id = emp.Id,
Text = $"{emp.FirstName} {emp.LastName}",
}).ToArray()
}).ToArray()
}).ToArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<p>Current selected tree item is <b>@SelectedId - @SelectedItem?.Text</b></p>
<p>Most recently expanded/collapsed tree item is <b>@ExpandedItem?.Text</b></p>

<FluentTreeView @bind-SelectedId="@SelectedId"
@bind-CurrentSelected="SelectedItem"
OnExpandedChanged="@(item => ExpandedItem = item)">
<FluentTreeItem Id="Item1" Text="Root item 1">
<FluentTreeItem Id="Item11" Text="Flowers" IconStart="@(new Icons.Regular.Size16.LeafOne())">
<FluentTreeItem Id="Item111" Text="Daisy" />
<FluentTreeItem Id="Item112" Text="Sunflower" />
<FluentTreeItem Id="Item113" Text="Rose" />
</FluentTreeItem>
<FluentTreeItem Id="Item12" Text="Nested item 2" />
<FluentTreeItem Id="Item13" Text="Nested item 3" />
</FluentTreeItem>
</FluentTreeView>

@code {
string? SelectedId;
FluentTreeItem? SelectedItem;
FluentTreeItem? ExpandedItem;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div class="my-2">
<b>Selected item:</b> @SelectedItem?.Text
</div>

<FluentTreeView Items="@Items"
@bind-SelectedItem="@SelectedItem">
<ItemTemplate>
<FluentBadge Color="BadgeColor.Informative" Content="@context.Id" Style="pointer-events: none;" />
@context.Text
</ItemTemplate>
</FluentTreeView>

@code
{
private ITreeViewItem? SelectedItem;
private IEnumerable<ITreeViewItem>? Items = new List<ITreeViewItem>();

// Read the Tree content and set the selected item
protected override void OnInitialized()
{
Items = GetCompanyOrganization();
SelectedItem = Items?.ElementAt(3);
}

// Example of a tree with a company organization
// (5 companies containing 4 departments with 10 employees)
private TreeViewItem[] GetCompanyOrganization()
{
return SampleData.People
.GetOrganization(companyCount: 5, departmentCount: 4, employeeCount: 10)
.ToTreeViewItems()
.ToArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<div class="my-2">
<b>Selected item:</b> @SelectedItem?.Text
</div>

<FluentTreeView Items="@Items"
@bind-SelectedItem="@SelectedItem" />

@code
{
private ITreeViewItem? SelectedItem;
private IEnumerable<ITreeViewItem>? Items = new List<ITreeViewItem>();

// Read the Tree content and set the selected item
protected override void OnInitialized()
{
Items = GetCompanyOrganization();
SelectedItem = Items?.ElementAt(3);
}

// Example of a tree with a company organization
// (5 companies containing 4 departments with 10 employees)
private TreeViewItem[] GetCompanyOrganization()
{
return SampleData.People
.GetOrganization(companyCount: 5, departmentCount: 4, employeeCount: 10)
.ToTreeViewItems()
.ToArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<div class="my-2">
<b>Selected items:</b> @(string.Join("; ", SelectedItems?.Select(i => i.Text) ?? []))
</div>

<FluentTreeView Items="@Items"
HideSelection="true"
SelectionMode="TreeSelectionMode.Multiple"
@bind-SelectedItems="@SelectedItems">
</FluentTreeView>

@code
{
private IEnumerable<ITreeViewItem>? SelectedItems;
private IEnumerable<ITreeViewItem>? Items = new List<ITreeViewItem>();

// Read the Tree content
protected override void OnInitialized()
{
Items = GetCompanyOrganization();
SelectedItems = Items.Take(2);
}

// Example of a tree with a company organization
// (5 companies containing 4 departments with 10 employees)
private TreeViewItem[] GetCompanyOrganization()
{
return SampleData.People
.GetOrganization(companyCount: 5, departmentCount: 4, employeeCount: 10)
.ToTreeViewItems(includeIcons: false)
.ToArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<div class="my-2">
<b>Selected items:</b> @(string.Join("; ", SelectedItems?.Select(i => i.Text) ?? []))
</div>

<FluentTreeView Items="@Items"
HideSelection="true"
SelectionMode="TreeSelectionMode.Multiple"
MultipleSelectionVisibility="@GetTreeSelectionVisibility"
@bind-SelectedItems="@SelectedItems">
</FluentTreeView>

@code
{
private IEnumerable<ITreeViewItem>? SelectedItems;
private IEnumerable<ITreeViewItem>? Items = new List<ITreeViewItem>();

// Read the Tree content
protected override void OnInitialized()
{
Items = GetCompanyOrganization();
}

// Example of a custom visibility function
private TreeSelectionVisibility GetTreeSelectionVisibility(ITreeViewItem item)
{
return item.Id.First() switch
{
// Company or Department => collapsed checkbox
'C' => TreeSelectionVisibility.Collapse,
'D' => TreeSelectionVisibility.Hidden,

// Employee or others => visible checkbox
'E' => TreeSelectionVisibility.Visible,
_ => TreeSelectionVisibility.Visible
};
}

// Example of a tree with a company organization
// (5 companies containing 4 departments with 10 employees)
private TreeViewItem[] GetCompanyOrganization()
{
return SampleData.People
.GetOrganization(companyCount: 5, departmentCount: 4, employeeCount: 10)
.ToTreeViewItems(includeIcons: false)
.ToArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<div class="my-2">
<b>Selected item:</b> @SelectedItem?.Text
</div>

<FluentTreeView Items="@Items"
LazyLoadItems="true"
@bind-SelectedItem="@SelectedItem" />

@code
{
private ITreeViewItem? SelectedItem;
private IEnumerable<ITreeViewItem>? Items = new List<ITreeViewItem>();

protected override async Task OnInitializedAsync()
{
Items = await GetItemsAsync();
}

// Generate a random number of items
// Including a "Fake" sub-item to simulate the [+]
private async Task<IEnumerable<ITreeViewItem>> GetItemsAsync()
{
await Task.Delay(300); // Simulate a delay for loading items

var nbItems = Random.Shared.Next(3, 9);

return Enumerable.Range(1, nbItems)
.Select(i => new TreeViewItem()
{
Text = $"Item {Random.Shared.Next(1, 9999)}",
OnExpandedAsync = OnExpandedAsync,
Items = TreeViewItem.LoadingTreeViewItems("Loading..."), // "Fake" sub-item to simulate the [+]
}).ToArray();
}

// Handle the expanded event to load items
private async Task OnExpandedAsync(TreeViewItemExpandedEventArgs e)
{
if (e.Expanded)
{
e.CurrentItem.Items = await GetItemsAsync();
}
else
{
// Remove sub-items and add a "Fake" item to simulate the [+]
e.CurrentItem.Items = TreeViewItem.LoadingTreeViewItems("Loading...");
}
}
}
Loading
Loading