@devoy/pharmnex-ui
v0.4.7
Published
Shared Pharmnex React UI components.
Downloads
150
Maintainers
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=trueThis 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-uiThen 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 publicBefore 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, andValidationMessageexported 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, andTableActionsexported as data/list helpers.- Helpers accept current portal prop names to allow page-by-page migration.
FilterPillscan render selected filter selects from{ key, label, value, data }rows.Paginationsupports existingsetParampage-size callback and keyboard ArrowLeft/ArrowRight navigation.
0.2.0 Features
Button,Modal,InputSelect,StatusBadge, andEmptyStateexported as shared primitives.AdvanceFilternow composes the shared primitives internally.- Dropdown menus portal to
document.bodyso 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.valueMapperor avalueMapperobject prop.
Build
npm install
npm run build