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

@openreachtech/crm-kit-frontend

v1.1.0

Published

Frontend kit for building CRM applications.

Readme

@openreachtech/crm-kit-frontend

Frontend kit for building CRM applications on Nuxt 3.

Concept

The kit ships the parts a CRM screen is made of: a list view over a records table, a detail view over one record, and the modules beside them for comments, memos, notifications, global search, email and file preview. Each part draws through furo-vue and reads its data through an adapter the consuming app writes.

Nothing in the kit knows your backend. A consuming project customizes it three ways and never forks it:

  1. An adapter wraps your data source behind one contract and returns the uniform { isSuccessful, payload, error } result the kit reads.
  2. A registry decides every multi-way choice: how a column renders, which editor a field uses, what a row action does. You pass strategies and the kit merges them over its own defaults, last one wins.
  3. The theme retunes CSS custom properties in your own cascade layer. No component edits, no specificity fights.

Installation

Requires Node.js 20.x (the version the CI builds against).

npm install @openreachtech/crm-kit-frontend

The package publishes to public npmjs.com and needs no registry setup. If your project already points the whole @openreachtech scope at GitHub Packages, the install fails on @openreachtech/furo-vue, which publishes to npmjs.com. npm offers no per-package registry override, so point the scope at npmjs.com:

@openreachtech:registry=https://registry.npmjs.org

nuxt and @nuxt/icon are peer dependencies your app brings itself. The kit imports Icon from #components, a virtual module only Nuxt resolves, so the consuming app must be a Nuxt app.

It is an ES module ("type": "module"); import it with ESM import syntax.

Wiring

Three steps, and the package cannot perform any of them for you. Skip one and the kit fails in a way that looks like a bug in the kit.

1. Transpile the package

// nuxt.config.js
build: {
  transpile: [
    '@openreachtech/crm-kit-frontend',
  ],
},

Twenty-four files under lib/ import Icon from #components. Without transpilation the packaged .vue files do not resolve that specifier and the build fails.

2. Register the furo icon renderer

// plugins/001.furo-icons.js
import {
  defineNuxtPlugin,
} from 'nuxt/app'

import {
  Icon,
} from '#components'

import {
  furoIconRegistry,
} from '@openreachtech/furo-vue/icons'

export default defineNuxtPlugin(() => {
  furoIconRegistry.useIconRenderer({
    component: Icon,
  })
})

furo-vue's offline registry carries 49 icon names and none of the kit's. Skip this and icons render empty.

3. Load the five published stylesheets, in this order, ahead of your own

// nuxt.config.js
css: [
  '@openreachtech/crm-kit-frontend/lib/assets/css/0000.crm-kit-layers.css',

  '@openreachtech/furo-nuxt/lib/assets/css/0100.reset.css',
  '@openreachtech/furo-vue/lib/assets/css/furo.css',

  '@openreachtech/crm-kit-frontend/lib/assets/css/crm-kit-theme.css',
  '@openreachtech/crm-kit-frontend/lib/assets/css/crm-kit-components.css',

  '~/assets/css/your-app.css',
],

The order carries meaning. 0000.crm-kit-layers.css declares the layer order in one statement, and a layer's position is fixed where its name first appears, so loading anything ahead of it pins a layer in the wrong place and your own @layer app quietly stops winning. furo.css carries the design tokens every kit component reads. crm-kit-theme.css is required, not optional: it defines the semantic tokens furo-vue does not ship, and without it edited cells, completed path-group stages and mentions render unstyled.

docs/theming.md documents the sizing and motion tokens, the class contract and how to override a rule. It ships inside this package rather than as a GitHub link, because the repository is private and such a link would 404; ADR 0017 records that departure from the README convention.

Usage

Import from the package root:

import {
  ListView,
  ListViewKit,
  buildErrorResult,
  buildSuccessResult,
} from '@openreachtech/crm-kit-frontend'

Write an adapter over your data source:

export default class CustomerListViewAdapter {
  constructor ({
    graphqlClientHash,
  }) {
    this.graphqlClientHash = graphqlClientHash
  }

  static create ({
    graphqlClientHash,
  }) {
    return new this({
      graphqlClientHash,
    })
  }

  async fetchRecords (query) {
    const capsule = await this.graphqlClientHash
      .customers
      .invokeRequest({
        variables: {
          input: query,
        },
      })

    if (capsule.hasError()) {
      return buildErrorResult({
        code: capsule.getErrorCode(),
        message: capsule.getErrorMessage(),
      })
    }

    return buildSuccessResult({
      payload: {
        records: capsule.records,
        columns: capsule.columns,
        rowKey: 'id',
        pagination: {
          page: query.pagination.page,
          offset: query.pagination.offset,
          limit: query.pagination.limit,
          totalRecords: capsule.totalRecords,
        },
      },
    })
  }

  // updateRecordsBatch completes the required half. fetchColumns (a header for an
  // empty result set), fetchEditOptions, deleteRecordsBatch and bulkChangeRecords
  // (the selection bar's Delete and Bulk change) are optional.
}

Build the kit once, with your own strategies merged over the defaults:

const listViewKit = ListViewKit.create({
  entityKey: 'customer',
  listViewAdapter: CustomerListViewAdapter.create({
    graphqlClientHash,
  }),

  customColumnRenderers: [
    CurrencyColumnRenderer.create(),
  ],
})

Then bind its props hash onto the part:

<template>
  <ListView
    v-bind="context.listViewKit.listViewProps"
    @record:action="context.onRecordAction($event)"
    @selection:change="context.onSelectionChange($event)"
  />
</template>

API

index.js is the API. It re-exports every promised symbol, and the package ships dist-types/, so an editor resolves each one's full signature from the package itself. Read index.js for the list, grouped by area, and your editor for the signatures.

Three kinds of symbol live there:

  • Components and their contexts. ListView, DetailViewSections, CommentsPanel and the rest. Twenty-nine of the thirty-one take a single parcel prop whose type the root publishes as <Component>Parcel; ListView and SidebarMenu take named props instead. Every component returns { context } from setup(), so a template ref reaches its context.
  • Classes a consumer constructs. ListViewKit.create() and the four Default*Registrar classes seed the registries. StrategyRegistry.create() holds your strategies. ToastNotifier, KitMessageResolver and the DTOs (Comment, EmailDraft, MemoDraft, Notification) follow the same .create() factory shape.
  • Contracts a consumer implements, published as types only: the ten adapters, Notifier, MessageResolver, FetchFile and ResolvePreviewHref. Name them off the package root, never off a lib/ path.

Stable surface

Four things are stable at 1.0.0, and nothing else is.

Every symbol re-exported from the package root. index.js is the list. Deep lib/** subpaths keep resolving today and carry no promise, so a symbol absent from the root can move or disappear in a minor release. 1.x will narrow the package's exports map to the blessed paths, which are the root and the stylesheets under lib/assets/css/. Reach for a deep subpath knowing that.

Eight theme tokens. Seven the kit owns. Six of those it defines outright, because furo-vue ships nothing for them: --color-background-edited-cell, --color-text-edited, --color-path-group-completed, --color-path-group-completed-foreground, --color-background-mention and --color-text-mention. The seventh, --size-focus-ring, is the kit's own name for the width of every focus ring it draws, and it defaults to furo-vue's --size-focus-ring-spread, a token furo-vue declares and no furo-vue component reads. The eighth, --color-ring, is furo-vue's, and the kit promises it anyway because the focus ring is the one furo token a consuming app must be able to retune on its own. --color-primary, --color-primary-foreground, --color-destructive and --color-destructive-foreground are furo-vue's too and carry no promise here: the kit retunes them for its reference shell, and a consuming app is expected to retune them again. It darkens --color-destructive in light mode because furo-vue's value measures 3.67:1 there, below the 4.5:1 that error text needs. --color-destructive-surface is the kit's own name for the fill a danger button takes, held apart so that button can follow the design without dragging the error text with it.

The .unit-* class contract. A component's root element carries .unit-<component-name>, and a child class is addressable only through that root: write .unit-comment-entry > .body, never .body alone. The DOM shape between root and child is free to change.

Message keys. A key the kit resolves through your MessageResolver keeps its name and its parameters. EN_MESSAGES and JA_MESSAGES ship the kit's own packs; override an entry by resolving the key yourself.

Contribution

The repository is private, so homepage, bugs and repository in package.json resolve only for members of the openreachtech organization. Bug reports, feature requests and code contributions are welcome from members through GitHub Issues.

git clone https://github.com/openreachtech/crm-kit-frontend.git
cd crm-kit-frontend
npm install
npm run lint
npm test

License

This project is released under the Apache License 2.0.

For more details, please see in the LICENSE file.

Developer

Open Reach Tech Inc.

Copyright

© 2026 Open Reach Tech Inc.