npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@astroapps/datagrid-fluent-ui

v0.2.0

Published

Style @astroapps/datagrid to match the FluentUI v9 DataGrid

Downloads

303

Readme

@astroapps/datagrid-fluent-ui

Styles @astroapps/datagrid to look like the FluentUI v9 DataGrid, and wires up the row-level behaviour Fluent gets from its row elements.

The searching — sort, filter, paging, filter options, client vs server — belongs to @astroapps/datagrid-search, as does anything else a second renderer would need: row selection, and the rules for which row clicks count and when a pager is worth showing. This package renders what that gives it and adds no logic of its own. Read its README for how the state works; this one covers the Fluent side.

Every metric here was measured against a live Fluent v9 DataGrid rather than copied from docs — see src/app/fluentgrid/page.tsx in the formServer site for the harness that diffs the two grids' computed styles, and src/app/fluentgrid/features/page.tsx for a tour of the search features.

npm i @astroapps/datagrid-search @astroapps/datagrid-fluent-ui

A whole grid

const state = useControl<SearchRequest>({
  ...defaultSearchOptions,
  length: 25,
});
const data = useClientData(state, { rows, columns }); // or useServerData
const search = useGridSearch(state, { columns, data });

<FluentDataGrid search={search} size="medium" rowKey={(r) => r.id} />;

That's a sortable, filterable, paged grid. Everything it renders follows from the search rather than a flag:

| Affordance | Appears when | | ---------------- | --------------------------------------------------------------- | | Sort arrow | the column has a sortField | | Multi-sort badge | sort mode isn't "single" and more than one column is sorted | | Filter funnel | the column's filter options resolve (see datagrid-search) | | Pager | there's a page before or after this one, or pageSizes is set | | Selection column | a selection is passed |

So a grid whose columns carry none of that renders as a plain table. Grid-wide off switches exist for the blunt cases: sortable={false}, filterable={false}, pager={false}.

Must be rendered inside a FluentProvider.

Props beyond the search

<FluentDataGrid
  search={search}
  size="medium" // "medium" | "small" | "extra-small"
  rowKey={(r) => r.id}
  selection={selection}
  noData="Nothing here"
  pager // false to suppress, or a node to replace
  pageSizes={[10, 25, 50]}
  renderFilterPopup={(props) => <MyBody {...props} />}
  renderFilterControl={(column, search) => <MyInlineFilter />}
/>

DataGridClasses props (className, cellClass, …) pass through and win over the Fluent defaults.

Composing it yourself

When you need to own the <DataGrid> call — extra header rows, a custom body wrapper — use the hook and spread its parts:

const fluent = useFluentDataGrid(search, { size, selection, rowKey });

<DataGrid
  {...fluent.gridProps}
  columns={fluent.columns} // selection column already prepended
  bodyRows={search.data.rowProps.bodyRows}
  getBodyRow={search.data.rowProps.getBodyRow}
/>;

gridProps carries the classes, renderHeaderContent, the row wrapper and the per-cell RenderControl wrapper. fluent.parts exposes the part classes, for building custom cells that still look like Fluent's.

The pieces are also exported individually — useFluentDataGridStyles, fluentHeaderContent, fluentRowWrapper, fluentSelectionColumn, FluentFilterPopover, FilterOptionList, FluentPager — for grids that need to compose them differently.

Selection

Page-scoped: the header checkbox reflects and acts on the rows currently rendered, and never disturbs a selection made on another page.

const selectedIds = useControl<string[]>([]);
const selection = makeGridSelection({
  selected: selectedIds,
  rows: data.rows, // the current page
  getId: (r) => r.id,
});

arraySelection is the same thing over plain state. Neither is a hook, despite taking a control — they read .value when called, so call them during render.

Cross-page "select all N matching" is deliberately not supported: it needs the filtered total, the live search, and a way to fetch every matching id, at which point selection stops being a renderer concern.

Custom filter popups

Three levels, most specific first:

  1. getColumnFilter(column).render — replaces one column's popup body, keeping the standard funnel and the loading/error/empty shell.
  2. renderFilterPopup on the grid — the same, as the default for every column.
  3. renderFilterControl on the grid — replaces the funnel and popup, for a column that wants an inline control instead.

The body receives FilterPopupProps, whose whole contract is a Control<string[] | undefined> for that column. FilterOptionList is exported so a custom body can keep the standard checkbox list and just add a header.

The body is a separate component mounted only while the popover is open, which is what makes an async option source lazy.

Styling

useFluentDataGridStyles returns the classes; the grid contributes no CSS of its own beyond them. Stable class names are on fluentDataGridClassNames, for CSS overrides and for tests that measure rendered cells.

Sizes match Fluent's: body rows are 44/34/32px of content, the header row is 32px at every size, and extra-small drops the row divider and uses 12px text.

Two structural differences from Fluent worth knowing:

  • No row box. The row wrapper is display: contents, so cells stay direct grid items and keep their explicit placement. Hover and selection are painted on the cells rather than the row — :hover still resolves up the ancestor chain.
  • Column widths. Fluent's resizable column sizing reserves a constant ~48px of the container and gives the slack to the last column, which a CSS grid template won't reproduce. Set the widths you want explicitly rather than letting either side distribute slack.

Pager

Fluent v9 ships no pagination component, so FluentPager is built from its primitives rather than matching a reference. It renders prev/next over offset/length with an optional page-size selector, and hides itself when everything fits on one page — unless pageSizes is set, since that selector is the only way back from a size that fits every row. Then it stays, with prev/next disabled.

It copes with a source that doesn't count: without a total it shows 1-10 instead of 1-10 of 42, and enables Next while the page comes back full. See pageInfo in datagrid-search for why counting is optional.