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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -424,3 +424,4 @@ all-icons.json
all-emojis.json
/global.json
/src/Core.Scripts/src/BuildConstants.ts
/src/Core.Scripts/obj/
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<div style="overflow-x: auto;">
<FluentDataGrid Items="@employees"
Style="min-width: max-content;"
ResizableColumns="true"
DisplayMode="DataGridDisplayMode.Grid">
<PropertyColumn Title="ID" Property="@(e => e.Id)" Width="10%" Pin="DataGridColumnPin.Start" Sortable="true" />
<PropertyColumn Title="Full Name" Property="@(e => e.FullName)" Width="160px" Pin="DataGridColumnPin.Start" 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.End">
<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,82 @@
---
title: Pinned columns
route: /DataGrid/PinnedColumns
---

# Pinned columns

Columns can be pinned (frozen) to the start or end edge of the grid so that they remain visible
while the user scrolls horizontally through wider datasets. Using `Start`/`End` instead of
`Left`/`Right` means pinned columns automatically work correctly in both LTR and RTL layouts.

## Parameters

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

| Value | Behavior |
|---|---|
| `DataGridColumnPin.None` | Default — column scrolls normally |
| `DataGridColumnPin.Start` | Column stays anchored to the start edge |
| `DataGridColumnPin.End` | Column stays anchored to the end edge |

## Rules

* **Explicit width required.** Every pinned column must declare a `Width`.
Pixel and non-pixel CSS units are supported. After the grid renders, sticky offsets are
recomputed from the rendered header widths so pinned columns stay aligned.
* **Start-pinned columns must be contiguous at the start.** Each start-pinned column must
immediately follow another start-pinned column, or be the very first column.
* **End-pinned columns must be contiguous at the end.** Each end-pinned column must
immediately precede another end-pinned column, or be the very last column.
* Violating the missing-width or ordering 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.Start" />
<PropertyColumn Title="Name" Property="@(e => e.Name)" Width="160px" Pin="DataGridColumnPin.Start" />
<PropertyColumn Title="City" Property="@(e => e.City)" />
<TemplateColumn Title="Actions" Width="120px" Pin="DataGridColumnPin.End">
...
</TemplateColumn>
</FluentDataGrid>
</div>
```

## Theming the pinned background

Pinned cells receive a solid background to prevent scrolling content from showing through. The
color defaults to `--colorNeutralBackground2` 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 keeps pinned columns aligned as widths change.
* 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.
* RTL layouts are fully supported: start and end automatically map to the correct physical
direction based on the document's writing mode.

## Example

Demonstrates pinned (frozen) columns using `Pin="DataGridColumnPin.Start"` and `Pin="DataGridColumnPin.End"`.

The two leftmost columns and the Actions column remain visible while the rest scroll horizontally. The grid is
wrapped in a scrollable container to enable sticky positioning and horizontal scrolling when needed. Resize the window
or the columns in the grid to see the effect.

**Pinned columns require an explicit `Width`**.

{{ DataGridPinnedColumns }}
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
19 changes: 19 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,25 @@ public abstract partial class ColumnBase<TGridItem>
[Parameter]
public string? Width { get; set; }

/// <summary>
/// Gets or sets whether this column is pinned (frozen) to the start or end edge of the grid,
/// so it remains visible when the user scrolls horizontally.
/// Pinned columns require an explicit <see cref="Width"/>.
/// Sticky offsets are recomputed from rendered header widths after the grid is rendered.
/// Start-pinned columns must be contiguous at the start of the column list;
/// end-pinned columns must be contiguous at the end.
/// </summary>
[Parameter]
public DataGridColumnPin Pin { get; set; } = DataGridColumnPin.None;

/// <summary>
/// The sticky start or end CSS offset seeded by
/// <see cref="FluentDataGrid{TGridItem}"/> when columns are collected and later updated from
/// rendered widths by JavaScript.
/// Not intended for direct use by consumers.
/// </summary>
internal string PinOffset { get; set; } = "0px";

/// <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