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

@devoy/pharmnex-ui

v0.4.7

Published

Shared Pharmnex React UI components.

Downloads

150

Readme

@devoy/pharmnex-ui

Shared React UI components for Pharmnex apps.

Version 0.4.7 includes the controlled AdvanceFilter, shared core UI primitives, reusable data/list page helpers, form components, configurable modal positioning, and appear/disappear animation presets.

Install Locally

From an app beside this package:

{
  "dependencies": {
    "@devoy/pharmnex-ui": "file:../pharmnex-ui"
  }
}

Then run install in the app.

For Create React App consumers, add this to the app's .npmrc before installing the local package:

install-links=true

This makes npm install the local package as a copied package instead of a symlink, which prevents duplicate React instances during local development.

Install From Registry

Install the public package from npm:

npm install @devoy/pharmnex-ui

Then import the components and package stylesheet:

import { Button, FormInput, TableToolbar } from '@devoy/pharmnex-ui';
import '@devoy/pharmnex-ui/styles.css';

Publish

The package is configured for public scoped distribution:

npm login
npm publish --access public

Before publishing, prepublishOnly runs clean, typecheck, and build.

Publishing requires Devoy organization access and npm 2FA approval.

Usage

import { useState } from 'react';
import { AdvanceFilter, Button, FormInput, InputSelect, Modal, StatusBadge, TableToolbar } from '@devoy/pharmnex-ui';
import '@devoy/pharmnex-ui/styles.css';

const filterOptions = [
  { key: 'status', label: 'Status' },
  { key: 'customer_name', label: 'Customer Name', paramKey: 'customer_id' },
];

const valueOptions = {
  status: [
    { key: 'active', label: 'Active', value: 1 },
    { key: 'inactive', label: 'Inactive', value: 0 },
  ],
  customer_name: [
    { id: 'cust_102', name: 'Care Plus Pharmacy' },
  ],
};

function CustomerListFilters() {
  const [open, setOpen] = useState(false);
  const [filters, setFilters] = useState([]);

  return (
    <AdvanceFilter
      open={open}
      title="Advanced Filters"
      description="Select fields and values to narrow the list."
      options={filterOptions}
      filters={filters}
      onChange={setFilters}
      onLoadOptions={(key) => valueOptions[key] || []}
      valueMapper={{
        status: (value) => Number(value),
      }}
      onApply={(params) => {
        console.log(params);
        setOpen(false);
      }}
      onReset={() => {
        console.log('reset filters');
      }}
      onClose={() => setOpen(false)}
    />
  );
}

Component API

Button

Reusable action button with primary, secondary, and ghost variants. Supports loading, disabled, fullWidth, and optional icon.

<Button variant="primary" loading={isSaving}>
  Save
</Button>

Modal

Portaled dialog shell with shared header, body, footer, Escape close, and backdrop close behavior.

<Modal
  appearAnimation="scale"
  disappearAnimation="fade"
  animationDuration={240}
  open={open}
  title="Confirm action"
  description="Review details before continuing."
  onClose={() => setOpen(false)}
  footer={<Button variant="primary">Confirm</Button>}
>
  Modal content
</Modal>

| Prop | Type | Default | Description | | --- | --- | --- | --- | | open / isOpen | boolean | - | Controls modal visibility. | | alignment | 'top' \| 'center' \| 'bottom' | 'center' | Sets the modal's vertical position. | | topPadding | number or CSS length | 24 | Overrides top viewport spacing. Numbers use pixels. | | bottomPadding | number or CSS length | 24 | Overrides bottom viewport spacing. Numbers use pixels. | | appearAnimation | Animation preset | 'none' | Animation used when opening. | | disappearAnimation | Animation preset | 'none' | Animation used when closing. | | animationDuration | number | 200 | Shared animation duration in milliseconds. | | closeOnBackdrop | boolean | true | Allows backdrop clicks to close the modal. | | closeOnEscape | boolean | true | Allows Escape to close the modal. |

Animation presets are none, fade, scale, slide-up, and slide-down. Keep the component mounted and change open; conditionally removing the component prevents its disappear animation from completing.

// Correct: the modal remains mounted while open changes.
<Modal open={open} disappearAnimation="fade" onClose={() => setOpen(false)} />

InputSelect

Searchable select input that accepts both UI-friendly and API-friendly option shapes. Menus are portaled to document.body, so dropdowns can overflow modal boundaries.

<InputSelect
  label="Customer"
  options={[{ id: 'cust_102', name: 'Care Plus Pharmacy' }]}
  value={customerId}
  onSelect={(option) => setCustomerId(option.value)}
  loading={isLoadingCustomers}
/>

StatusBadge

Small status label with automatic variants for common values such as Paid, Pending, Void, and Inactive.

<StatusBadge status="Paid" />
<StatusBadge variant="danger">Overdue</StatusBadge>

EmptyState

Reusable no-data block with optional action.

<EmptyState
  title="No invoices found"
  description="Try changing filters or date range."
  actionLabel="Reset View"
  onAction={handleReset}
/>

AdvanceFilter

| Prop | Type | Description | | --- | --- | --- | | open / isOpen | boolean | Controls modal visibility. | | alignment | 'top' \| 'center' \| 'bottom' | Sets the modal's vertical position. Defaults to center. | | topPadding | number or CSS length | Overrides the backdrop's top padding. Numbers are treated as pixels. | | bottomPadding | number or CSS length | Overrides the backdrop's bottom padding. Numbers are treated as pixels. | | appearAnimation | 'none' \| 'fade' \| 'scale' \| 'slide-up' \| 'slide-down' | Animation used when the modal opens. Defaults to none. | | disappearAnimation | 'none' \| 'fade' \| 'scale' \| 'slide-up' \| 'slide-down' | Animation used when the modal closes. Defaults to none. | | animationDuration | number | Animation duration in milliseconds. Defaults to 200. | | options | Array | Available filter definitions. | | filters | Array | Selected filter rows. | | onChange | function | Receives the next selected filters array. | | onLoadOptions | function | Called when a selected filter value input receives focus. Can return value options. | | onApply | function | Receives normalized params and selected filters. | | onReset | function | Called after filters are cleared. | | onClose | function | Called when the modal is dismissed. | | valueMapper | function or object | Optional mapper for API param values. Can be one global mapper or a map by filter key. |

For example, place the advanced filter near the top with a custom offset:

<AdvanceFilter
  alignment="top"
  appearAnimation="slide-down"
  disappearAnimation="fade"
  animationDuration={240}
  topPadding={72}
  open={filtersOpen}
  {...filterProps}
/>

For disappear animations, keep AdvanceFilter mounted and control it with open or isOpen. Avoid {open && <AdvanceFilter />}, because React would remove the component before the package can play its exit transition.

Use paramKey or apiKey on a filter option when the UI field key differs from the API request key. For example, a filter can display as customer_name but submit customer_id.

Filter Shape

{
  key: 'status',
  label: 'Status',
  paramKey: 'status',
  value: '',
  disabled: false,
  error: '',
  options: [
    { key: 'active', label: 'Active', value: 1 }
  ]
}

Value options may use either UI-friendly or API-friendly shapes:

{ key: 'active', label: 'Active', value: 1 }
{ id: 'cust_102', name: 'Care Plus Pharmacy' }

For { id, name }, the component displays name and applies id.

Data/List Helpers

TableToolbar

List-page header wrapper. It supports the current portal prop names used by TableHeaderComponent: bulkAction, buttons, closeButton, dropdownButton, advanceFilterButton, createButton, and moreButton.

<TableToolbar
  bulkAction={bulkAction}
  buttons={[{ id: 1, button: 'Delete', handler: handleDelete }]}
  closeButton={{ showCloseButton: true, handler: clearSelection }}
  dropdownButton={<Button>All Customers</Button>}
  advanceFilterButton={<Button variant="primary">Advanced Filters</Button>}
  createButton={{ buttonName: 'Create Customer', handler: handleCreate }}
/>

SearchInput

Controlled search field with optional debounce and clear action.

<SearchInput value={search} onChange={setSearch} debounceMs={250} />

FilterPills

Applied-filter row. Supports current selectedInputs style where each filter has key, label, value, and data options.

<FilterPills
  filters={selectedInputs}
  onFocusFilter={(key) => loadFilterOptions(key)}
  onChangeFilter={(key, value) => updateFilterValue(key, value)}
  onRemoveFilter={(key) => removeFilter(key)}
  onClearAll={clearAdvanceFilters}
/>

Pagination

Pagination compatible with the current portal props: currentPage, totalPages, onChangePage, pageSize, and setParam. Future consumers can use onPageChange and onPageSizeChange aliases.

<Pagination
  currentPage={page}
  totalPages={totalPages}
  onChangePage={setPage}
  pageSize={pageSize}
  setParam={setPageSize}
/>

BulkActionBar

Standalone selected-row action bar. TableToolbar uses it automatically when bulkAction.length > 0.

TableActions

Small row-action wrapper that stops event propagation before running action handlers, useful inside clickable table rows.

Form Components

Form fields follow the portal's current value-first pattern: onChange(nextValue, event).

FormInput

Base text input with shared label, required mark, helper text, error text, right icon/text actions, and optional value transforms.

<FormInput
  label="Customer Name"
  name="customer_name"
  value={customerName}
  onChange={setCustomerName}
  error={errors.customer_name}
  required
/>

Textarea

Multiline field with the same validation and helper-text pattern.

<Textarea label="Notes" value={notes} onChange={setNotes} rows={4} />

DateInput

Date field wrapper over FormInput; supports min, max, and the same value-first callback.

<DateInput label="Due Date" value={dueDate} onChange={setDueDate} />

PasswordInput

Password field with built-in show/hide text action.

<PasswordInput label="Password" value={password} onChange={setPassword} />

FileUpload

Controlled file picker. It returns a single File by default, or an array of files when multiple is true.

<FileUpload
  label="Upload Document"
  accept=".pdf,.png,.jpg,.jpeg"
  value={documentFile}
  onChange={setDocumentFile}
/>

ValidationMessage

Shared validation/helper text pattern.

<ValidationMessage tone="error">{errors.email}</ValidationMessage>

0.4.0 Features

  • FormInput, Textarea, DateInput, PasswordInput, FileUpload, and ValidationMessage exported as form helpers.
  • Fields use value-first callbacks to match existing portal forms.
  • Shared helper/error text styling replaces per-field inline validation markup.
  • Specialized wrappers cover common portal field cases without bringing Redux preferences into the package.

0.3.0 Features

  • Pagination, TableToolbar, BulkActionBar, SearchInput, FilterPills, and TableActions exported as data/list helpers.
  • Helpers accept current portal prop names to allow page-by-page migration.
  • FilterPills can render selected filter selects from { key, label, value, data } rows.
  • Pagination supports existing setParam page-size callback and keyboard ArrowLeft/ArrowRight navigation.

0.2.0 Features

  • Button, Modal, InputSelect, StatusBadge, and EmptyState exported as shared primitives.
  • AdvanceFilter now composes the shared primitives internally.
  • Dropdown menus portal to document.body so they can overlap modal boundaries.
  • Input select accepts { key, label, value }, { key, value }, and { id, name } option shapes.

0.1.x Features

  • Keyboard navigation inside dropdowns with arrow keys, Enter, and Escape.
  • Loading state while async filter value options are fetched.
  • Disabled filter rows and disabled dropdown options.
  • External per-filter error messages.
  • Per-filter value mapping through filter.valueMapper or a valueMapper object prop.

Build

npm install
npm run build