Skip to content
Open
Show file tree
Hide file tree
Changes from 11 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<div style="overflow-x: auto;">
<FluentDataGrid Items="@employees"
Style="min-width: max-content;"
StripedRows="true"
ResizableColumns="true"
DisplayMode="DataGridDisplayMode.Grid">
<PropertyColumn Title="ID" Property="@(e => e.Id)" Width="100px" Pin="DataGridColumnPin.Left" Sortable="true" />
<PropertyColumn Title="Full Name" Property="@(e => e.FullName)" Width="160px" Pin="DataGridColumnPin.Left" Sortable="true" />
<PropertyColumn Title="Department" Property="@(e => e.Department)" Sortable="true" />
<PropertyColumn Title="Location" Property="@(e => e.Location)" Sortable="true" />
<PropertyColumn Title="Start Date" Property="@(e => e.StartDate)" Sortable="true" />
<PropertyColumn Title="Salary" Property="@(e => e.Salary)" Width="120px" Sortable="true" Align="DataGridCellAlignment.End" />
<TemplateColumn Title="Actions" Width="120px" Pin="DataGridColumnPin.Right">
<FluentButton IconStart="@(new Icons.Regular.Size16.Edit())" Appearance="ButtonAppearance.Subtle" Title="Edit" @onclick="@(() => selectedName = context.FullName + " (edit)")" />
<FluentButton IconStart="@(new Icons.Regular.Size16.Delete())" Appearance="ButtonAppearance.Subtle" Title="Delete" @onclick="@(() => selectedName = context.FullName + " (delete)")" />
</TemplateColumn>
</FluentDataGrid>
</div>

@if (!string.IsNullOrEmpty(selectedName))
{
<p style="margin-top: 1rem;">Last action: <strong>@selectedName</strong></p>
}

@code {
string selectedName = string.Empty;

record Employee(int Id, string FullName, string Department, string Location, string StartDate, string Salary);

IQueryable<Employee> employees = new[]
{
new Employee(1, "Denis Voituron", "Engineering", "Brussels", "2019-03-01", "$120,000"),
new Employee(2, "Vincent Baaij", "Engineering", "Amsterdam", "2018-07-15", "$130,000"),
new Employee(3, "Harry Mars", "Executive", "Medina", "1975-04-04", "$1,000,000"),
new Employee(4, "Bruno Styles", "Executive", "Bellevue", "1992-02-17", "$950,000"),
new Employee(5, "Taylor Eilish", "Developer Relations", "Portland", "2007-01-22", "$200,000"),
new Employee(6, "Billie Swift", "Languages", "Seattle", "2005-08-01", "$180,000"),
new Employee(7, "Jacky Bond", "Framework", "Seattle", "2010-06-14", "$190,000"),
new Employee(8, "James Chan", "Framework", "Cambridge", "2009-03-30", "$185,000"),
new Employee(9, "John Cage", "Community", "San Diego", "2011-11-01", "$160,000"),
new Employee(10, "Nick Travolta", "Engineering", "New York", "2016-05-20", "$155,000"),
}.AsQueryable();
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@ component and is the one normally put onto a page. Internally it uses the`<Fluen
the grid. It is possible (technically speaking) to use these components manually, but that is **not** the recommended way of working with the DataGrid.

## Rendering

The DataGrid uses standard HTML table elements for rendering the grid. It supports 2 different display modes through the `DisplayMode`
parameter: `DataGridDisplayMode.Grid` (default) and `DataGridDisplayMode.Table`.

- With the `Grid` mode, the `GridTableColumns` parameter can be
used to specify column widths in fractions. It basically provides an HTML table element with a `display: grid;` style.
- With the `Table` mode, it uses standard HTML table elements and rendering. Column widths are best specified through the `Width` parameter on the columns.

> [!NOTE] Specifically when using `Virtualize`, it is **highly recommended** to use the `Table` display mode as the `Grid` mode can exhibit odd scrolling behavior.


## Accessibility

You can use the <kbd>Arrow</kbd> keys to navigate through a DataGrid. When a header cell is focused and the column is sortable, you can use the
Expand All @@ -45,7 +46,7 @@ again will toggle the sort direction. When `HeaderCellAsButtonWithMenu` is true,
A sort can be removed by right clicking (or by pressing <kbd>Shift</kbd> + <kbd>r</kbd>) on the header column (with exception of
the default sort).

_The minimal width for a sortable column is 75 pixels._
*The minimal width for a sortable column is 75 pixels.*

## Row size

Expand Down Expand Up @@ -76,19 +77,20 @@ The following values can be localized:
- DataGrid_SortMenuDescending
- DataGrid_ToggleNesting


## Using the DataGrid component with EF Core

If you want to use the `FluentDataGrid` with data provided through EF Core, you need to install an additional package so the grid knows how to resolve queries asynchronously for efficiency.

### Installation

Install the package by running the command:

```cshtml
dotnet add package Microsoft.FluentUI.AspNetCore.Components.DataGrid.EntityFrameworkAdapterCopy
```

### Usage

In your `Program.cs` file you need to add the following after the `builder.Services.AddFluentUIComponents();` line:

```csharp
Expand All @@ -100,27 +102,33 @@ builder.Services.AddDataGridEntityFrameworkAdapter();Copy
If you want to use the `FluentDataGrid` with data provided through OData, you need to install an additional package so the grid knows how to resolve queries asynchronously for efficiency.

### Installation

Install the package by running the command:

```cshtml
dotnet add package Microsoft.FluentUI.AspNetCore.Components.DataGrid.ODataAdapterCopy
```

### Usage

In your `Program.cs` file you need to add the following after the `builder.Services.AddFluentUIComponents();` line:

```csharp
builder.Services.AddDataGridODataAdapter();
```

## Examples

The following examples show how to use the DataGrid component in different scenarios:

### Basics

- [Getting started](/DataGrid/GettingStarted)
- [Typical grid usage](/DataGrid/Typical)

### Layout

- [Pinned columns](/DataGrid/PinnedColumns)
- [Loading and empty content](/DataGrid/LoadingAndEmptyContent)
- [Auto fit columns](/DataGrid/AutoFit)
- [Auto items per page](/DataGrid/AutoItemsPerPage)
Expand All @@ -129,24 +137,26 @@ The following examples show how to use the DataGrid component in different scena
- [Table with scrollbars](/DataGrid/TableScrollbars)

### Sorting

- [Custom comparer for sorting](/DataGrid/CustomComparerSort)
- [Custom sorting](/DataGrid/CustomSort)

### Columns

- [Single/Multi select](/DataGrid/MultiSelect)
- [Dynamic columns](/DataGrid/DynamicColumns)
- [Column headers](/DataGrid/HeaderGeneration)
- [Template columns](/DataGrid/TemplateColumns)
- [Template columns 2](/DataGrid/TemplateColumns2)

### Advanced

- [Custom comparer](/DataGrid/CustomComparerSort)
- [Virtualized grid](/DataGrid/Virtualize)
- [Remote data](/DataGrid/RemoteData)
- [Hierarchical grid](/DataGrid/HierarchicalDataGrid)
- [Manual grid](/DataGrid/ManualDataGrid)


## Migrating to v5

{{ INCLUDE File=MigrationFluentDataGrid }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
title: Pinned columns
order: 0095
route: /DataGrid/PinnedColumns
---

# Pinned columns

Columns can be pinned (frozen) to the left or right edge of the grid so that they remain visible
while the user scrolls horizontally through wider datasets.

## Parameters

Set the `Pin` parameter on any `PropertyColumn` or `TemplateColumn`:

| Value | Behavior |
|---|---|
| `DataGridColumnPin.None` | Default — column scrolls normally |
| `DataGridColumnPin.Left` | Column stays anchored to the left edge |
| `DataGridColumnPin.Right` | Column stays anchored to the right edge |

## Rules

* **Explicit pixel width required.** Every pinned column must declare a `Width` in pixels
(e.g. `Width="150px"`). Relative units (`fr`, `%`) are not supported because the browser cannot
determine a fixed sticky offset from them at render time.
* **Left-pinned columns must be contiguous at the start.** Each left-pinned column must
immediately follow another left-pinned column, or be the very first column.
* **Right-pinned columns must be contiguous at the end.** Each right-pinned column must
immediately precede another right-pinned column, or be the very last column.
* Violating any of these rules throws an `ArgumentException` with a descriptive message.

## Scrollable container

Sticky positioning only activates inside a scrollable ancestor. Wrap the grid in a container with
`overflow-x: auto` and give the grid `Style="min-width: max-content;"` so that a horizontal scroll
bar appears when columns overflow the container:

```razor
<div style="overflow-x: auto;">
<FluentDataGrid Items="@employees" Style="min-width: max-content;">
<PropertyColumn Title="ID" Property="@(e => e.Id)" Width="60px" Pin="DataGridColumnPin.Left" />
<PropertyColumn Title="Name" Property="@(e => e.Name)" Width="160px" Pin="DataGridColumnPin.Left" />
<PropertyColumn Title="City" Property="@(e => e.City)" />
<TemplateColumn Title="Actions" Width="120px" Pin="DataGridColumnPin.Right">
...
</TemplateColumn>
</FluentDataGrid>
</div>
```

## Theming the pinned background

Pinned cells receive a solid background to prevent scrolling content from showing through. The
color defaults to `--colorNeutralBackground1` and can be overridden per-grid with the CSS custom
property `--fluent-data-grid-pinned-background`:

```css
.my-grid {
--fluent-data-grid-pinned-background: var(--colorNeutralBackground2);
}
```

## Notes

* Column resizing interacts correctly with sticky offsets — the JavaScript in
`FluentDataGrid.razor.ts` recalculates `left` / `right` values after every resize step via
`UpdatePinnedColumnOffsets`.
* Virtualization and paging are fully compatible because each rendered row's cells carry the
same `position: sticky` styling regardless of which page or scroll position is active.
* In RTL layouts the browser interprets `left` / `right` according to the document direction, so
pinned columns behave correctly without additional configuration.

## Example

Demonstrates pinned (frozen) columns using `Pin="DataGridColumnPin.Left"` and `Pin="DataGridColumnPin.Right"`.
The two leftmost columns and the Actions column remain visible while the rest scroll horizontally.

Wrap the grid in a `<div style="overflow-x: auto;">` container and give the grid a `Style="min-width: max-content;"`
so that the horizontal scroll bar appears.

Pinned columns require an explicit pixel `Width`.

{{ DataGridPinnedColumns }}
1 change: 1 addition & 0 deletions src/Core.Scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
obj/
6 changes: 4 additions & 2 deletions src/Core/Components/DataGrid/Columns/ColumnBase.razor
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@
else if (Grid.HeaderCellAsButtonWithMenu)
{
string? tooltip = Tooltip ? (HeaderTooltip ?? Title) : null;
string headerPopupLayerStyle = $"position: relative; z-index: {ZIndex.DataGridHeaderPopup};";
string headerButtonStyle = $"width: calc(100% - 10px); {headerPopupLayerStyle}";

@if (AnyColumnActionEnabled)
{
<FluentButton Id="@_columnId" Appearance="ButtonAppearance.Subtle" Class="col-sort-button" Style="width: calc(100% - 10px);" @onclick="@HandleColumnHeaderClickedAsync" aria-label="@tooltip" title="@tooltip" @oncontextmenu="@(() => Grid.RemoveSortByColumnAsync(this))">
<FluentButton Id="@_columnId" Appearance="ButtonAppearance.Subtle" Class="col-sort-button" Style="@headerButtonStyle" @onclick="@HandleColumnHeaderClickedAsync" aria-label="@tooltip" title="@tooltip" @oncontextmenu="@(() => Grid.RemoveSortByColumnAsync(this))">
<div class="col-title-text" title="@tooltip">
@HeaderTitleContent
</div>
Expand Down Expand Up @@ -62,7 +64,7 @@
</div>
</div>
}
<FluentMenu @ref="@_menu" Trigger="@_columnId">
<FluentMenu @ref="@_menu" Trigger="@_columnId" Style="@headerPopupLayerStyle">
<FluentMenuList>
@if (Sortable.HasValue ? Sortable.Value : IsSortableByDefault())
{
Expand Down
17 changes: 17 additions & 0 deletions src/Core/Components/DataGrid/Columns/ColumnBase.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,23 @@ public abstract partial class ColumnBase<TGridItem>
[Parameter]
public string? Width { get; set; }

/// <summary>
/// Gets or sets whether this column is pinned (frozen) to the left or right edge of the grid,
/// so it remains visible when the user scrolls horizontally.
/// Pinned columns require an explicit <see cref="Width"/> in pixels (e.g., <c>"150px"</c>).
/// Left-pinned columns must be contiguous at the start of the column list;
/// right-pinned columns must be contiguous at the end.
/// </summary>
[Parameter]
public DataGridColumnPin Pin { get; set; } = DataGridColumnPin.None;

/// <summary>
/// The sticky <c>left</c> or <c>right</c> CSS offset (in pixels) computed by
/// <see cref="FluentDataGrid{TGridItem}"/> when columns are collected.
/// Not intended for direct use by consumers.
/// </summary>
internal double PinOffsetPx { get; set; }
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why a number ? This is probably better to have a string like 10px but also for other CSS values.


/// <summary>
/// Gets or sets the minimal width of the column.
/// Defaults to 100px for a regular column and 50px for a select column.
Expand Down
Loading
Loading