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

@multiplatform.one/frappe-ui

v6.7.0

Published

Pre-wired Tamagui components for Frappe doctypes with live data

Downloads

62

Readme

@multiplatform.one/frappe-ui

Pre-wired Tamagui components for Frappe doctypes with live data: tables, forms, kanban, calendar, dashboards, and the doctype field registry.

Install

pnpm add @multiplatform.one/frappe-ui

What it owns

  • FrappeProvider — Frappe connection context (baseURL, auth, socket port)
  • View wrappersFrappeTable, FrappeForm, FrappeList, FrappeListView, FrappeKanban, FrappeCalendar, FrappeGantt, FrappeReportBuilder, FrappeDashboard (+ FrappeKPICard, FrappeChartCard), FormSidebar
  • Document collaborationFrappeAssignment (assign-to over live ToDo CRUD), FrappeLikedBy (_liked_by heart + avatar cluster), FrappeAttachments, FrappeTags, FrappeTimeline (live File / Tag Link / Comment + Version feeds)
  • Live aggregatesuseFrappeAggregate (server-side count/sum/avg/min/max + groupBy, refetched on realtime events)
  • Field registry — maps Frappe fieldtypes to form field components (registerField, getFieldComponent, initDefaultRegistry, fieldtypeToTableField, childFieldsToColumns)

What it must not do

  • Never re-implement table or forms primitives — it adapts them. Dependency direction: frappe-uitable / forms / components / theme; those packages never depend back on frappe-ui or frappe.
  • Adapters forward the shared field contract (disabled / readOnly / error / required / skeleton / compact) — they do not translate it.

Usage

import { FrappeProvider, FrappeTable } from "@multiplatform.one/frappe-ui";

interface ToDo {
  name: string;
  doctype: string;
  description: string;
  status: "Open" | "Closed";
}

export function ToDoTable() {
  return (
    <FrappeProvider baseURL="https://mysite.frappe.cloud">
      <FrappeTable<ToDo>
        doctype="ToDo"
        columns={[
          { accessorKey: "description", header: "Description" },
          { accessorKey: "status", header: "Status" },
        ]}
        fields={["name", "description", "status"]}
      />
    </FrappeProvider>
  );
}

FrappeTable accepts the rest of the DataTable props from @multiplatform.one/table (filtering, sorting, pagination, serverSide, …).

Infinite scroll (server-side)

With serverSide and paginationMode="infinite", FrappeTable supplies the DataTable page loader automatically: each page maps through the same Frappe query params as the classic fetch path (limit_start, limit_page_length, order_by, operator-aware filters, global search), the total count is fetched once per query state (on page 0), and the table pre-loads page N+1 while page N is on screen. A consumer-provided loadPage prop overrides the built-in loader.

<FrappeTable<ToDo> doctype="ToDo" serverSide paginationMode="infinite" ... />

Link field (backend search)

The Link field (foreign-key picker) is a combobox whose typing searches the backend — the same server search stock Frappe's link control uses (frappe.desk.search.search_link): relevance ranking, the doctype's link get_query conditions, searchfield semantics, and permissions are all enforced server-side.

  • Opening fetches the first page with an empty query (stock focus behavior); keystrokes are debounced (~300ms effective) and every query renders the server's result set — never client-side filtering of a preloaded list. No requests fire while the dropdown is closed or the field is locked.
  • pageLength (default 20) sets results per page; filters apply server-side on top of the doctype's link query; searchFields maps to searchfield; referenceDoctype feeds the server's link-query context.
  • A saved value's display title resolves on mount via frappe.desk.search.get_link_title (session-cached, falls back to the raw name), so an existing FK never renders as a bare id while titles are enabled.
  • Search failures render inside the dropdown ("You don't have permission to search {doctype}" for 401/403, "Search failed" otherwise).
  • In table cells the registry mounts the same combobox chromeless; the editor opens as a popover and picking a value commits the row (single-value picker).
  • Canonical change prop: onChange(name). allowCreate + onCreateNew offer a create affordance when the typed text matches nothing.

The underlying data hooks (useLinkSearch, useLinkTitle) live in @multiplatform.one/frappe; the combobox primitives (async onSearch, valueLabel, searchError) live in @multiplatform.one/forms.

Live aggregates

Dashboard numbers are computed server-side (frappe.client.get_count, or get_list with an aggregate expression + group_by over the whole filtered doctype) and refetched when the doctype's realtime room reports a change — never counted from the synced row window, which is bounded to a page. useFrappeAggregate coalesces event bursts through a debounce window (default 300ms, debounceMs) and reports honest states: isLoading (first load), isStale (refetch in flight), error + isPermissionError (403), and value === undefined for empty aggregates. Widgets watching the same doctype share one socket room.

import { FrappeKPICard, FrappeChartCard } from "@multiplatform.one/frappe-ui";

// Inside a <FrappeProvider> (or pass baseURL/auth props directly)
<FrappeKPICard
  config={{ id: "open-todos", title: "Open ToDos", value: 0 }} // value overridden live
  dataSource={{ doctype: "ToDo", aggregate: "count", filters: { status: "Open" } }}
/>
<FrappeChartCard
  config={{ id: "by-status", title: "ToDos by status", type: "bar" }}
  dataSource={{ doctype: "ToDo", groupBy: "status" }}
/>

Without a dataSource, both cards are static passthroughs of the catalog KPICard / ChartCard. FrappeDashboard and FrappeReportBuilder stream rows live through the sync client and show a subtle stale affordance while a post-event refresh is in flight.

Related packages

  • @multiplatform.one/frappe — the live-sync client this package fetches through
  • @multiplatform.one/table — the framework-agnostic DataTable being wrapped
  • @multiplatform.one/forms — the field components the registry maps onto

Styling

Structural styling flows through the knobs system in @multiplatform.one/theme (see agent-os/standards/frontend/knobs-system.md in the multiplatform.one repo).

License

Apache-2.0