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

@ranjeetk25/schema-grid-ui-mantine

v0.4.1

Published

Mantine v8 UI for Schema Grid: cell editors, filter and column builders, import wizard and export dialog.

Readme

@ranjeetk25/schema-grid-ui-mantine

Mantine v8 UI for Schema Grid: cell editors and renderers, a filter builder, a column builder, saved-view and group-by controls, a conflict prompt, an import wizard, an export dialog, a paste-report toast and a theme bridge.

Peers: react 18, @mantine/core|hooks|dates ^8, dayjs, zod ^3.25 || ^4. Optional peer: @mantine/notifications ^8.

One-component page

<SchemaGridWorkbench> is a whole admin grid page: header, saved views, filter + chips, group, search, undo/redo, import, export, "Add column" (side panel with a ghost-column preview), the conflict prompt, polling and a quiet status bar. Features follow the data source's capabilities (groupBy:false hides Group, search:false hides search, changeFeed:false stops polling, "updates-only" polls without removing rows, write.cells:false makes the grid read-only with a banner, export.maxRows caps the CSV export); the toolbar waits for them to load. features can only switch things off.

import { createGridClient } from "@ranjeetk25/schema-grid-ag-grid";
import { SchemaGridWorkbench } from "@ranjeetk25/schema-grid-ui-mantine";

// Module scope: the workbench compares the client by reference.
const leads = createGridClient({ baseUrl: "/api/grid", gridId: "leads" });

export function LeadsPage({ user }: { user: PermissionUser }) {
  return (
    <div style={{ height: "100dvh" }}>
      <SchemaGridWorkbench client={leads} user={user} title="Leads" subtitle="All applicants" />
    </div>
  );
}

Without a server client, pass dataSource + schema (+ onSchemaChange to persist column edits). Other props:

| Prop | | |---|---| | resolver, registry, uiRegistry, mode | defaults: role resolver, default registries, "server" with client else "client" | | views + onViewsChange / viewStore | controlled views, or a WorkbenchViewStore (load(gridId)/save(gridId, views)); default createLocalStorageViewStore() keyed by grid id | | features | Partial<{ filter, group, search, views, export, import, addColumn, undo, polling, paint, colorRules }> — false turns one off | | toolbarStart, toolbarEnd, statusBar | a node or (ctx) => node; ctx has handle, schema, features, capabilities (raw, once loaded), effectiveCapabilities (core mergeCapabilities, null until loaded), openImport, openExport, openAddColumn, refetch | | emptyState | shown over the grid when there are no rows | | onError(error) | every { kind, op, message, error } also shown as a banner: permission-denied, network (Retry), capability-denied, schema-changed (Reload), export failures (op: "export", banner with Retry) | | events | host grid events merged with the workbench's own (see below) | | exportFileName | a string, or (ctx) => string with { gridId, schema, view, format, date }; default ${gridId}-${view ?? "all"}-${YYYY-MM-DD}.csv, slugified | | pollIntervalMs, height ("fill" default: give the parent a height), pageSize, roles, gridProps | | | userDirectory | v0.4 { search(query), resolve(ids) } → ActorRef[]: adds People pickers (per-person RoleRule.users) to "Who can access" and option "Who can set". Absent: no People pickers; stored users are kept |

Host events

events?: Partial<SchemaGridEvents> (also gridProps.events; a handler on events wins per name). beforeCellsChange chains host → workbench: the host may veto with false, or return a transformed / reduced batch — meta on the batch or on individual changes travels to the data source untouched. Every other event (onCellsChange, onConflict, onRowsCreate/Delete, onColumn*, onOptionCreate, onViewChange, onRemoteChanges, onSchemaChanged) fans out to the host first, then the workbench; a throwing host handler is reported through onError and never breaks the built-in handling (the conflict prompt still opens after your onConflict).

Columns picker

The toolbar "Columns" button lists every column the user may read (columns hidden by permission are never listed; ColumnDef.hidden ones start unchecked): search, a checkbox per column, move up / down, "Show all" / "Hide all", and a badge with the hidden count. Changes go through the grid's column state, so they live in the current view (marked unsaved), persist with "Save changes" / "Save as new view" and are undone by switching views.

Who may change the schema

With a client, "Add column", the header's "Edit column… / Insert…" and the trailing "+" appear only when the server's capabilities report schema.write: true (createGridRegistry answers it from permission(ctx, "updateSchema") and the presence of a schema store). In direct (dataSource + schema) mode the host owns the schema, so they stay on; features.addColumn: false still turns them off.

Restricted options

A select / multiSelect / creatableSelect option may carry settableBy: { roles: [...] } (core Option.settableBy). The pickers offer only the options the signed-in user can set; a value the row already holds stays visible and renders normally, but is locked in the picker with an "Only Admin can set this" tooltip. Servers and the in-memory source reject the rest ("Option “Verified” can only be set by Admin"), which the grid shows as a cell error. The column panel's Options editor has a per-option "Who can set" pill (Everyone | Only roles…) mirroring the column's access section.

Capability-gated field types (v0.4.1)

A link column needs a source that can lookup records, a user column one that serves options (core fieldTypeAvailability). The column builder (ColumnPanel, ColumnBuilderModal, ColumnForm, TypeStep) takes capabilities (the workbench passes effectiveCapabilities) and hides the types it can't back; an existing column keeps its type, shown with the reason. Editors get capabilities too (UiEditorProps.capabilities, from the grid context) and gate on them instead of on method presence: the link picker shows "Linking isn't set up for this grid" and the user picker "People search isn't set up for this grid" instead of calling; without options a creatable select shows "Creating options isn't set up for this grid" instead of its "Create" entry, and dynamic selects and the set filter keep their static options.

Per-cell refusals from the server (e.g. "Only specific people can edit this column") show verbatim in the save banner and on the cell.

Saved vs. not saved

The status bar's "N saved" counts applied cells. Changes a data source (or a beforeCellsChange hook that dropped them) declines quietly come back as ChangeResult.rejected: the cell reverts with no error state, nothing is announced assertively, and the bar reads "N changes not saved" (paste summaries add "R not saved").

Cell colors (v0.4)

Excel-style colors, all derived from the grid's handle and the source's capabilities.cellColors ({ read, write, filter }):

  • Paint: the toolbar's "Cell color" popover (palette + "No color") paints the range selection, else the focused cell (handle.setCellColor). It shows when the source reads and writes colors (features.paint); the swatches are disabled with a hint until a selected cell is paintable (handle.canPaint(), re-checked on selection / focus changes). Each paint is one undo step. When cells were skipped (no edit access) or rejected, a toast says so (notifyCellColorReport, same optional peer as the paste toast); gridProps.onCellColorReport still receives every report. There is no cell context menu, so painting lives in the toolbar only.
  • Color rules: the "Color rules" button (a count badge when the view has rules) opens a dialog, loaded on first open, listing the view's rules. Each has a palette swatch, the target (whole row or a multi-select of readable columns), a condition built with the regular FilterBuilder (no color operators: a rule can't test colors), an Enabled switch, move up / down and delete. Save validates with core's validateColorRules (problems show under their rule) and calls handle.setColorRules, which flows to the view like filter / sort (the view shows unsaved changes until saved). Rules are evaluated client-side, so the button shows for any source once capabilities load (features.colorRules), and hides when the user can read no column. v0.4.1: a condition may test filterable: false columns (the dialog's builder runs with allowUnfilterable); such a rule shows a subtle "Can't be used to filter by color" note (hover: which column). Pass capabilities to a standalone ColorRulesDialog so columns outside the source's filter scope count too.
  • Filter by color: with cellColors.filter, the filter builder offers "color is" (a swatch multi-select) and "has no color" on every readable column, filterable: false ones included (color operators only there); pass capabilities to a standalone FilterBuilder / FilterButton to get them. The header menu gets a "Filter by color" submenu (palette, "No color", "Clear color filter") that sets one top-level color condition for the column (setColumnColorFilter). Outside the workbench, wrap the grid in <CellColorFilterProvider value={{ filterByColor, activeColors }}> to get the submenu; without a provider it is hidden. Chips read "Status color is Red, Blue".
  • Blocked by a rule (v0.4.1): a server can't filter a column by color when an enabled rule that can color it (a cells rule targeting it, or any row rule) tests a column it can't filter on. For such a column the filter builder drops "color is" / "has no color" (the operator pill's tooltip says why) and the header menu shows "Filter by color" disabled with the reason (Can't filter by color: a color rule on it uses "Verdict", which can't be filtered on the server). The workbench wires it; standalone, pass colorRules to FilterBuilder / FilterButton and blockedReason in the CellColorFilterProvider value (ag-grid's colorFilterBlockedReason).
  • Palette / dark mode: CELL_COLOR_PALETTE (label, swatch, fill per CellColor) and <CellColorSwatch color> use ag-grid's CELL_COLOR_TOKENS; mantineGridCssVariablesResolver sets the --sg-color-<name> variables per colour scheme (cellColorCssVariables("light" | "dark")), so painted cells, rule colors and swatches follow Mantine's dark mode.

Lazy chunks

The import wizard, the export dialog, the column panel and the color rules dialog load on first open, and @ranjeetk25/schema-grid-io (exceljs, papaparse) is imported only inside an export / import run, so none of them sit in the page chunk.

Entry points

| Import | Contents | |---|---| | @ranjeetk25/schema-grid-ui-mantine | everything below, plus ViewSwitcher, GroupByBar, ConflictPopover, RemoteChangedBadge, useGridThemeFromMantine, notifyClipboardReport, and v0.4 CellColorButton, CellColorPicker, ColorSwatchMultiSelect, ColorRulesDialog, CellColorFilterProvider, setColumnColorFilter, CELL_COLOR_PALETTE, CellColorSwatch, notifyCellColorReport | | …/editors | editors, renderers, createMantineUiRegistry() | | …/filter-builder | FilterBuilder, FilterChips, FilterButton, draft model | | …/column-builder | ColumnBuilderModal, ZodForm, FormulaEditor, column draft model | | …/import-export | ImportWizard, ExportDialog |

import { createMantineUiRegistry } from "@ranjeetk25/schema-grid-ui-mantine/editors";

// The real @ranjeetk25/schema-grid-ag-grid registry: createDefaultUiRegistry().extend(...)
const uiRegistry = createMantineUiRegistry({
  widgets: { select: { renderer: MyBadge } }, // ui-mantine widget props, adapted for you
  overrides: { url: { editorPopup: false } }, // raw AG Grid UiFieldType partials
});

Widgets are written against small grid-agnostic props (UiEditorProps, UiRendererProps) and adapted to AG Grid (toGridRenderer, toInlineGridEditor, toPopupGridEditor via ag-grid's createPopupEditor). The same widgets power the column builder's default-value field, its preview and the filter builder's value inputs (resolveEditorComponent, resolveRendererWidget, filterInputFor).

Column filters are ag-grid's defaults (ConditionFilter, whose model is a core FilterCondition); ui-mantine only overrides renderers and editors.

Conflicts: const prompt = useMantineConflictPrompt(), pass events={{ onConflict: prompt.onConflict }} to the grid, and render <ConflictPopover conflict={prompt.conflict} opened={prompt.opened} onResolve={prompt.resolve} onClose={prompt.dismiss} …> when prompt.conflict is set.

Hidden columns (access "hidden", or missing from the access map) never appear in any picker. Formula columns are read-only: no editor, never an import target.

Why popup editors skip the portal

Mantine renders dropdowns into document.body by default. Inside AG Grid that breaks editing: a click on a portaled option lands outside the grid, AG Grid treats it as focus leaving the cell (stopEditingWhenCellsLoseFocus plus its outside-click detection), stops the edit and unmounts the editor before the option's click registers. The value is lost or the wrong one is committed.

So every dropdown in this package renders inline (withinPortal: false on Combobox/Popover/Menu/Tooltip, comboboxProps={{ withinPortal: false }} on Select/MultiSelect/TagsInput, popoverProps on the date pickers, including timePickerProps.popoverProps on DateTimePicker). Editors with a dropdown, picker or multi-line body are wrapped with createPopupEditor, which tells AG Grid to render them in its popup layer (cellEditorPopup: true), so the inline dropdown is not clipped by the cell's overflow: hidden.

The same rule keeps FilterButton's popover open while you pick options in the nested selects.

Popup set: longText, date, datetime, select, multiSelect, creatableSelect, user, link.

Notifications (optional)

notifyClipboardReport(report) lazily loads @mantine/notifications (a literal import(), so bundlers resolve it; pass { loader } to supply the module yourself) and shows a toast such as "Pasted 40 cells, 3 skipped (2 invalid, 1 read-only)". Mount <Notifications /> from @mantine/notifications in your app for it to appear. When the package is not installed it resolves "unavailable" and never throws. notifyCellColorReport(report) (v0.4) does the same for a paint that skipped or lost cells ("Colored 3 cells red, 1 skipped (read-only)") and resolves "nothing" without a toast when every cell was painted.

Upstream contracts

@ranjeetk25/schema-grid-core, -ag-grid and -io are consumed only through src/internal/{core,grid,io}-contracts.ts. All three are real re-exports plus a few local helpers for gaps (and, for ag-grid, the widget adapters).