@page "/components/data-grid"
@page "/components/datagrid"

<PageTitle>DataGrid — Lumeo</PageTitle>

<PageHeader Title="DataGrid" Description="A powerful data grid with sorting, filtering, pagination, selection, inline editing, column pinning, resizable columns, row editing, layout persistence, context menus, and CSV/Excel/JSON export." />

<Alert Variant="Alert.AlertVariant.Info" ShowIcon="true" Class="mt-6">
    <ChildContent>
        <strong>Dependencies &amp; licensing — read carefully if you're shipping commercially.</strong>
        <ul class="mt-2 space-y-1.5 list-disc list-inside">
            <li><strong>ClosedXML</strong> 0.104.2 (Excel <code>.xlsx</code> export) — MIT, fully free for any use.</li>
            <li><strong>QuestPDF</strong> 2024.12.3 (PDF export) — <strong>dual-licensed</strong>:
                <ul class="ml-6 mt-1 space-y-0.5 list-disc list-inside">
                    <li><strong>Free</strong> for individual developers and for organizations with annual gross revenue under $1M USD (Community License).</li>
                    <li><strong>Paid</strong> ($699/dev/year as of writing) for everyone else — purchased direct from
                        <a href="https://www.questpdf.com/license/" class="underline hover:text-foreground">questpdf.com</a>.
                        Lumeo doesn't take a cut.</li>
                </ul>
            </li>
            <li>CSV / JSON exports use built-in .NET serialization — no third-party dep, no licensing concern.</li>
        </ul>
        If you don't use the PDF export feature, the QuestPDF dependency is still loaded as a transitive package
        but never invoked — only calling <code>ExportPdfAsync</code> triggers the licensing requirement.
    </ChildContent>
</Alert>

<div class="mt-10 space-y-3">
    <Heading Id="when-to-use" Level="2" Class="scroll-mt-20">When to Use</Heading>
    <ul class="list-disc list-inside space-y-1 text-muted-foreground text-sm">
        <li>Complex data grids with advanced features like sorting, filtering, and grouping</li>
        <li>Enterprise table views with inline editing and cell-level validation</li>
        <li>Column pinning and resizable columns for wide data sets</li>
        <li>Scenarios requiring row selection, context menus, and data export (CSV/Excel/JSON)</li>
        <li>Dashboards where the grid starts compact but the user can expand it to fullscreen for a full view</li>
    </ul>
</div>

<Stack Gap="8" Class="mt-8">
    <ComponentDemo Title="Basic DataGrid" Description="A simple data grid with sortable columns and pagination." Code="@_basicCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Sortable="true" Format="C0" />
            <DataGridColumnDef TItem="Employee" Title="Start Date" Field="StartDate" Sortable="true" Format="yyyy-MM-dd" />
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Toolbar, Search & Filtering" Description="Enable the toolbar for global search, column visibility, and CSV export. Column filters appear as inline popovers." Code="@_toolbarCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true" ShowToolbar="true">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" Filterable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" Filterable="true" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Sortable="true" Format="C0" Filterable="true" FilterType="DataGridFilterType.Number" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" Filterable="true" />
            <DataGridColumnDef TItem="Employee" Title="Start Date" Field="StartDate" Sortable="true" Format="yyyy-MM-dd" Filterable="true" FilterType="DataGridFilterType.Date" />
        </DataGrid>
    </ComponentDemo>

    <div class="space-y-3">
        <Heading Id="toolbar-customization" Level="2" Class="scroll-mt-20">Toolbar customization</Heading>
        <Text Size="sm" Color="muted">
            The toolbar is composed of independent slots — Search, Columns, Export, and Expand — each togglable with its own parameter. Defaults preserve the full toolbar; opt-out by setting any of <code>ShowSearch</code>, <code>ShowColumnChooser</code>, <code>ShowExport</code>, or <code>Expandable</code> to <code>false</code>. For fine-grained control over which file formats appear in the Export dropdown, use the <code>ExportFormats</code> flags enum. This is especially useful in Blazor WebAssembly, where PDF generators like QuestPDF throw <code>PlatformNotSupportedException</code> — hide PDF by setting <code>ExportFormats="DataGridExportFormat.Csv | DataGridExportFormat.Excel"</code>.
        </Text>
    </div>

    <ComponentDemo Title="Minimal toolbar (search only)" Description="Hide the Columns and Export buttons to give users a cleaner toolbar with just global search." Code="@_toolbarMinimalCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true"
                  ShowToolbar="true"
                  ShowColumnChooser="false"
                  ShowExport="false">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" Filterable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" Filterable="true" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Sortable="true" Format="C0" />
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Export-only toolbar" Description="Hide the search input and Columns button to keep only the Export dropdown — useful for read-only, report-style grids." Code="@_toolbarExportOnlyCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true"
                  ShowToolbar="true"
                  ShowSearch="false"
                  ShowColumnChooser="false">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Sortable="true" Format="C0" />
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="CSV-only export (WASM-friendly)" Description="Restrict the Export dropdown to CSV. Use this pattern in Blazor WebAssembly where PDF export libraries aren't supported." Code="@_toolbarCsvOnlyCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true"
                  ShowToolbar="true"
                  ExportFormats="DataGridExportFormat.Csv">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" Filterable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" Filterable="true" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Sortable="true" Format="C0" />
        </DataGrid>
    </ComponentDemo>

    <div class="space-y-3">
        <Heading Id="filtering-extensibility" Level="2" Class="scroll-mt-20">Filtering Extensibility</Heading>
        <Text Size="sm" Color="muted">
            Column filters pick sensible defaults from <code>FilterType</code>, but two extension points let you customize the experience per-column without forking the grid.
        </Text>

        <Heading Level="3" Class="mt-4">Restricting operators per column</Heading>
        <Text Size="sm" Color="muted">
            Pass a <code>List&lt;FilterOperator&gt;</code> to the <code>Operators</code> parameter to limit which operators the built-in filter popover shows. The grid intersects this list with the defaults for the column's FilterType, so nonsensical combinations (e.g. <code>StartsWith</code> on a Number column) are filtered out automatically.
        </Text>
        <pre class="rounded-md border border-border/40 bg-muted/30 p-3 text-xs overflow-x-auto"><code>&lt;DataGridColumnDef TItem="Employee"
                   Title="Name"
                   Field="Name"
                   Filterable="true"
                   Operators="@@(new List&lt;FilterOperator&gt; { FilterOperator.Equals, FilterOperator.StartsWith })" /&gt;</code></pre>

        <Heading Level="3" Class="mt-4">Custom filter UI via FilterTemplate</Heading>
        <Text Size="sm" Color="muted">
            For columns that need a bespoke experience (range sliders, relative-date pickers, tag pickers, ...), supply a <code>FilterTemplate</code>. The render fragment receives a <code>DataGridFilterTemplateContext</code> exposing the field name, the currently-applied <code>FilterDescriptor</code> (or null), and an <code>Apply</code> callback. Invoke <code>Apply</code> with a descriptor to commit the filter, or with <code>null</code> to clear it.
        </Text>
        <pre class="rounded-md border border-border/40 bg-muted/30 p-3 text-xs overflow-x-auto"><code>&lt;DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Filterable="true"&gt;
    &lt;FilterTemplate Context="ctx"&gt;
        &lt;Slider Min="0" Max="200000" Value="_min" ValueChanged="v =&gt; _min = v" /&gt;
        &lt;Button OnClick="@@(async () =&gt; await ctx.Apply(
            new FilterDescriptor(ctx.Field!, FilterOperator.GreaterThanOrEqual, _min, FilterType: DataGridFilterType.Number)))"&gt;
            Apply
        &lt;/Button&gt;
        &lt;Button Variant="Button.ButtonVariant.Ghost" OnClick="@@(async () =&gt; await ctx.Apply(null))"&gt;
            Clear
        &lt;/Button&gt;
    &lt;/FilterTemplate&gt;
&lt;/DataGridColumnDef&gt;</code></pre>

        <Text Size="sm" Color="muted" Class="mt-2">
            What is <em>not</em> pluggable today (tracked for a future release):
            registering brand-new <code>FilterOperator</code> values,
            combining column filters with OR logic,
            and LINQ-expression compilation for server-side evaluation.
            Filters are always AND-combined across columns; within a column you get one operator + value at a time.
        </Text>
    </div>

    <ComponentDemo Title="Multi-Select with Checkboxes" Description="Select multiple rows with checkboxes. The header checkbox toggles select all." Code="@_multiSelectCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true"
                  SelectionMode="DataGridSelectionMode.Multiple"
                  SelectedItemsChanged="@OnMultiSelectionChanged">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
        </DataGrid>
        @if (_multiSelectedCount > 0)
        {
            <Alert Class="mt-4" Title="Selection" Description="@($"{_multiSelectedCount} row(s) selected.")">
            </Alert>
        }
    </ComponentDemo>

    <ComponentDemo Title="Single Row Selection" Description="Click a row to select it. Useful for master-detail views." Code="@_singleSelectCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true"
                  SelectionMode="DataGridSelectionMode.Single"
                  SelectedItemsChanged="@OnSelectionChanged">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" />
        </DataGrid>
        @if (_selectedEmployee is not null)
        {
            <Alert Class="mt-4" Title="Selected" Description="@($"{_selectedEmployee.Name} — {_selectedEmployee.Department} — {_selectedEmployee.Salary.ToString("C0")}")">
            </Alert>
        }
    </ComponentDemo>

    <ComponentDemo Title="Striped, Bordered & Compact" Description="Visual variants for different density needs." Code="@_styledCode">
        <DataGrid TItem="Employee" Items="@_employees" Striped="true" Bordered="true" Compact="true" PageSize="5" ShowPagination="true">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
            <DataGridColumnDef TItem="Employee" Title="Start Date" Field="StartDate" Format="yyyy-MM-dd" />
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Custom Cell Templates" Description="Use CellTemplate to render custom content like avatars, badges, and action buttons." Code="@_templateCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true">
            <DataGridColumnDef TItem="Employee" Title="Employee" Field="Name" Sortable="true">
                <CellTemplate>
                    <Flex Align="center" Gap="2">
                        <Avatar Size="Avatar.AvatarSize.Sm">
                            <AvatarFallback>@context.Name[..2].ToUpper()</AvatarFallback>
                        </Avatar>
                        <Stack Gap="0">
                            <Lumeo.Text As="span" Weight="medium">@context.Name</Lumeo.Text>
                            <Lumeo.Text As="div" Size="xs" Color="muted">@context.Department</Lumeo.Text>
                        </Stack>
                    </Flex>
                </CellTemplate>
            </DataGridColumnDef>
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Sortable="true" Format="C0" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status">
                <CellTemplate>
                    <Badge Variant="@(context.Status == "Active" ? Badge.BadgeVariant.Default : Badge.BadgeVariant.Secondary)">
                        @context.Status
                    </Badge>
                </CellTemplate>
            </DataGridColumnDef>
            <DataGridColumnDef TItem="Employee" Title="Actions" Field="Name">
                <CellTemplate>
                    <Flex Align="center" Gap="1">
                        <Button Variant="Button.ButtonVariant.Ghost" Size="Button.ButtonSize.Sm">
                            <DynamicIcon Name="Pencil" Class="h-3.5 w-3.5" />
                        </Button>
                        <Button Variant="Button.ButtonVariant.Ghost" Size="Button.ButtonSize.Sm">
                            <DynamicIcon Name="Trash2" Class="h-3.5 w-3.5 text-destructive" />
                        </Button>
                    </Flex>
                </CellTemplate>
            </DataGridColumnDef>
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Expandable Detail Rows" Description="Use DetailTemplate to show additional information when a row is expanded." Code="@_detailCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true">
            <DetailTemplate>
                <Stack Gap="2" Class="p-4">
                    <Lumeo.Text Size="sm"><Lumeo.Text As="span" Weight="medium">Full Name:</Lumeo.Text> @context.Name</Lumeo.Text>
                    <Lumeo.Text Size="sm"><Lumeo.Text As="span" Weight="medium">Department:</Lumeo.Text> @context.Department</Lumeo.Text>
                    <Lumeo.Text Size="sm"><Lumeo.Text As="span" Weight="medium">Annual Salary:</Lumeo.Text> @context.Salary.ToString("C0")</Lumeo.Text>
                    <Lumeo.Text Size="sm"><Lumeo.Text As="span" Weight="medium">Start Date:</Lumeo.Text> @context.StartDate.ToString("MMMM dd, yyyy")</Lumeo.Text>
                    <Lumeo.Text Size="sm"><Lumeo.Text As="span" Weight="medium">Status:</Lumeo.Text> @context.Status</Lumeo.Text>
                </Stack>
            </DetailTemplate>
            <ChildContent>
                <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
                <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
                <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
            </ChildContent>
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Column Pinning" Description="Pin columns to the left or right edge so they stay visible while scrolling horizontally." Code="@_pinCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" Pin="PinDirection.Left" Width="180" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Width="150" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" Width="120" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" Width="120" />
            <DataGridColumnDef TItem="Employee" Title="Start Date" Field="StartDate" Format="yyyy-MM-dd" Width="150" />
            <DataGridColumnDef TItem="Employee" Title="Actions" Field="Name" Pin="PinDirection.Right" Width="100">
                <CellTemplate>
                    <Button Variant="Button.ButtonVariant.Ghost" Size="Button.ButtonSize.Sm">
                        <DynamicIcon Name="Ellipsis" Class="h-4 w-4" />
                    </Button>
                </CellTemplate>
            </DataGridColumnDef>
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Resizable Columns" Description="Drag the column border to resize. All columns are resizable by default. Use MinWidth and MaxWidth to constrain sizing." Code="@_resizeCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" Width="200" MinWidth="100" MaxWidth="400" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Width="150" MinWidth="80" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" Width="120" MinWidth="80" MaxWidth="200" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" Width="100" />
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Inline Cell Editing" Description="Click a cell to edit it inline. Press Enter to commit, Escape to cancel." Code="@_editCode">
        <DataGrid TItem="Employee" Items="@_editableEmployees" PageSize="5" ShowPagination="true"
                  EditMode="DataGridEditMode.Cell" OnCellEdit="@HandleCellEdit">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
        </DataGrid>
        @if (_lastEditInfo is not null)
        {
            <Alert Class="mt-4" Title="Cell Edited" Description="@_lastEditInfo">
            </Alert>
        }
    </ComponentDemo>

    <ComponentDemo Title="Batch / Buffered Editing"
                   Description="Edit cells inline as in Cell mode, but commits land in a per-row pending-changes buffer. A pending strip and Save all / Discard buttons appear above the grid. Optional + Add row trigger pushes new rows into the Added buffer. OnBatchSave receives Modified + Added when the user clicks Save all.">
        <DataGrid TItem="Employee" Items="@_batchEmployees" PageSize="5" ShowPagination="true"
                  EditMode="DataGridEditMode.Batch"
                  ShowAddRow="true"
                  NewItemFactory="@(() => new Employee("(new)", "Engineering", 0, "Active", DateTime.Today))"
                  OnBatchSave="@HandleBatchSave">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
        </DataGrid>
        @if (_lastBatchSummary is not null)
        {
            <Alert Class="mt-4" Title="Batch Saved" Description="@_lastBatchSummary" />
        }
    </ComponentDemo>

    <ComponentDemo Title="Row Editing" Description="Edit an entire row at once. Click the edit button to enter edit mode, then save or cancel." Code="@_rowEditCode">
        <DataGrid TItem="Employee" Items="@_rowEditEmployees" PageSize="5" ShowPagination="true"
                  EditMode="DataGridEditMode.Row" OnRowEdit="@HandleRowEdit">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
        </DataGrid>
        @if (_lastRowEditInfo is not null)
        {
            <Alert Class="mt-4" Title="Row Edited" Description="@_lastRowEditInfo">
            </Alert>
        }
    </ComponentDemo>

    <ComponentDemo Title="Footer Aggregation" Description="Show computed summaries (Sum, Average, Count, Min, Max) in the table footer." Code="@_aggregateCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="20">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Aggregate="AggregateType.Count" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" Sortable="true" Aggregate="AggregateType.Average" />
            <DataGridColumnDef TItem="Employee" Title="Start Date" Field="StartDate" Format="yyyy-MM-dd" />
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Row Grouping" Description="Group rows by a field. Click the group header to expand or collapse each group. Set GroupsExpandedByDefault to control the initial state." Code="@_groupingCode">
        <Stack Gap="2">
            <Flex Align="center" Gap="2">
                <Lumeo.Text Size="sm">Group by:</Lumeo.Text>
                <Select Value="@_groupBy" ValueChanged="@((string v) => { _groupBy = v; StateHasChanged(); })">
                    <SelectTrigger Class="h-8 w-[160px]">
                        <span class="text-sm">@(_groupBy == "" ? "None" : _groupBy)</span>
                    </SelectTrigger>
                    <SelectContent>
                        <SelectItem Value="">None</SelectItem>
                        <SelectItem Value="Department">Department</SelectItem>
                        <SelectItem Value="Status">Status</SelectItem>
                    </SelectContent>
                </Select>
                <Switch Checked="@_groupsExpandedByDefault" CheckedChanged="@((bool v) => { _groupsExpandedByDefault = v; StateHasChanged(); })" />
                <Lumeo.Text Size="sm" Color="muted">Expanded by default</Lumeo.Text>
            </Flex>
            <DataGrid TItem="Employee" Items="@_employees"
                      GroupBy="@(string.IsNullOrEmpty(_groupBy) ? null : _groupBy)"
                      GroupsExpandedByDefault="@_groupsExpandedByDefault"
                      Striped="true">
                <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
                <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" />
                <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
                <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" Sortable="true" Aggregate="AggregateType.Average" />
                <DataGridColumnDef TItem="Employee" Title="Start Date" Field="StartDate" Format="yyyy-MM-dd" />
            </DataGrid>
        </Stack>
    </ComponentDemo>

    <ComponentDemo Title="Group Panel (Interactive)" Description="Set ShowGroupPanel=&quot;true&quot; to render the chip-strip panel above the grid. Flag columns Groupable=&quot;true&quot; to make them addable. Use GroupByFields for ordered multi-level grouping — each level adds a nested row with its own aggregate." Code="@_groupPanelCode">
        <Stack Gap="3">
            <Flex Align="center" Gap="4" Wrap="true">
                <Flex Align="center" Gap="2">
                    <Lumeo.Text Size="sm" Weight="medium">Group panel:</Lumeo.Text>
                    <Segmented @bind-Value="_groupPanelMode">
                        <SegmentedItem Value="off">Off</SegmentedItem>
                        <SegmentedItem Value="on">On</SegmentedItem>
                    </Segmented>
                </Flex>
                <Flex Align="center" Gap="2">
                    <Lumeo.Text Size="sm" Weight="medium">Levels:</Lumeo.Text>
                    <Segmented @bind-Value="_groupPanelLevels">
                        <SegmentedItem Value="single">Single (Department)</SegmentedItem>
                        <SegmentedItem Value="multi">Multi (Department → Status)</SegmentedItem>
                    </Segmented>
                </Flex>
            </Flex>
            <DataGrid TItem="Employee" Items="@_employees"
                      ShowGroupPanel="@(_groupPanelMode == "on")"
                      GroupByFields="@(_groupPanelLevels == "multi"
                                        ? new[] { "Department", "Status" }
                                        : new[] { "Department" })"
                      Striped="true">
                <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
                <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" Groupable="true" />
                <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" Groupable="true" />
                <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" Sortable="true" Aggregate="AggregateType.Average" />
                <DataGridColumnDef TItem="Employee" Title="Start Date" Field="StartDate" Format="yyyy-MM-dd" />
            </DataGrid>
        </Stack>
    </ComponentDemo>

    <ComponentDemo Title="Column Reordering" Description="Drag column headers to reorder them. Enable with the Reorderable property on the grid." Code="@_reorderCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true" Reorderable="true">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Conditional Row Styling" Description="Apply dynamic CSS classes or inline styles to rows based on data. Useful for highlighting specific records." Code="@_rowStyleCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true"
                  RowClass="@(e => e.Status == "Inactive" ? "opacity-50" : "")"
                  RowStyle="@(e => e.Salary > 100000 ? "font-weight: 600;" : "")">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status">
                <CellTemplate>
                    <Badge Variant="@(context.Status == "Active" ? Badge.BadgeVariant.Default : Badge.BadgeVariant.Secondary)">
                        @context.Status
                    </Badge>
                </CellTemplate>
            </DataGridColumnDef>
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Context Menu" Description="Right-click a row to open a custom context menu with actions." Code="@_contextMenuCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true">
            <RowContextMenu>
                <Stack Gap="0">
                    <Button Variant="Button.ButtonVariant.Ghost" Size="Button.ButtonSize.Sm" Class="w-full justify-start gap-2 px-2">
                        <DynamicIcon Name="Pencil" Class="h-3.5 w-3.5" />
                        Edit
                    </Button>
                    <Button Variant="Button.ButtonVariant.Ghost" Size="Button.ButtonSize.Sm" Class="w-full justify-start gap-2 px-2">
                        <DynamicIcon Name="Copy" Class="h-3.5 w-3.5" />
                        Duplicate
                    </Button>
                    <Separator Class="my-1" />
                    <Button Variant="Button.ButtonVariant.Ghost" Size="Button.ButtonSize.Sm" Class="w-full justify-start gap-2 px-2 text-destructive">
                        <DynamicIcon Name="Trash2" Class="h-3.5 w-3.5" />
                        Delete
                    </Button>
                </Stack>
            </RowContextMenu>
            <ChildContent>
                <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
                <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
                <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" />
                <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
            </ChildContent>
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Layout Persistence" Description="Save and restore column widths, order, visibility, sorts, filters, and the group panel (runtime group fields) to localStorage. The layout survives page refreshes. Use the Layouts dropdown in the toolbar to save named layouts under Personal, Global, or System Default scopes." Code="@_layoutPersistenceCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true"
                  ShowToolbar="true" Reorderable="true"
                  EnableLayoutPersistence="true" LayoutStorageKey="demo-datagrid-layout">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" Filterable="true" Width="200" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" Filterable="true" Width="150" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" Sortable="true" Width="120" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" Filterable="true" Width="100" />
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Named Layout Manager" Description="Pass GlobalLayouts to surface pre-built layouts in the Layouts dropdown. Users can also save their own Personal layouts which are stored in localStorage. The panel shows Personal / Global / System Default scopes." Code="@_namedLayoutCode">
        <Stack Gap="3">
            @if (_savedLayoutNotice is not null)
            {
                <Alert Title="Layout Event" Description="@_savedLayoutNotice">
                </Alert>
            }
            <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true"
                      ShowToolbar="true" Reorderable="true"
                      EnableLayoutPersistence="true" LayoutStorageKey="demo-named-layout"
                      GlobalLayouts="@_globalLayouts"
                      OnSaveNamedLayout="@(nl => { _savedLayoutNotice = $"Saved named layout: \"{nl.Name}\" (scope: {nl.Scope})"; StateHasChanged(); })"
                      OnDeleteNamedLayout="@(id => { _savedLayoutNotice = $"Deleted layout: {id}"; StateHasChanged(); })">
                <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" Filterable="true" Width="200" />
                <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" Filterable="true" Width="150" />
                <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" Sortable="true" Width="120" />
                <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" Filterable="true" Width="100" />
            </DataGrid>
        </Stack>
    </ComponentDemo>

    <div class="space-y-3">
        <Heading Id="saving-and-loading-layouts" Level="2" Class="scroll-mt-20">Saving and loading layouts</Heading>
        <Text Size="sm" Color="muted">
            For full control over where layouts are stored (your own database, a REST API, cookies, etc.), use the programmatic JSON API. Capture a grid reference with <code>@@ref</code>, then call <code>ExportLayout()</code> to get a JSON string and <code>ApplyLayoutJsonAsync(json)</code> to round-trip it later. The snapshot covers column order, widths, visibility, pin state, sorts, filters, global search, current page, page size, and the group panel (multi-level group fields) — both single and multi-level grouping levels round-trip.
        </Text>
        <pre class="rounded-md border border-border/40 bg-muted/30 p-3 text-xs overflow-x-auto"><code>&lt;DataGrid @@ref="_grid" TItem="Order" Items="_orders"&gt;
    &lt;DataGridColumnDef TItem="Order" Field="Id" Title="ID" /&gt;
    &lt;DataGridColumnDef TItem="Order" Field="Customer" Title="Customer" /&gt;
    &lt;DataGridColumnDef TItem="Order" Field="Total" Title="Total" Format="C2" /&gt;
&lt;/DataGrid&gt;

&lt;Button OnClick="SaveLayout"&gt;Save layout&lt;/Button&gt;
&lt;Button OnClick="LoadLayout"&gt;Load layout&lt;/Button&gt;

@@code {
    private DataGrid&lt;Order&gt;? _grid;
    [Inject] MyAppDbContext Db { get; set; } = default!;

    private async Task SaveLayout()
    {
        if (_grid is null) return;
        var json = _grid.ExportLayout();
        await Db.UserLayouts.AddAsync(new UserLayout {
            UserId = CurrentUserId,
            GridKey = "orders",
            Json = json,
            SavedAt = DateTime.UtcNow
        });
        await Db.SaveChangesAsync();
    }

    private async Task LoadLayout()
    {
        if (_grid is null) return;
        var latest = await Db.UserLayouts
            .Where(l =&gt; l.UserId == CurrentUserId &amp;&amp; l.GridKey == "orders")
            .OrderByDescending(l =&gt; l.SavedAt)
            .FirstOrDefaultAsync();
        if (latest is not null)
            await _grid.ApplyLayoutJsonAsync(latest.Json);
    }
}</code></pre>
        <Text Size="sm" Color="muted">
            The JSON shape is the public <code>DataGridLayoutSnapshot</code> record. The snapshot is versioned (<code>Version: 2</code> today) so future breaking changes can be migrated; v1 payloads still load — the legacy single-level <code>GroupBy</code> field is honoured as a fallback when <code>GroupByFields</code> is absent. Columns referenced in a snapshot that no longer exist on the grid are silently ignored; columns present on the grid but missing from the snapshot are appended at the end.
        </Text>
    </div>

    <div class="space-y-3">
        <Heading Id="reorder-columns" Level="2" Class="scroll-mt-20">Reorder columns</Heading>
        <Text Size="sm" Color="muted">
            Set <code>Reorderable="true"</code> on the grid to enable column reordering. Users can reorder columns two ways:
        </Text>
        <ul class="list-disc list-inside space-y-1 text-muted-foreground text-sm">
            <li><strong>Drag-and-drop</strong>: grab a column header and drag it left or right. A 2px vertical drop indicator renders at the nearest column boundary to show where the column will land when released.</li>
            <li><strong>Keyboard / menu arrows</strong>: open the Toggle Columns popover from the toolbar and use the up/down arrow buttons next to each column. This path is the accessible fallback for users who can't use pointer drag-and-drop.</li>
        </ul>
        <Text Size="sm" Color="muted">
            To pin an individual column so it cannot be reordered (for example, a required identifier column), set <code>Reorderable="false"</code> on that <code>DataGridColumnDef</code>. Both the drag handle and the menu arrows respect this flag.
        </Text>
        <pre class="rounded-md border border-border/40 bg-muted/30 p-3 text-xs overflow-x-auto"><code>&lt;DataGrid TItem="Order" Items="_orders" Reorderable="true"&gt;
    &lt;DataGridColumnDef TItem="Order" Field="Id" Title="ID" Reorderable="false" /&gt;
    &lt;DataGridColumnDef TItem="Order" Field="Customer" Title="Customer" /&gt;
    &lt;DataGridColumnDef TItem="Order" Field="Total" Title="Total" Format="C2" /&gt;
&lt;/DataGrid&gt;</code></pre>
    </div>

    <ComponentDemo Title="Custom Toolbar Content" Description="The ToolbarContent component slots your custom buttons next to the toolbar's filter chips. Use the Class attribute to tweak the wrapper (gap, flex-wrap, etc.)." Code="@_toolbarContentCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true" ShowToolbar="true">
            <ChildContent>
                <ToolbarContent TItem="Employee" Class="gap-3">
                    <Button Variant="Button.ButtonVariant.Outline" Size="Button.ButtonSize.Sm" Class="gap-1.5">
                        <DynamicIcon Name="Plus" Class="h-3.5 w-3.5" />
                        Add Employee
                    </Button>
                </ToolbarContent>
                <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" Filterable="true" />
                <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" />
                <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" Sortable="true" />
                <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
            </ChildContent>
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Composing Toolbar Tools" Description="Mix your own buttons with built-in tools. When you provide ToolbarContent, the default tool stack is suppressed — you pick exactly which tools to include and in what order. Each tool picks up the grid's state via a cascading context — no props needed." Code="@_toolbarComposedCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true" ShowToolbar="true">
            <ChildContent>
                <ToolbarContent TItem="Employee" Class="gap-2 flex-wrap">
                    <Button Variant="Button.ButtonVariant.Outline" Size="Button.ButtonSize.Sm" Class="gap-1.5">
                        <DynamicIcon Name="RefreshCw" Class="h-3.5 w-3.5" />
                        Refresh
                    </Button>
                    <DataGridToolbarColumns TItem="Employee" />
                    <DataGridToolbarExport TItem="Employee" />
                    <DataGridToolbarFullscreen TItem="Employee" />
                </ToolbarContent>
                <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" Filterable="true" />
                <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" />
                <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" Sortable="true" />
                <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
            </ChildContent>
        </DataGrid>
    </ComponentDemo>

    <Alert Variant="Alert.AlertVariant.Info" Title="Grouping in ServerMode" Class="my-4">
        <strong><code>GroupBy</code>, <code>GroupByFields</code>, and the runtime group panel
        (<code>ShowGroupPanel="true"</code>) all work in <code>ServerMode</code> — including
        drag-to-group, add-level dropdown, per-chip remove, and clear-all.</strong> Grouping is
        applied client-side to whatever rows the server returned for the current page. For grouping
        across the whole dataset, either set <code>ShowPagination="false"</code> (server returns all
        rows) or have the server pre-aggregate groups itself.
    </Alert>

    <ComponentDemo Title="Server-Side Mode" Description="For large datasets, use ServerMode to handle sorting, filtering, and pagination on the server. Sorting, filtering, search, and pagination all trigger OnServerRequest. Loading state and request cancellation are managed automatically." Code="@_serverModeCode">
        <DataGrid TItem="Employee" Items="@_serverItems"
                  ServerMode="true"
                  TotalCount="@_serverTotalCount"
                  PageSize="5"
                  ShowPagination="true"
                  ShowToolbar="true"
                  OnServerRequest="@LoadServerData">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" Filterable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" Filterable="true" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Server-Side Error Handling" Description="Toggle the simulate error button to trigger a server failure. OnError receives the exception so you can display a message outside the grid." Code="@_errorHandlingCode">
        <Stack Gap="3">
            <Flex Gap="2" Align="center">
                <Button Variant="@(_simulateError ? Button.ButtonVariant.Destructive : Button.ButtonVariant.Outline)"
                        Size="Button.ButtonSize.Sm"
                        OnClick="@(() => { _simulateError = !_simulateError; _serverErrorDisplay = null; })">
                    @(_simulateError ? "Simulating failure — click to recover" : "Simulate server error")
                </Button>
            </Flex>
            <DataGrid TItem="Employee" Items="@_errorItems"
                      ServerMode="true"
                      TotalCount="@_errorTotalCount"
                      PageSize="5"
                      ShowPagination="true"
                      OnServerRequest="@LoadErrorData"
                      OnError="@(ex => _serverErrorDisplay = ex.Message)">
                <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
                <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
                <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" />
                <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
            </DataGrid>
            @if (!string.IsNullOrEmpty(_serverErrorDisplay))
            {
                <Alert Title="Server Error" Description="@_serverErrorDisplay">
                </Alert>
            }
        </Stack>
    </ComponentDemo>

    <ComponentDemo Title="Multi-Sort" Description="Click multiple column headers to sort by multiple fields. The grid applies sorts in the order they were added. Click a column again to cycle through ascending, descending, and unsorted." Code="@_multiSortCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true">
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Sortable="true" Format="C0" />
            <DataGridColumnDef TItem="Employee" Title="Start Date" Field="StartDate" Sortable="true" Format="yyyy-MM-dd" />
        </DataGrid>
    </ComponentDemo>

    <div class="space-y-3">
        <Heading Id="virtualization" Level="2" Class="scroll-mt-20">Virtualization &amp; large datasets</Heading>
        <Text Size="sm" Color="muted">
            Lumeo's DataGrid supports two distinct strategies for large datasets — pick the one that matches where your data lives:
        </Text>
        <ul class="list-disc list-inside space-y-1 text-muted-foreground text-sm pl-2">
            <li>
                <strong>Client virtualization</strong> — items are already in memory. Set <code>ShowPagination="false"</code>
                and pass all rows; once the row count exceeds <code>VirtualizeThreshold</code> (default 500),
                Blazor's built-in <code>&lt;Virtualize&gt;</code> renders only the rows in the viewport. Tune
                <code>VirtualItemSize</code> if your rows are denser/taller than the 41 px default — an inaccurate
                value makes the scrollbar drift. Paged grids never trigger this path because they only render
                <code>PageSize</code> rows at a time.
            </li>
            <li>
                <strong>Server-side paging</strong> — items live on a backend. Set <code>ServerMode="true"</code>,
                handle <code>OnServerRequest</code> (Skip/Take/Sort/Filter), and report <code>TotalCount</code>.
                The grid shows numbered pages and the <code>PageSizeOptions</code> selector. Use this when you have
                many pages of data but only want to fetch one page at a time.
            </li>
        </ul>
        <ul class="list-disc list-inside space-y-1 text-muted-foreground text-sm pl-2 mt-1">
            <li>
                <strong>Virtualised server mode (infinite scroll)</strong> — the third mode. Set
                <code>Virtualized="true"</code> + provide an <code>OnRangeRequest</code> callback. The grid drops
                pagination, renders one tall scrollable body, and fetches a sliding window of rows from your
                backend as the user scrolls. Sort, column filters and global search all trigger an automatic
                refetch via <code>Virtualize.RefreshDataAsync()</code> — no manual wiring needed. Use this when you
                have hundreds of thousands of rows and want a grid that feels native, not paged.
            </li>
        </ul>
    </div>

    <ComponentDemo Title="Virtualised server mode (infinite scroll)" Description="Set Virtualized=true + provide OnRangeRequest. The grid fetches a sliding window of rows from your backend as the user scrolls, hides pagination, and re-fetches automatically when sort/filter/search change. Demo fakes a 50000-row backend with an in-memory list." Code="@_virtualizedServerCode">
        <DataGrid TItem="Employee"
                  Items="@(Array.Empty<Employee>())"
                  Virtualized="true"
                  Height="320px"
                  OnRangeRequest="@FakeRangeRequest"
                  ShowToolbar="true">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" Filterable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Custom page-size options" Description="Pass a custom array to the rows-per-page selector — useful for grids with many rows where 100 isn't enough. Empty array hides the selector." Code="@_pageSizeOptionsCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="10" PageSizeOptions="new[] { 10, 50, 100, 200, 300 }" ShowPagination="true">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Fixed Height with Scroll" Description="Set a fixed height on the grid to enable vertical scrolling for long lists." Code="@_fixedHeightCode">
        <DataGrid TItem="Employee" Items="@_employees" Height="250px" ShowPagination="false">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
            <DataGridColumnDef TItem="Employee" Title="Start Date" Field="StartDate" Format="yyyy-MM-dd" />
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Loading State" Description="Show skeleton rows while data is loading." Code="@_loadingCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" IsLoading="true">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Format="C0" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" />
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Empty State" Description="Custom content when the grid has no data." Code="@_emptyCode">
        <DataGrid TItem="Employee" Items="@(new List<Employee>())" PageSize="5">
            <EmptyContent>
                <Stack Gap="3" Align="center" Class="py-4">
                    <DynamicIcon Name="SearchX" Class="h-10 w-10 text-muted-foreground/40" />
                    <Lumeo.Text Size="sm" Color="muted">No employees found. Try adjusting your filters.</Lumeo.Text>
                </Stack>
            </EmptyContent>
            <ChildContent>
                <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" />
                <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" />
            </ChildContent>
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Expandable (Fullscreen)" Description="Enable the Expandable feature to add a fullscreen icon in the toolbar. Click it to pop the grid into a fullscreen modal overlay. Selection, filters, sorts, and pagination all survive the transition. Press Esc or click the close button to exit." Code="@_expandableCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true" ShowToolbar="true"
                  Expandable="true" FullscreenTitle="Employees"
                  SelectionMode="DataGridSelectionMode.Multiple">
            <DataGridColumnDef TItem="Employee" Title="Name" Field="Name" Sortable="true" Filterable="true" />
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" Filterable="true" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Sortable="true" Format="C0" Filterable="true" FilterType="DataGridFilterType.Number" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" Filterable="true" />
            <DataGridColumnDef TItem="Employee" Title="Start Date" Field="StartDate" Sortable="true" Format="yyyy-MM-dd" />
        </DataGrid>
    </ComponentDemo>

    <ComponentDemo Title="Full-Featured DataGrid" Description="All features combined: toolbar, search, filtering, sorting, selection, column visibility, export, conditional styling, and aggregation." Code="@_fullCode">
        <DataGrid TItem="Employee" Items="@_employees" PageSize="5" ShowPagination="true"
                  ShowToolbar="true" SelectionMode="DataGridSelectionMode.Multiple"
                  Striped="true" Hoverable="true" Reorderable="true"
                  RowClass="@(e => e.Status == "Inactive" ? "opacity-50" : "")">
            <DataGridColumnDef TItem="Employee" Title="Employee" Field="Name" Sortable="true" Filterable="true" Pin="PinDirection.Left" Width="200">
                <CellTemplate>
                    <Flex Align="center" Gap="2">
                        <Avatar Size="Avatar.AvatarSize.Sm">
                            <AvatarFallback>@context.Name[..2].ToUpper()</AvatarFallback>
                        </Avatar>
                        <Lumeo.Text As="span" Weight="medium">@context.Name</Lumeo.Text>
                    </Flex>
                </CellTemplate>
            </DataGridColumnDef>
            <DataGridColumnDef TItem="Employee" Title="Department" Field="Department" Sortable="true" Filterable="true" Width="140" />
            <DataGridColumnDef TItem="Employee" Title="Salary" Field="Salary" Sortable="true" Filterable="true" FilterType="DataGridFilterType.Number" Format="C0" Width="120" Aggregate="AggregateType.Average" />
            <DataGridColumnDef TItem="Employee" Title="Status" Field="Status" Filterable="true" Width="100">
                <CellTemplate>
                    <Badge Variant="@(context.Status == "Active" ? Badge.BadgeVariant.Default : Badge.BadgeVariant.Secondary)">
                        @context.Status
                    </Badge>
                </CellTemplate>
            </DataGridColumnDef>
            <DataGridColumnDef TItem="Employee" Title="Start Date" Field="StartDate" Sortable="true" Format="yyyy-MM-dd" Width="130" />
        </DataGrid>
    </ComponentDemo>
</Stack>

<!-- API Reference -->
<Stack Gap="6" Class="mt-12">
    <Heading Level="2">API Reference</Heading>

    <Stack Gap="8" Class="mt-8">
        <Stack>
            <Heading Level="3" Class="mb-3">DataGrid&lt;TItem&gt;</Heading>
            <Stack Class="overflow-x-auto rounded-lg border border-border/40">
                <table class="w-full min-w-[600px] text-sm">
                    <thead>
                        <tr class="border-b border-border/40 bg-muted/30">
                            <th class="px-4 py-2 text-left font-medium text-foreground">Property</th>
                            <th class="px-4 py-2 text-left font-medium text-foreground">Type</th>
                            <th class="px-4 py-2 text-left font-medium text-foreground">Default</th>
                            <th class="px-4 py-2 text-left font-medium text-foreground">Description</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-border/40">
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Items</td><td class="px-4 py-2 font-mono text-xs">IEnumerable&lt;TItem&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Data source for the grid.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Columns</td><td class="px-4 py-2 font-mono text-xs">List&lt;DataGridColumn&lt;TItem&gt;&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Programmatic column definitions (alternative to DataGridColumnDef).</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">PageSize</td><td class="px-4 py-2 font-mono text-xs">int</td><td class="px-4 py-2 text-xs">10</td><td class="px-4 py-2 text-muted-foreground">Number of rows per page.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">ShowPagination</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">true</td><td class="px-4 py-2 text-muted-foreground">Show pagination controls.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">ShowToolbar</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Show toolbar with search, export, and column visibility.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">ShowSearch</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">true</td><td class="px-4 py-2 text-muted-foreground">Show the global search box in the toolbar. Requires <code>ShowToolbar</code>.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">ShowColumnChooser</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">true</td><td class="px-4 py-2 text-muted-foreground">Show the Columns (visibility/reorder) button in the toolbar.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">ShowExport</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">true</td><td class="px-4 py-2 text-muted-foreground">Show the Export button and its format dropdown. Set <code>false</code> to hide export entirely; for per-format control see <code>ExportFormats</code>.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">ExportFormats</td><td class="px-4 py-2 font-mono text-xs">DataGridExportFormat</td><td class="px-4 py-2 text-xs">All</td><td class="px-4 py-2 text-muted-foreground">Flags enum controlling which formats appear in the Export dropdown. Combine with bitwise OR, e.g. <code>DataGridExportFormat.Csv | DataGridExportFormat.Excel</code>. When no flags are set the Export button is hidden.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">SelectionMode</td><td class="px-4 py-2 font-mono text-xs">DataGridSelectionMode</td><td class="px-4 py-2 text-xs">None</td><td class="px-4 py-2 text-muted-foreground">Row selection: None, Single, or Multiple.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">EditMode</td><td class="px-4 py-2 font-mono text-xs">DataGridEditMode</td><td class="px-4 py-2 text-xs">None</td><td class="px-4 py-2 text-muted-foreground">Inline editing: None, Cell, Row, or Batch (buffered).</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">OnBatchSave</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;DataGridBatchSaveEventArgs&lt;TItem&gt;&gt;</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Fires when the user clicks "Save all" in <code>EditMode=Batch</code>. Carries <code>Modified</code> + <code>Added</code> rows. The grid's pending-changes buffer is cleared automatically on a clean return.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">ShowAddRow</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Renders a "+ Add row" trigger below the body in batch mode. Requires <code>NewItemFactory</code>.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">NewItemFactory</td><td class="px-4 py-2 font-mono text-xs">Func&lt;TItem&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Factory used by the "+ Add row" trigger. Each click produces a fresh row that lives in the batch buffer until <code>OnBatchSave</code> commits it.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">HasPendingChanges</td><td class="px-4 py-2 font-mono text-xs">bool (get)</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">True when the batch buffer contains a buffered edit or a new row. Bind via <code>@@ref</code> for warn-on-navigate workflows.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">BatchSaveAllText / BatchDiscardText / BatchAddRowText</td><td class="px-4 py-2 font-mono text-xs">string?</td><td class="px-4 py-2 text-xs">Save all / Discard / Add row</td><td class="px-4 py-2 text-muted-foreground">Button labels for the batch-mode toolbar. Override for localization.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">ColumnVirtualize</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Simplified column virtualization — caps the number of horizontally-rendered data columns at <code>MaxVisibleColumns</code>. Pinned columns always render. Useful for very wide grids.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">MaxVisibleColumns</td><td class="px-4 py-2 font-mono text-xs">int</td><td class="px-4 py-2 text-xs">30</td><td class="px-4 py-2 text-muted-foreground">Column cap used when <code>ColumnVirtualize=true</code>.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Striped</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Alternate row background colors.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Bordered</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Show cell borders.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Compact</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Smaller row height and text.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Hoverable</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">true</td><td class="px-4 py-2 text-muted-foreground">Highlight rows on hover.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Height</td><td class="px-4 py-2 font-mono text-xs">string?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Fixed height with scrollable body (e.g., "400px").</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Reorderable</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Enable drag-and-drop column reordering.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">RowReorderable</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Enable drag-and-drop row reordering.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">RowClass</td><td class="px-4 py-2 font-mono text-xs">Func&lt;TItem, string&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Function returning CSS class(es) for each row.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">RowStyle</td><td class="px-4 py-2 font-mono text-xs">Func&lt;TItem, string&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Function returning inline style for each row.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">PageSizeOptions</td><td class="px-4 py-2 font-mono text-xs">int[]</td><td class="px-4 py-2 text-xs">[10, 25, 50, 100]</td><td class="px-4 py-2 text-muted-foreground">Page-size options shown in the rows-per-page selector. Pass <code>new[] { 10, 50, 100, 200, 300 }</code> for larger datasets, or an empty array to hide the selector entirely.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">VirtualizeThreshold</td><td class="px-4 py-2 font-mono text-xs">int</td><td class="px-4 py-2 text-xs">500</td><td class="px-4 py-2 text-muted-foreground">Row count above which Blazor's <code>&lt;Virtualize&gt;</code> kicks in. Only relevant when <code>ShowPagination=false</code> — paged grids never reach the threshold and never virtualize.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">VirtualItemSize</td><td class="px-4 py-2 font-mono text-xs">float</td><td class="px-4 py-2 text-xs">41</td><td class="px-4 py-2 text-muted-foreground">Estimated row height in CSS pixels. Tune if rows are denser/taller than the default.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">VirtualOverscanCount</td><td class="px-4 py-2 font-mono text-xs">int</td><td class="px-4 py-2 text-xs">3</td><td class="px-4 py-2 text-muted-foreground">Extra rows rendered above/below the viewport to hide pop-in during fast scrolling.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Virtualized</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Enables ItemsProvider-based infinite-scroll mode. Hides pagination and fetches a sliding row window via <code>OnRangeRequest</code> as the user scrolls. Sort/filter/search refresh the virtualizer automatically.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">OnRangeRequest</td><td class="px-4 py-2 font-mono text-xs">Func&lt;DataGridRangeRequest, ValueTask&lt;DataGridRangeResponse&lt;TItem&gt;&gt;&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Range-fetch callback for virtualised server mode. Receives <code>StartIndex</code>, <code>Count</code>, current Sort/Filter/Search context; must return the slice plus current <code>TotalCount</code>.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">ServerMode</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Enable server-side sorting, filtering, and pagination.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">TotalCount</td><td class="px-4 py-2 font-mono text-xs">int</td><td class="px-4 py-2 text-xs">0</td><td class="px-4 py-2 text-muted-foreground">Total item count for server-side pagination.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">IsLoading</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Show loading skeleton overlay.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">EnableLayoutPersistence</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Auto-save/restore column layout to localStorage. Enables the Layouts dropdown in the toolbar.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">LayoutStorageKey</td><td class="px-4 py-2 font-mono text-xs">string?</td><td class="px-4 py-2 text-xs">auto</td><td class="px-4 py-2 text-muted-foreground">Custom localStorage key for layout persistence.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">SavedLayout</td><td class="px-4 py-2 font-mono text-xs">DataGridLayout?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Externally provided layout to apply on load.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">GlobalLayouts</td><td class="px-4 py-2 font-mono text-xs">List&lt;DataGridNamedLayout&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Pre-built named layouts shown in the Global scope of the Layouts panel.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">OnSaveNamedLayout</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;DataGridNamedLayout&gt;</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Fires when the user saves a named layout. Personal layouts are also persisted to localStorage automatically.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">OnDeleteNamedLayout</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;string&gt;</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Fires when the user deletes a named layout. Receives the layout Id.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">DetailTemplate</td><td class="px-4 py-2 font-mono text-xs">RenderFragment&lt;TItem&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Template for expandable row details.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">EmptyContent</td><td class="px-4 py-2 font-mono text-xs">RenderFragment?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Custom content when no data.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">&lt;ToolbarContent&gt;</td><td class="px-4 py-2 font-mono text-xs">component</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Nested component placed inside ChildContent. Takes Class for wrapper styling and ChildContent for custom buttons / built-in toolbar tools.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">RowContextMenu</td><td class="px-4 py-2 font-mono text-xs">RenderFragment&lt;TItem&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Right-click context menu template for rows.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">OnRowClick</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;TItem&gt;</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Callback when a row is clicked.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">OnRowDoubleClick</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;TItem&gt;</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Callback when a row is double-clicked.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">OnCellEdit</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;CellEditEventArgs&lt;TItem&gt;&gt;</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Callback when a cell edit is committed.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">OnRowEdit</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;RowEditEventArgs&lt;TItem&gt;&gt;</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Callback when a row edit is committed with all changed values.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">OnColumnReorder</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;ColumnReorderEventArgs&gt;</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Callback when columns are reordered via drag-and-drop.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">OnRowReorder</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;RowReorderEventArgs&lt;TItem&gt;&gt;</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Callback when rows are reordered via drag-and-drop.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">OnLayoutSave</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;DataGridLayout&gt;</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Fires when the layout is saved (for external persistence).</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">SelectedItems</td><td class="px-4 py-2 font-mono text-xs">IReadOnlyList&lt;TItem&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Two-way bindable selected items collection.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">SelectedItemsChanged</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;IReadOnlyList&lt;TItem&gt;&gt;</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Callback when selection changes.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">OnServerRequest</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;DataGridServerRequest&gt;</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Server-side data request callback with sorts, filters, page info.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">OnError</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;Exception&gt;</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Fires when a server request throws an exception.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">GroupBy</td><td class="px-4 py-2 font-mono text-xs">string?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Single-level group field. Works in both client and <code>ServerMode</code>; use <code>GroupByFields</code> for multi-level.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Expandable</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Adds an expand icon to the toolbar for toggling fullscreen mode.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">IsExpanded</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Two-way bindable fullscreen state. Set from code to programmatically expand/collapse.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">IsExpandedChanged</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;bool&gt;</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Fires whenever the fullscreen state toggles.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">FullscreenTitle</td><td class="px-4 py-2 font-mono text-xs">string?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Optional title shown in the fullscreen header bar. Defaults to "Data Grid".</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Class</td><td class="px-4 py-2 font-mono text-xs">string?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Additional CSS classes for the root element.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">ExportLayout()</td><td class="px-4 py-2 font-mono text-xs">string</td><td class="px-4 py-2 text-xs">method</td><td class="px-4 py-2 text-muted-foreground">Serializes the current layout (column order, widths, visibility, pin state, sorts, filters, global search, page, page size, group-by) to a JSON string. Call via <code>@@ref</code>.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">ApplyLayoutJsonAsync(json)</td><td class="px-4 py-2 font-mono text-xs">Task</td><td class="px-4 py-2 text-xs">method</td><td class="px-4 py-2 text-muted-foreground">Deserializes a JSON layout produced by <code>ExportLayout</code> and applies it. Throws <code>JsonException</code> on malformed input; silently ignores columns that no longer exist.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">GroupsExpandedByDefault</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">true</td><td class="px-4 py-2 text-muted-foreground">Initial expand state for group headers when grouping is active.</td></tr>
                    </tbody>
                </table>
            </Stack>
        </Stack>

        <Stack>
            <Heading Level="3" Class="mb-3">ToolbarContent&lt;TItem&gt;</Heading>
            <Lumeo.Text Size="sm" Color="muted" Class="mb-3">Place inside <code>&lt;ChildContent&gt;</code> of a DataGrid. Accepts custom buttons or built-in toolbar tool components (see below) that automatically pick up the grid's state via a cascading context.</Lumeo.Text>
            <Stack Class="overflow-x-auto rounded-lg border border-border/40">
                <table class="w-full min-w-[600px] text-sm">
                    <thead>
                        <tr class="border-b border-border/40 bg-muted/30">
                            <th class="px-4 py-2 text-left font-medium text-xs uppercase tracking-wider">Property</th>
                            <th class="px-4 py-2 text-left font-medium text-xs uppercase tracking-wider">Type</th>
                            <th class="px-4 py-2 text-left font-medium text-xs uppercase tracking-wider">Default</th>
                            <th class="px-4 py-2 text-left font-medium text-xs uppercase tracking-wider">Description</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-border/40">
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Class</td><td class="px-4 py-2 font-mono text-xs">string?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">CSS classes applied to the wrapper around ChildContent + the toolbar's filter chips.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">ChildContent</td><td class="px-4 py-2 font-mono text-xs">RenderFragment?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Your custom buttons and any of the built-in toolbar tool components.</td></tr>
                    </tbody>
                </table>
            </Stack>
        </Stack>

        <Stack>
            <Heading Level="3" Class="mb-3">Toolbar Tool Components</Heading>
            <Lumeo.Text Size="sm" Color="muted" Class="mb-3">Built-in tool components you can drop inside <code>&lt;ToolbarContent&gt;</code>. Each takes only <code>TItem</code> and reads everything else (selection, columns, export formats, layout config, Interop, Localizer) from a cascading <code>DataGridToolbarContext&lt;TItem&gt;</code>.</Lumeo.Text>
            <Stack Class="overflow-x-auto rounded-lg border border-border/40">
                <table class="w-full min-w-[600px] text-sm">
                    <thead>
                        <tr class="border-b border-border/40 bg-muted/30">
                            <th class="px-4 py-2 text-left font-medium text-xs uppercase tracking-wider">Component</th>
                            <th class="px-4 py-2 text-left font-medium text-xs uppercase tracking-wider">Renders</th>
                            <th class="px-4 py-2 text-left font-medium text-xs uppercase tracking-wider">Visible when</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-border/40">
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">&lt;DataGridToolbarFullscreen&gt;</td><td class="px-4 py-2 text-muted-foreground">Fullscreen toggle icon button.</td><td class="px-4 py-2 text-muted-foreground"><code>Expandable="true"</code> on the grid.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">&lt;DataGridToolbarCopySelected&gt;</td><td class="px-4 py-2 text-muted-foreground">Copy-to-clipboard button (tab-separated, selected rows).</td><td class="px-4 py-2 text-muted-foreground">At least one row is selected.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">&lt;DataGridToolbarColumns&gt;</td><td class="px-4 py-2 text-muted-foreground">Column visibility + reorder dropdown.</td><td class="px-4 py-2 text-muted-foreground">Always (gate on the grid via <code>ShowColumnChooser</code>).</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">&lt;DataGridToolbarExport&gt;</td><td class="px-4 py-2 text-muted-foreground">Export dropdown (CSV / Excel / PDF per <code>ExportFormats</code>).</td><td class="px-4 py-2 text-muted-foreground"><code>ExportFormats != None</code>.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">&lt;DataGridToolbarLayouts&gt;</td><td class="px-4 py-2 text-muted-foreground">Saved-layouts panel (personal + global).</td><td class="px-4 py-2 text-muted-foreground"><code>EnableLayoutPersistence="true"</code>.</td></tr>
                    </tbody>
                </table>
            </Stack>
            <Lumeo.Text Size="xs" Color="muted" Class="mt-2">Omit <code>&lt;ToolbarContent&gt;</code> and all five tools auto-render in the default order (Fullscreen, CopySelected, Columns, Export, Layouts) gated by their <code>Show*</code> flags on the DataGrid. Provide <code>&lt;ToolbarContent&gt;</code> and the default stack is suppressed entirely — you pick which tools to include and where, no duplicates.</Lumeo.Text>
        </Stack>

        <Stack>
            <Heading Level="3" Class="mb-3">DataGridColumnDef&lt;TItem&gt;</Heading>
            <Stack Class="overflow-x-auto rounded-lg border border-border/40">
                <table class="w-full min-w-[600px] text-sm">
                    <thead>
                        <tr class="border-b border-border/40 bg-muted/30">
                            <th class="px-4 py-2 text-left font-medium text-foreground">Property</th>
                            <th class="px-4 py-2 text-left font-medium text-foreground">Type</th>
                            <th class="px-4 py-2 text-left font-medium text-foreground">Default</th>
                            <th class="px-4 py-2 text-left font-medium text-foreground">Description</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-border/40">
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Title</td><td class="px-4 py-2 font-mono text-xs">string?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Column header text.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Field</td><td class="px-4 py-2 font-mono text-xs">string?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Property name to bind to on TItem.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">FieldSelector</td><td class="px-4 py-2 font-mono text-xs">Func&lt;TItem, object?&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Custom value accessor (alternative to Field).</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Width</td><td class="px-4 py-2 font-mono text-xs">double?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Column width in pixels.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">MinWidth</td><td class="px-4 py-2 font-mono text-xs">double?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Minimum column width in pixels during resize.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">MaxWidth</td><td class="px-4 py-2 font-mono text-xs">double?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Maximum column width in pixels during resize.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Sortable</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Enable sorting for this column.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Filterable</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Enable filtering for this column.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Resizable</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">true</td><td class="px-4 py-2 text-muted-foreground">Allow column resizing via drag handle.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Pin</td><td class="px-4 py-2 font-mono text-xs">PinDirection</td><td class="px-4 py-2 text-xs">None</td><td class="px-4 py-2 text-muted-foreground">Pin column: None, Left, or Right.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Pinnable</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Whether column can be pinned/unpinned via the UI.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Groupable</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">false</td><td class="px-4 py-2 text-muted-foreground">Whether column can be used for grouping.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Reorderable</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-xs">true</td><td class="px-4 py-2 text-muted-foreground">Whether this column can be reordered (via header drag-and-drop or the Toggle Columns menu arrows). Set to <code>false</code> to pin an individual column's position even when the grid-level <code>Reorderable</code> is true.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">FilterType</td><td class="px-4 py-2 font-mono text-xs">DataGridFilterType</td><td class="px-4 py-2 text-xs">Text</td><td class="px-4 py-2 text-muted-foreground">Filter input type: Text, Number, Date, Select, Boolean.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">FilterOptions</td><td class="px-4 py-2 font-mono text-xs">List&lt;FilterOption&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Options for Select filter type (label + value pairs).</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Operators</td><td class="px-4 py-2 font-mono text-xs">List&lt;FilterOperator&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Optional whitelist of operators to show in the filter UI for this column. When null, defaults for the FilterType are used.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">FilterTemplate</td><td class="px-4 py-2 font-mono text-xs">RenderFragment&lt;DataGridFilterTemplateContext&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Custom filter UI for this column. Replaces the built-in operator + value inputs when provided. Invoke the context's Apply callback with a FilterDescriptor to commit, or null to clear.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Format</td><td class="px-4 py-2 font-mono text-xs">string?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Format string (e.g., "C0", "yyyy-MM-dd", "N2").</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Aggregate</td><td class="px-4 py-2 font-mono text-xs">AggregateType</td><td class="px-4 py-2 text-xs">None</td><td class="px-4 py-2 text-muted-foreground">Footer aggregate: None, Sum, Average, Count, Min, Max.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">CustomSort</td><td class="px-4 py-2 font-mono text-xs">Comparison&lt;object?&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Custom sort comparison function for this column.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">CellClass</td><td class="px-4 py-2 font-mono text-xs">Func&lt;TItem, string&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Function returning dynamic CSS class(es) for each cell.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">HeaderCssClass</td><td class="px-4 py-2 font-mono text-xs">string?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Custom CSS class for the header cell.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">CellTemplate</td><td class="px-4 py-2 font-mono text-xs">RenderFragment&lt;TItem&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Custom render template for cell content.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">HeaderTemplate</td><td class="px-4 py-2 font-mono text-xs">RenderFragment?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Custom render template for header.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">EditTemplate</td><td class="px-4 py-2 font-mono text-xs">RenderFragment&lt;CellEditContext&lt;TItem&gt;&gt;?</td><td class="px-4 py-2 text-xs">--</td><td class="px-4 py-2 text-muted-foreground">Custom template for inline editing input.</td></tr>
                    </tbody>
                </table>
            </Stack>
        </Stack>

        <Stack>
            <Heading Level="3" Class="mb-3">Event Args</Heading>
            <Stack Class="overflow-x-auto rounded-lg border border-border/40">
                <table class="w-full min-w-[600px] text-sm">
                    <thead>
                        <tr class="border-b border-border/40 bg-muted/30">
                            <th class="px-4 py-2 text-left font-medium text-foreground">Type</th>
                            <th class="px-4 py-2 text-left font-medium text-foreground">Properties</th>
                            <th class="px-4 py-2 text-left font-medium text-foreground">Description</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-border/40">
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">CellEditEventArgs&lt;TItem&gt;</td><td class="px-4 py-2 font-mono text-xs">Item, Field, OldValue, NewValue</td><td class="px-4 py-2 text-muted-foreground">Fired when a cell edit is committed.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">RowEditEventArgs&lt;TItem&gt;</td><td class="px-4 py-2 font-mono text-xs">Item, ChangedValues</td><td class="px-4 py-2 text-muted-foreground">Fired when a row edit is committed. ChangedValues is a Dictionary&lt;string, object?&gt;.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">ColumnReorderEventArgs</td><td class="px-4 py-2 font-mono text-xs">ColumnId, OldIndex, NewIndex</td><td class="px-4 py-2 text-muted-foreground">Fired when a column is drag-reordered.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">RowReorderEventArgs&lt;TItem&gt;</td><td class="px-4 py-2 font-mono text-xs">Item, OldIndex, NewIndex</td><td class="px-4 py-2 text-muted-foreground">Fired when a row is drag-reordered.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">DataGridServerRequest</td><td class="px-4 py-2 font-mono text-xs">Page, PageSize, Sorts?, Filters?, GlobalSearch?, GroupBy?, CancellationToken</td><td class="px-4 py-2 text-muted-foreground">Sent to OnServerRequest with current grid state. GroupBy contains the current group field if set. CancellationToken should be passed to your HTTP client to abort superseded requests.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">DataGridLayout</td><td class="px-4 py-2 font-mono text-xs">Columns, Sorts?, Filters?, PageSize?, GlobalSearch?</td><td class="px-4 py-2 text-muted-foreground">Serializable layout snapshot for persistence.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">DataGridNamedLayout</td><td class="px-4 py-2 font-mono text-xs">Id, Name, Scope, Layout</td><td class="px-4 py-2 text-muted-foreground">A named layout entry. Scope is "Personal", "Global", or "SystemDefault".</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">DataGridLayoutSnapshot</td><td class="px-4 py-2 font-mono text-xs">Version, Columns, Sorts, Filters, GlobalSearch?, CurrentPage, PageSize, GroupBy?</td><td class="px-4 py-2 text-muted-foreground">Public, JSON-serializable shape used by <code>ExportLayout</code>/<code>ApplyLayoutJsonAsync</code>. Version is bumped on schema changes.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">DataGridColumnLayout</td><td class="px-4 py-2 font-mono text-xs">Field, Order, Visible, Width?, Pin?</td><td class="px-4 py-2 text-muted-foreground">Per-column entry inside a <code>DataGridLayoutSnapshot</code>.</td></tr>
                    </tbody>
                </table>
            </Stack>
        </Stack>

        <Stack>
            <Heading Level="3" Class="mb-3">Enums</Heading>
            <Grid Columns="2" Gap="4">
                <Stack Class="overflow-x-auto rounded-lg border border-border/40">
                    <table class="w-full min-w-[600px] text-sm">
                        <thead>
                            <tr class="border-b border-border/40 bg-muted/30">
                                <th class="px-4 py-2 text-left font-medium text-foreground" colspan="2">DataGridSelectionMode</th>
                            </tr>
                        </thead>
                        <tbody class="divide-y divide-border/40">
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">None</td><td class="px-4 py-2 text-muted-foreground">No selection.</td></tr>
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">Single</td><td class="px-4 py-2 text-muted-foreground">Select one row at a time.</td></tr>
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">Multiple</td><td class="px-4 py-2 text-muted-foreground">Select multiple rows with checkboxes.</td></tr>
                        </tbody>
                    </table>
                </Stack>
                <Stack Class="overflow-x-auto rounded-lg border border-border/40">
                    <table class="w-full min-w-[600px] text-sm">
                        <thead>
                            <tr class="border-b border-border/40 bg-muted/30">
                                <th class="px-4 py-2 text-left font-medium text-foreground" colspan="2">PinDirection</th>
                            </tr>
                        </thead>
                        <tbody class="divide-y divide-border/40">
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">None</td><td class="px-4 py-2 text-muted-foreground">Column scrolls normally.</td></tr>
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">Left</td><td class="px-4 py-2 text-muted-foreground">Sticky to left edge.</td></tr>
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">Right</td><td class="px-4 py-2 text-muted-foreground">Sticky to right edge.</td></tr>
                        </tbody>
                    </table>
                </Stack>
                <Stack Class="overflow-x-auto rounded-lg border border-border/40">
                    <table class="w-full min-w-[600px] text-sm">
                        <thead>
                            <tr class="border-b border-border/40 bg-muted/30">
                                <th class="px-4 py-2 text-left font-medium text-foreground" colspan="2">DataGridEditMode</th>
                            </tr>
                        </thead>
                        <tbody class="divide-y divide-border/40">
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">None</td><td class="px-4 py-2 text-muted-foreground">No editing.</td></tr>
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">Cell</td><td class="px-4 py-2 text-muted-foreground">Click a cell to edit inline.</td></tr>
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">Row</td><td class="px-4 py-2 text-muted-foreground">Edit entire row at once.</td></tr>
                        </tbody>
                    </table>
                </Stack>
                <Stack Class="overflow-x-auto rounded-lg border border-border/40">
                    <table class="w-full min-w-[600px] text-sm">
                        <thead>
                            <tr class="border-b border-border/40 bg-muted/30">
                                <th class="px-4 py-2 text-left font-medium text-foreground" colspan="2">AggregateType</th>
                            </tr>
                        </thead>
                        <tbody class="divide-y divide-border/40">
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">None</td><td class="px-4 py-2 text-muted-foreground">No aggregation.</td></tr>
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">Sum</td><td class="px-4 py-2 text-muted-foreground">Sum of numeric values.</td></tr>
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">Average</td><td class="px-4 py-2 text-muted-foreground">Average of numeric values.</td></tr>
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">Count</td><td class="px-4 py-2 text-muted-foreground">Count of non-null values.</td></tr>
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">Min</td><td class="px-4 py-2 text-muted-foreground">Minimum value.</td></tr>
                            <tr><td class="px-4 py-2 font-mono text-xs text-primary">Max</td><td class="px-4 py-2 text-muted-foreground">Maximum value.</td></tr>
                        </tbody>
                    </table>
                </Stack>
            </Grid>
        </Stack>

        <Stack>
            <Heading Level="3" Class="mb-3">DataGridFilterType</Heading>
            <Stack Class="overflow-x-auto rounded-lg border border-border/40">
                <table class="w-full min-w-[600px] text-sm">
                    <thead>
                        <tr class="border-b border-border/40 bg-muted/30">
                            <th class="px-4 py-2 text-left font-medium text-foreground">Value</th>
                            <th class="px-4 py-2 text-left font-medium text-foreground">Description</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-border/40">
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Text</td><td class="px-4 py-2 text-muted-foreground">Text filter with contains, starts with, equals operators.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Number</td><td class="px-4 py-2 text-muted-foreground">Numeric filter with comparison and between operators.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Date</td><td class="px-4 py-2 text-muted-foreground">Date filter with date picker and comparison operators.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Select</td><td class="px-4 py-2 text-muted-foreground">Multi-select checkbox filter from predefined options.</td></tr>
                        <tr><td class="px-4 py-2 font-mono text-xs text-primary">Boolean</td><td class="px-4 py-2 text-muted-foreground">True/false toggle filter.</td></tr>
                    </tbody>
                </table>
            </Stack>
        </Stack>
    </Stack>
</Stack>

<div class="mt-12 space-y-3">
    <Heading Id="tree-grid" Level="2" Class="scroll-mt-20">Tree-grid mode (hierarchical rows)</Heading>
    <p class="text-muted-foreground text-sm">
        Set <code>ChildItemsSelector</code> to a function that returns a row's children
        (<code>null</code>/empty for leaves) and the grid renders a recursive tree-grid:
        the first visible column gets an expand/collapse chevron indented by depth, the
        <code>&lt;table&gt;</code> uses <code>role="treegrid"</code>, and expandable rows carry
        <code>aria-expanded</code> / <code>aria-level</code>. Sorting, filtering and paging apply
        to the <em>root</em> items; a paged-in root shows its whole expanded subtree. Tree-grid
        mode and <code>DetailTemplate</code> master-detail are mutually exclusive — tree mode wins.
    </p>
    <div class="overflow-x-auto rounded-lg border border-border/40">
        <table class="w-full text-sm">
            <thead class="bg-muted/40"><tr><th class="px-4 py-2 text-left font-medium">Parameter</th><th class="px-4 py-2 text-left font-medium">Type</th><th class="px-4 py-2 text-left font-medium">Description</th></tr></thead>
            <tbody class="divide-y divide-border/40">
                <tr><td class="px-4 py-2 font-mono text-xs text-primary">ChildItemsSelector</td><td class="px-4 py-2 font-mono text-xs">Func&lt;TItem, IEnumerable&lt;TItem&gt;?&gt;?</td><td class="px-4 py-2 text-muted-foreground">When set, enables tree-grid mode; returns a row's child rows.</td></tr>
                <tr><td class="px-4 py-2 font-mono text-xs text-primary">TreeGridDefaultExpanded</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-muted-foreground">Whether tree nodes start expanded. Default <code>false</code>.</td></tr>
                <tr><td class="px-4 py-2 font-mono text-xs text-primary">TreeColumnField</td><td class="px-4 py-2 font-mono text-xs">string?</td><td class="px-4 py-2 text-muted-foreground">Field of the column carrying the chevron + indentation. Null = first visible column.</td></tr>
                <tr><td class="px-4 py-2 font-mono text-xs text-primary">OnTreeNodeExpand</td><td class="px-4 py-2 font-mono text-xs">EventCallback&lt;TItem&gt;</td><td class="px-4 py-2 text-muted-foreground">Raised when a tree node is expanded or collapsed.</td></tr>
            </tbody>
        </table>
    </div>

    <Heading Id="group-panel" Level="2" Class="scroll-mt-20 mt-8">Group panel &amp; multi-level grouping</Heading>
    <p class="text-muted-foreground text-sm">
        Set <code>ShowGroupPanel="true"</code> to render a strip above the grid listing the active
        grouping levels as removable chips, plus an "add level" dropdown of every column flagged
        <code>Groupable</code>. Use <code>GroupByFields</code> (an ordered list of column fields) for
        nested multi-level grouping — the grid renders nested group rows with increasing indentation
        and a per-group aggregate row (the same <code>Column.Aggregate</code> values as the grand-total
        footer, computed over each group). A single-element list (or the legacy <code>GroupBy</code>
        string) keeps the classic single-level grouping.
    </p>
    <p class="text-muted-foreground text-sm mt-2">
        <strong>Works in <code>ServerMode</code> too.</strong> Both single-level <code>GroupBy</code>
        and multi-level <code>GroupByFields</code> are applied client-side to whatever rows the server
        returned, and the runtime panel handlers (drag-to-add, add-level dropdown, per-chip remove,
        clear-all) trigger an in-place regroup without an extra server round-trip. Grouping operates
        over the <em>current page</em> only — for cross-page grouping, either disable pagination
        (<code>ShowPagination="false"</code>) so the server delivers all rows in one response, or have
        the server pre-aggregate groups itself.
    </p>
    <div class="overflow-x-auto rounded-lg border border-border/40">
        <table class="w-full text-sm">
            <thead class="bg-muted/40"><tr><th class="px-4 py-2 text-left font-medium">Parameter</th><th class="px-4 py-2 text-left font-medium">Type</th><th class="px-4 py-2 text-left font-medium">Description</th></tr></thead>
            <tbody class="divide-y divide-border/40">
                <tr><td class="px-4 py-2 font-mono text-xs text-primary">ShowGroupPanel</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-muted-foreground">Shows the grouping chip strip + add-level dropdown. Default <code>false</code>.</td></tr>
                <tr><td class="px-4 py-2 font-mono text-xs text-primary">GroupByFields</td><td class="px-4 py-2 font-mono text-xs">IReadOnlyList&lt;string&gt;?</td><td class="px-4 py-2 text-muted-foreground">Ordered column fields for multi-level grouping; takes precedence over <code>GroupBy</code>. Works in both client and <code>ServerMode</code> (grouping runs over the rows the server returned for the current page).</td></tr>
                <tr><td class="px-4 py-2 font-mono text-xs text-primary">GroupPanelText</td><td class="px-4 py-2 font-mono text-xs">string?</td><td class="px-4 py-2 text-muted-foreground">Placeholder hint shown in the panel when nothing is grouped.</td></tr>
                <tr><td class="px-4 py-2 font-mono text-xs text-primary">Column.Groupable</td><td class="px-4 py-2 font-mono text-xs">bool</td><td class="px-4 py-2 text-muted-foreground">Gates whether a column can be added to the group panel.</td></tr>
            </tbody>
        </table>
    </div>
</div>

<div class="mt-12">
    <Heading Id="related" Level="2" Class="scroll-mt-20">Related Components</Heading>
    <ul class="mt-4 space-y-2 text-sm">
        <li><a href="/components/data-table" class="text-primary hover:underline">DataTable</a> — A simpler table component for basic tabular data without advanced grid features</li>
        <li><a href="/components/table" class="text-primary hover:underline">Table</a> — Low-level table building blocks for fully custom table layouts</li>
        <li><a href="/components/pagination" class="text-primary hover:underline">Pagination</a> — Standalone pagination control for navigating through paged data</li>
    </ul>
</div>

@code {
    // ---- Virtualised server mode demo ----------------------------------
    // Simulates a 50,000-row backend on top of the existing _employees seed.
    // Sort/filter/search context arrives in DataGridRangeRequest; we apply
    // it in-memory and slice the requested window.
    private List<Employee>? _virtualBackend;

    private List<Employee> VirtualBackend
    {
        get
        {
            if (_virtualBackend is not null) return _virtualBackend;
            _virtualBackend = new List<Employee>(50_000);
            for (var i = 0; i < 50_000; i++)
            {
                var seed = _employees[i % _employees.Count];
                _virtualBackend.Add(new Employee(
                    $"{seed.Name} #{i + 1}",
                    seed.Department,
                    seed.Salary + (i % 13) * 100,
                    seed.Status,
                    seed.StartDate));
            }
            return _virtualBackend;
        }
    }

    private ValueTask<DataGridRangeResponse<Employee>> FakeRangeRequest(DataGridRangeRequest req)
    {
        IEnumerable<Employee> q = VirtualBackend;
        if (!string.IsNullOrWhiteSpace(req.GlobalSearch))
        {
            var s = req.GlobalSearch.ToLowerInvariant();
            q = q.Where(e => e.Name.ToLowerInvariant().Contains(s)
                          || e.Department.ToLowerInvariant().Contains(s)
                          || e.Status.ToLowerInvariant().Contains(s));
        }
        if (req.Filters is not null)
        {
            foreach (var f in req.Filters)
            {
                if (string.IsNullOrEmpty(f.Field) || string.IsNullOrEmpty(f.Value?.ToString())) continue;
                var fieldVal = f.Value!.ToString()!.ToLowerInvariant();
                q = f.Field switch
                {
                    "Name"       => q.Where(e => e.Name.ToLowerInvariant().Contains(fieldVal)),
                    "Department" => q.Where(e => e.Department.ToLowerInvariant().Contains(fieldVal)),
                    "Status"     => q.Where(e => e.Status.ToLowerInvariant().Contains(fieldVal)),
                    _ => q
                };
            }
        }
        if (req.Sorts is not null && req.Sorts.Count > 0)
        {
            IOrderedEnumerable<Employee>? ordered = null;
            foreach (var s in req.Sorts)
            {
                Func<Employee, object?> key = s.Field switch
                {
                    "Name"       => e => e.Name,
                    "Department" => e => e.Department,
                    "Salary"     => e => e.Salary,
                    _ => e => e.Name
                };
                ordered = ordered is null
                    ? (s.Direction == SortDirection.Descending ? q.OrderByDescending(key) : q.OrderBy(key))
                    : (s.Direction == SortDirection.Descending ? ordered.ThenByDescending(key) : ordered.ThenBy(key));
            }
            q = ordered ?? q;
        }
        var materialised = q.ToList();
        var slice = materialised.Skip(req.StartIndex).Take(req.Count).ToList();
        return ValueTask.FromResult(new DataGridRangeResponse<Employee>(slice, materialised.Count));
    }

    private Employee? _selectedEmployee;
    private int _multiSelectedCount;
    private string? _lastEditInfo;
    private string? _lastRowEditInfo;
    private string? _savedLayoutNotice;
    private string _groupBy = "Department";
    private bool _groupsExpandedByDefault = true;
    private string _groupPanelMode = "on";
    private string _groupPanelLevels = "single";

    private List<DataGridNamedLayout> _globalLayouts = new()
    {
        new DataGridNamedLayout("global-1", "Engineering View", "Global", new DataGridLayout
        {
            Columns = new()
            {
                new ColumnLayout { Field = "Name", Visible = true, Order = 0, Width = 200 },
                new ColumnLayout { Field = "Department", Visible = true, Order = 1, Width = 150 },
                new ColumnLayout { Field = "Salary", Visible = true, Order = 2, Width = 120 },
                new ColumnLayout { Field = "Status", Visible = false, Order = 3 },
            }
        }),
        new DataGridNamedLayout("global-2", "HR Overview", "Global", new DataGridLayout
        {
            Columns = new()
            {
                new ColumnLayout { Field = "Name", Visible = true, Order = 0, Width = 200 },
                new ColumnLayout { Field = "Status", Visible = true, Order = 1, Width = 100 },
                new ColumnLayout { Field = "Department", Visible = true, Order = 2, Width = 150 },
                new ColumnLayout { Field = "Salary", Visible = false, Order = 3 },
            }
        }),
        new DataGridNamedLayout("global-3", "Salary Report", "Global", new DataGridLayout
        {
            Columns = new()
            {
                new ColumnLayout { Field = "Name", Visible = true, Order = 0, Width = 200 },
                new ColumnLayout { Field = "Salary", Visible = true, Order = 1, Width = 150 },
                new ColumnLayout { Field = "Department", Visible = true, Order = 2, Width = 150 },
                new ColumnLayout { Field = "Status", Visible = true, Order = 3, Width = 100 },
            },
            Sorts = new() { new SortDescriptor("Salary", SortDirection.Descending) }
        }),
    };

    private List<Employee> _employees = new()
    {
        new("Alice Johnson", "Engineering", 95000, "Active", new DateTime(2021, 3, 15)),
        new("Bob Smith", "Marketing", 72000, "Active", new DateTime(2020, 7, 1)),
        new("Carol White", "Engineering", 105000, "Active", new DateTime(2019, 11, 20)),
        new("David Brown", "Sales", 68000, "Inactive", new DateTime(2022, 1, 10)),
        new("Eve Davis", "Engineering", 98000, "Active", new DateTime(2020, 5, 5)),
        new("Frank Wilson", "Marketing", 75000, "Active", new DateTime(2021, 8, 22)),
        new("Grace Lee", "Sales", 82000, "Active", new DateTime(2019, 6, 30)),
        new("Henry Taylor", "Engineering", 110000, "Active", new DateTime(2018, 9, 12)),
        new("Ivy Martinez", "Marketing", 71000, "Inactive", new DateTime(2022, 4, 18)),
        new("Jack Anderson", "Sales", 88000, "Active", new DateTime(2020, 12, 3)),
        new("Kate Thomas", "Engineering", 102000, "Active", new DateTime(2021, 2, 14)),
        new("Leo Jackson", "Sales", 79000, "Active", new DateTime(2019, 10, 8)),
    };

    private List<Employee> _editableEmployees = new()
    {
        new("Alice Johnson", "Engineering", 95000, "Active", new DateTime(2021, 3, 15)),
        new("Bob Smith", "Marketing", 72000, "Active", new DateTime(2020, 7, 1)),
        new("Carol White", "Engineering", 105000, "Active", new DateTime(2019, 11, 20)),
        new("David Brown", "Sales", 68000, "Inactive", new DateTime(2022, 1, 10)),
        new("Eve Davis", "Engineering", 98000, "Active", new DateTime(2020, 5, 5)),
    };

    private List<Employee> _rowEditEmployees = new()
    {
        new("Alice Johnson", "Engineering", 95000, "Active", new DateTime(2021, 3, 15)),
        new("Bob Smith", "Marketing", 72000, "Active", new DateTime(2020, 7, 1)),
        new("Carol White", "Engineering", 105000, "Active", new DateTime(2019, 11, 20)),
        new("David Brown", "Sales", 68000, "Inactive", new DateTime(2022, 1, 10)),
        new("Eve Davis", "Engineering", 98000, "Active", new DateTime(2020, 5, 5)),
    };

    // --- Batch-edit demo ---
    private List<Employee> _batchEmployees = new()
    {
        new("Alice Johnson", "Engineering", 95000, "Active", new DateTime(2021, 3, 15)),
        new("Bob Smith", "Marketing", 72000, "Active", new DateTime(2020, 7, 1)),
        new("Carol White", "Engineering", 105000, "Active", new DateTime(2019, 11, 20)),
    };
    private string? _lastBatchSummary;

    private Task HandleBatchSave(DataGridBatchSaveEventArgs<Employee> args)
    {
        _lastBatchSummary =
            $"Saved {args.Modified.Count} modified row(s), {args.Added.Count} new row(s).";
        return Task.CompletedTask;
    }

    // Server-side demo state
    private List<Employee> _serverItems = new();
    private int _serverTotalCount;

    private async Task LoadServerData(DataGridServerRequest request)
    {
        try
        {
            await Task.Delay(500, request.CancellationToken);

            var query = _employees.AsQueryable();

            if (!string.IsNullOrEmpty(request.GlobalSearch))
            {
                var s = request.GlobalSearch;
                query = query.Where(e =>
                    e.Name.Contains(s, StringComparison.OrdinalIgnoreCase) ||
                    e.Department.Contains(s, StringComparison.OrdinalIgnoreCase) ||
                    e.Status.Contains(s, StringComparison.OrdinalIgnoreCase));
            }

            if (request.Sorts?.Count > 0)
            {
                var sort = request.Sorts[0];
                query = (sort.Field, sort.Direction) switch
                {
                    ("Name", SortDirection.Ascending) => query.OrderBy(e => e.Name),
                    ("Name", SortDirection.Descending) => query.OrderByDescending(e => e.Name),
                    ("Department", SortDirection.Ascending) => query.OrderBy(e => e.Department),
                    ("Department", SortDirection.Descending) => query.OrderByDescending(e => e.Department),
                    ("Salary", SortDirection.Ascending) => query.OrderBy(e => e.Salary),
                    ("Salary", SortDirection.Descending) => query.OrderByDescending(e => e.Salary),
                    _ => query
                };
            }

            _serverTotalCount = query.Count();
            _serverItems = query
                .Skip((request.Page - 1) * request.PageSize)
                .Take(request.PageSize)
                .ToList();
        }
        catch (OperationCanceledException) { }
    }

    // Error-handling demo state
    private List<Employee> _errorItems = new();
    private int _errorTotalCount;
    private bool _simulateError;
    private string? _serverErrorDisplay;

    private async Task LoadErrorData(DataGridServerRequest request)
    {
        await Task.Delay(300, request.CancellationToken);
        if (_simulateError)
            throw new InvalidOperationException("Connection timeout: the remote server did not respond.");
        _errorTotalCount = _employees.Count;
        _errorItems = _employees
            .Skip((request.Page - 1) * request.PageSize)
            .Take(request.PageSize)
            .ToList();
    }

    private void OnSelectionChanged(IReadOnlyList<Employee> items)
    {
        _selectedEmployee = items.FirstOrDefault();
    }

    private void OnMultiSelectionChanged(IReadOnlyList<Employee> items)
    {
        _multiSelectedCount = items.Count;
    }

    private Task HandleCellEdit(CellEditEventArgs<Employee> args)
    {
        _lastEditInfo = $"Edited {args.Field} on {args.Item.Name}: \"{args.OldValue}\" -> \"{args.NewValue}\"";
        return Task.CompletedTask;
    }

    private Task HandleRowEdit(RowEditEventArgs<Employee> args)
    {
        var changes = string.Join(", ", args.ChangedValues.Select(kv => $"{kv.Key}={kv.Value}"));
        _lastRowEditInfo = $"Row edited for {args.Item.Name}: {changes}";
        return Task.CompletedTask;
    }

    private record Employee(string Name, string Department, double Salary, string Status, DateTime StartDate);

    private string _basicCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5"" ShowPagination=""true"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" Sortable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" Sortable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Sortable=""true"" Format=""C0"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Start Date"" Field=""StartDate"" Format=""yyyy-MM-dd"" />
</DataGrid>";

    private string _toolbarCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          ShowToolbar=""true"" ShowPagination=""true"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name""
                       Sortable=""true"" Filterable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary""
                       Filterable=""true"" FilterType=""DataGridFilterType.Number"" Format=""C0"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Status"" Field=""Status""
                       Filterable=""true"" />
</DataGrid>";

    private string _toolbarMinimalCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          ShowToolbar=""true""
          ShowColumnChooser=""false""
          ShowExport=""false"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" Sortable=""true"" Filterable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" Sortable=""true"" Filterable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Sortable=""true"" Format=""C0"" />
</DataGrid>";

    private string _toolbarExportOnlyCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          ShowToolbar=""true""
          ShowSearch=""false""
          ShowColumnChooser=""false"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" Sortable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" Sortable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Sortable=""true"" Format=""C0"" />
</DataGrid>";

    private string _toolbarCsvOnlyCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          ShowToolbar=""true""
          ExportFormats=""DataGridExportFormat.Csv"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" Sortable=""true"" Filterable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" Sortable=""true"" Filterable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Sortable=""true"" Format=""C0"" />
</DataGrid>";

    private string _multiSelectCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          SelectionMode=""DataGridSelectionMode.Multiple""
          SelectedItemsChanged=""@OnSelectionChanged"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Format=""C0"" />
</DataGrid>";

    private string _singleSelectCode = @"<DataGrid TItem=""Employee"" Items=""@employees""
          SelectionMode=""DataGridSelectionMode.Single""
          SelectedItemsChanged=""@OnSelectionChanged"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Format=""C0"" />
</DataGrid>";

    private string _styledCode = @"<DataGrid TItem=""Employee"" Items=""@employees""
          Striped=""true"" Bordered=""true"" Compact=""true"" PageSize=""5"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Format=""C0"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Status"" Field=""Status"" />
</DataGrid>";

    private string _groupingCode = @"<DataGrid TItem=""Employee"" Items=""@employees""
          GroupBy=""Department""
          GroupsExpandedByDefault=""true"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" Sortable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Status"" Field=""Status"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary""
                       Format=""C0"" Aggregate=""AggregateType.Average"" />
</DataGrid>";

    private string _groupPanelCode = @"@*  ShowGroupPanel renders a chip bar above the grid; users drag
    chips to reorder grouping levels and click × to remove them.
    Columns flagged Groupable=""true"" appear in the ""add level"" dropdown.
    GroupByFields is an ordered list — index 0 is the outermost level. *@
<DataGrid TItem=""Employee"" Items=""@employees""
          ShowGroupPanel=""true""
          GroupByFields=""@(new[] { ""Department"", ""Status"" })"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" Sortable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department""
                       Sortable=""true"" Groupable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Status"" Field=""Status"" Groupable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary""
                       Format=""C0"" Sortable=""true""
                       Aggregate=""AggregateType.Average"" />
</DataGrid>";

    private string _templateCode = @"<DataGridColumnDef TItem=""Employee"" Title=""Employee"" Field=""Name"">
    <CellTemplate>
        <Flex Align=""center"" Gap=""2"">
            <Avatar Size=""Avatar.AvatarSize.Sm"">
                <AvatarFallback>@context.Name[..2]</AvatarFallback>
            </Avatar>
            <Stack Gap=""0"">
                <Lumeo.Text As=""span"" Weight=""medium"">@context.Name</Lumeo.Text>
                <Lumeo.Text As=""div"" Size=""xs"" Color=""muted"">@context.Department</Lumeo.Text>
            </Stack>
        </Flex>
    </CellTemplate>
</DataGridColumnDef>";

    private string _detailCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5"">
    <DetailTemplate>
        <Stack Gap=""2"" Class=""p-4"">
            <Lumeo.Text Size=""sm""><Lumeo.Text As=""span"" Weight=""medium"">Full Name:</Lumeo.Text> @context.Name</Lumeo.Text>
            <Lumeo.Text Size=""sm""><Lumeo.Text As=""span"" Weight=""medium"">Department:</Lumeo.Text> @context.Department</Lumeo.Text>
        </Stack>
    </DetailTemplate>
    <ChildContent>
        <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" />
        <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
    </ChildContent>
</DataGrid>";

    private string _pinCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name""
                       Pin=""PinDirection.Left"" Width=""180"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" Width=""150"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Format=""C0"" Width=""120"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Actions"" Field=""Name""
                       Pin=""PinDirection.Right"" Width=""100"">
        <CellTemplate>...</CellTemplate>
    </DataGridColumnDef>
</DataGrid>";

    private string _resizeCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name""
                       Width=""200"" MinWidth=""100"" MaxWidth=""400"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department""
                       Width=""150"" MinWidth=""80"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary""
                       Format=""C0"" Width=""120"" MinWidth=""80"" MaxWidth=""200"" />
</DataGrid>";

    private string _editCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          EditMode=""DataGridEditMode.Cell"" OnCellEdit=""@HandleCellEdit"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Status"" Field=""Status"" />
</DataGrid>";

    private string _rowEditCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          EditMode=""DataGridEditMode.Row"" OnRowEdit=""@HandleRowEdit"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Format=""C0"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Status"" Field=""Status"" />
</DataGrid>

@code {
    private Task HandleRowEdit(RowEditEventArgs<Employee> args)
    {
        var changes = string.Join("", "", args.ChangedValues.Select(kv => $""{kv.Key}={kv.Value}""));
        Console.WriteLine($""Row edited: {args.Item.Name} — {changes}"");
        return Task.CompletedTask;
    }
}";

    private string _aggregateCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""20"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name""
                       Aggregate=""AggregateType.Count"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary""
                       Format=""C0"" Aggregate=""AggregateType.Average"" />
</DataGrid>";

    private string _reorderCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          Reorderable=""true"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" Sortable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Format=""C0"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Status"" Field=""Status"" />
</DataGrid>";

    private string _rowStyleCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          RowClass=""@(e => e.Status == ""Inactive"" ? ""opacity-50"" : """")""
          RowStyle=""@(e => e.Salary > 100000 ? ""font-weight: 600;"" : """")"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Format=""C0"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Status"" Field=""Status"">
        <CellTemplate>
            <Badge Variant=""@(context.Status == ""Active""
                ? Badge.BadgeVariant.Default : Badge.BadgeVariant.Secondary)"">
                @context.Status
            </Badge>
        </CellTemplate>
    </DataGridColumnDef>
</DataGrid>";

    private string _contextMenuCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5"">
    <RowContextMenu>
        <Stack Gap=""0"">
            <Button Variant=""Button.ButtonVariant.Ghost"" Size=""Button.ButtonSize.Sm""
                    Class=""w-full justify-start gap-2 px-2"">
                <DynamicIcon Name=""Pencil"" Class=""h-3.5 w-3.5"" /> Edit
            </Button>
            <Button Variant=""Button.ButtonVariant.Ghost"" Size=""Button.ButtonSize.Sm""
                    Class=""w-full justify-start gap-2 px-2"">
                <DynamicIcon Name=""Copy"" Class=""h-3.5 w-3.5"" /> Duplicate
            </Button>
            <Separator Class=""my-1"" />
            <Button Variant=""Button.ButtonVariant.Ghost"" Size=""Button.ButtonSize.Sm""
                    Class=""w-full justify-start gap-2 px-2 text-destructive"">
                <DynamicIcon Name=""Trash2"" Class=""h-3.5 w-3.5"" /> Delete
            </Button>
        </Stack>
    </RowContextMenu>
    <ChildContent>
        <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" />
        <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
        <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Format=""C0"" />
    </ChildContent>
</DataGrid>";

    private string _layoutPersistenceCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          ShowToolbar=""true"" Reorderable=""true""
          EnableLayoutPersistence=""true""
          LayoutStorageKey=""my-grid-layout"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name""
                       Sortable=""true"" Filterable=""true"" Width=""200"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department""
                       Sortable=""true"" Width=""150"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary""
                       Format=""C0"" Width=""120"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Status"" Field=""Status"" Width=""100"" />
</DataGrid>

@* The toolbar Layouts dropdown lets users save named layouts (Personal scope)
   and reset to the System Default. Layouts are auto-saved to localStorage. *@";

    private string _namedLayoutCode = @"@code {
    private List<DataGridNamedLayout> _globalLayouts = new()
    {
        new DataGridNamedLayout(""global-1"", ""Engineering View"", ""Global"", new DataGridLayout
        {
            Columns = new()
            {
                new ColumnLayout { Field = ""Name"", Visible = true, Order = 0 },
                new ColumnLayout { Field = ""Department"", Visible = true, Order = 1 },
                new ColumnLayout { Field = ""Salary"", Visible = true, Order = 2 },
                new ColumnLayout { Field = ""Status"", Visible = false, Order = 3 },
            }
        }),
        new DataGridNamedLayout(""global-2"", ""HR Overview"", ""Global"", new DataGridLayout
        {
            Columns = new()
            {
                new ColumnLayout { Field = ""Name"", Visible = true, Order = 0 },
                new ColumnLayout { Field = ""Status"", Visible = true, Order = 1 },
                new ColumnLayout { Field = ""Department"", Visible = true, Order = 2 },
                new ColumnLayout { Field = ""Salary"", Visible = false, Order = 3 },
            }
        }),
    };
}

<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          ShowToolbar=""true"" Reorderable=""true""
          EnableLayoutPersistence=""true""
          LayoutStorageKey=""my-named-layout-grid""
          GlobalLayouts=""@_globalLayouts""
          OnSaveNamedLayout=""@(nl => Console.WriteLine($""Saved: {nl.Name}""))""
          OnDeleteNamedLayout=""@(id => Console.WriteLine($""Deleted: {id})""))"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name""
                       Sortable=""true"" Filterable=""true"" Width=""200"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department""
                       Sortable=""true"" Width=""150"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary""
                       Format=""C0"" Width=""120"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Status"" Field=""Status"" Width=""100"" />
</DataGrid>";

    private string _toolbarContentCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          ShowToolbar=""true"">
    <ChildContent>
        <ToolbarContent TItem=""Employee"" Class=""gap-3"">
            <Button Variant=""Button.ButtonVariant.Outline"" Size=""Button.ButtonSize.Sm""
                    Class=""gap-1.5"">
                <DynamicIcon Name=""Plus"" Class=""h-3.5 w-3.5"" />
                Add Employee
            </Button>
        </ToolbarContent>
        <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name""
                           Sortable=""true"" Filterable=""true"" />
        <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
        <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Format=""C0"" />
    </ChildContent>
</DataGrid>";

    private string _toolbarComposedCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          ShowToolbar=""true"">
    <ChildContent>
        @* Placing ToolbarContent suppresses the default right-hand tool stack — you
           pick exactly which tools to include and where. No need for Expandable /
           ShowExport / ShowColumnChooser flags; tools render wherever you place them. *@
        <ToolbarContent TItem=""Employee"" Class=""gap-2 flex-wrap"">
            <Button Variant=""Button.ButtonVariant.Outline"" Size=""Button.ButtonSize.Sm""
                    Class=""gap-1.5"">
                <DynamicIcon Name=""RefreshCw"" Class=""h-3.5 w-3.5"" />
                Refresh
            </Button>
            <DataGridToolbarColumns TItem=""Employee"" />
            <DataGridToolbarExport TItem=""Employee"" />
            <DataGridToolbarFullscreen TItem=""Employee"" />
        </ToolbarContent>
        <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" Sortable=""true"" />
        <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
    </ChildContent>
</DataGrid>";

    private string _serverModeCode = @"<DataGrid TItem=""Employee"" Items=""@_serverItems""
          ServerMode=""true""
          TotalCount=""@_totalCount""
          PageSize=""10""
          ShowPagination=""true""
          ShowToolbar=""true""
          OnServerRequest=""@LoadData"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name""
                       Sortable=""true"" Filterable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department""
                       Sortable=""true"" Filterable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary""
                       Format=""C0"" Sortable=""true"" />
</DataGrid>

@code {
    private List<Employee> _serverItems = new();
    private int _totalCount;

    private async Task LoadData(DataGridServerRequest request)
    {
        try
        {
            // Pass CancellationToken to your HTTP client — in-flight requests
            // are cancelled automatically when a newer interaction fires.
            var result = await _api.GetEmployees(
                page: request.Page,
                pageSize: request.PageSize,
                sorts: request.Sorts,
                filters: request.Filters,
                search: request.GlobalSearch,
                ct: request.CancellationToken);

            _serverItems = result.Items.ToList();
            _totalCount = result.TotalCount;
            // IsLoading is managed internally — do not set it manually.
        }
        catch (OperationCanceledException) { /* superseded request — ignore */ }
    }
}";

    private string _errorHandlingCode = @"<DataGrid TItem=""Employee"" Items=""@_items"" TotalCount=""@_total""
          ServerMode=""true"" OnServerRequest=""@LoadData""
          OnError=""@(ex => _errorMsg = ex.Message)"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" Sortable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Format=""C0"" />
</DataGrid>

@if (!string.IsNullOrEmpty(_errorMsg))
{
    <Alert>
        <AlertTitle>Server Error</AlertTitle>
        <AlertDescription>@_errorMsg</AlertDescription>
    </Alert>
}

@code {
    private string? _errorMsg;

    private async Task LoadData(DataGridServerRequest request)
    {
        // Exceptions thrown here are forwarded to OnError
        // instead of crashing the grid.
        var result = await _api.GetEmployees(request);
        _items = result.Items.ToList();
        _total = result.TotalCount;
    }
}";

    private string _multiSortCode = @"@* Click column headers to sort. Click again to cycle:
   Ascending -> Descending -> None.
   Sort indicators show the current direction. *@

<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5"">
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department""
                       Sortable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name""
                       Sortable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary""
                       Sortable=""true"" Format=""C0"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Start Date"" Field=""StartDate""
                       Sortable=""true"" Format=""yyyy-MM-dd"" />
</DataGrid>";

    private string _fixedHeightCode = @"<DataGrid TItem=""Employee"" Items=""@employees""
          Height=""250px"" ShowPagination=""false"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Format=""C0"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Status"" Field=""Status"" />
</DataGrid>";

    private string _virtualizedServerCode = @"// Backend interface — replace with your real API call
async ValueTask<DataGridRangeResponse<Employee>> LoadRange(DataGridRangeRequest req) {
    var resp = await Http.GetFromJsonAsync<EmployeeRangeResponse>(
        $""api/employees?skip={req.StartIndex}&take={req.Count}""
        + $""&sort={Encode(req.Sorts)}&filter={Encode(req.Filters)}""
        + $""&q={req.GlobalSearch}"",
        req.CancellationToken);
    return new DataGridRangeResponse<Employee>(resp!.Items, resp.TotalCount);
}

<DataGrid TItem=""Employee""
          Virtualized=""true""
          Height=""400px""
          OnRangeRequest=""@LoadRange""
          ShowToolbar=""true"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name""
                       Sortable=""true"" Filterable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" Sortable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Format=""C0"" Sortable=""true"" />
</DataGrid>";

    private string _pageSizeOptionsCode = @"<DataGrid TItem=""Employee"" Items=""@employees""
          PageSize=""10""
          PageSizeOptions=""new[] { 10, 50, 100, 200, 300 }""
          ShowPagination=""true"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" Sortable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Format=""C0"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Status"" Field=""Status"" />
</DataGrid>";

    private string _loadingCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5"" IsLoading=""true"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Format=""C0"" />
</DataGrid>";

    private string _emptyCode = @"<DataGrid TItem=""Employee"" Items=""@(new List<Employee>())"" PageSize=""5"">
    <EmptyContent>
        <Stack Gap=""3"" Align=""center"" Class=""py-4"">
            <DynamicIcon Name=""SearchX"" Class=""h-10 w-10 text-muted-foreground/40"" />
            <Lumeo.Text Size=""sm"" Color=""muted"">No employees found.</Lumeo.Text>
        </Stack>
    </EmptyContent>
    <ChildContent>
        <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" />
        <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" />
    </ChildContent>
</DataGrid>";

    private string _expandableCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          ShowPagination=""true"" ShowToolbar=""true""
          Expandable=""true"" FullscreenTitle=""Employees""
          SelectionMode=""DataGridSelectionMode.Multiple"">
    <DataGridColumnDef TItem=""Employee"" Title=""Name"" Field=""Name"" Sortable=""true"" Filterable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department"" Sortable=""true"" Filterable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary"" Sortable=""true"" Format=""C0""
                       Filterable=""true"" FilterType=""DataGridFilterType.Number"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Status"" Field=""Status"" Filterable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Start Date"" Field=""StartDate""
                       Sortable=""true"" Format=""yyyy-MM-dd"" />
</DataGrid>";

    private string _fullCode = @"<DataGrid TItem=""Employee"" Items=""@employees"" PageSize=""5""
          ShowToolbar=""true"" SelectionMode=""DataGridSelectionMode.Multiple""
          Striped=""true"" Hoverable=""true"" Reorderable=""true""
          RowClass=""@(e => e.Status == ""Inactive"" ? ""opacity-50"" : """")"">
    <DataGridColumnDef TItem=""Employee"" Title=""Employee"" Field=""Name""
                       Sortable=""true"" Filterable=""true""
                       Pin=""PinDirection.Left"" Width=""200"">
        <CellTemplate>
            <Flex Align=""center"" Gap=""2"">
                <Avatar Size=""Avatar.AvatarSize.Sm"">
                    <AvatarFallback>@context.Name[..2]</AvatarFallback>
                </Avatar>
                <Lumeo.Text As=""span"" Weight=""medium"">@context.Name</Lumeo.Text>
            </Flex>
        </CellTemplate>
    </DataGridColumnDef>
    <DataGridColumnDef TItem=""Employee"" Title=""Department"" Field=""Department""
                       Sortable=""true"" Filterable=""true"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Salary"" Field=""Salary""
                       Sortable=""true"" Filterable=""true""
                       FilterType=""DataGridFilterType.Number""
                       Format=""C0"" Aggregate=""AggregateType.Average"" />
    <DataGridColumnDef TItem=""Employee"" Title=""Status"" Field=""Status""
                       Filterable=""true"">
        <CellTemplate>
            <Badge Variant=""@(context.Status == ""Active"" ? Badge.BadgeVariant.Default : Badge.BadgeVariant.Secondary)"">
                @context.Status
            </Badge>
        </CellTemplate>
    </DataGridColumnDef>
</DataGrid>";
}
