@revenyu/ui

Shared Vue 3 components, app-shell, and Frappe API layer for Revenyu-suite apps (HR Recruiter, Payroll, CRM, ERP).
2,727
Install
npm install @revenyu/ui
Latest Version:0.15.1
Vue:3
License:Custom
Last Updated:Sep 13, 2026
Links: npm
Maintainer: mhmedrjb

@revenyu/ui

Shared Vue 3 components, app-shell chrome, and Frappe API layer extracted from hr_recruiter, for reuse across Revenyu-suite apps (HR Recruiter, Payroll, CRM, ERP — see the logos in AppSwitcher). Ships raw .vue/.ts source, same as frappe-ui: consumers compile it with their own Vite/tsc, there is no prebuilt dist.

This package assumes a Frappe backend. It is not a framework/backend-agnostic UI kit — api/* calls whitelisted Frappe methods, lib/doppio/controllers/call.js talks to /api/method/* with Frappe's CSRF header, and several components read/write Frappe doctypes.

Install

Published to the public npm registry as @revenyu/ui. The GitHub repo (PROJECXIO/revenyu-ui) is private, but the npm package is not — installing needs no GitHub access and no auth token:

yarn add @revenyu/ui
// package.json
"dependencies": {
  "@revenyu/ui": "^0.15.0"
}

No build step runs on install — your own Vite/tsc compiles the .vue/.ts source directly out of node_modules/@revenyu/ui/src/..., same as frappe-ui.

Peer dependencies you must already have: vue >=3.5, vue-router >=4.

Pin >=0.15.0 if you use LinkSelect. 0.14.0 was published from the commit before LinkSelect landed, so that one version's tarball has no such export and consuming builds fail with [MISSING_EXPORT] "LinkSelect" is not exported by ".../@revenyu/ui/src/index.ts".

Package entrypoints

Only these five subpaths are importable — the exports map deliberately exposes no others, so api/*, utils/*, and composables/useAnchoredPosition are internal to the library and not part of the public surface:

Import What it is
@revenyu/ui Every component, store, composable and type listed below
@revenyu/ui/style.css The design tokens every component reads via var(--x)
@revenyu/ui/rv-list.css The opt-in .rv-* list-page stylesheet
@revenyu/ui/tailwind Tailwind preset mapping the tokens to theme values
@revenyu/ui/tsconfig.base.json Shared strict TS compiler options

Setup

// main.ts
import '@revenyu/ui/style.css'
import { setCallRouter } from '@revenyu/ui'
import router from './router'

setCallRouter(router) // lets the API layer redirect to your login route on 401/403

Your router needs a route named Login (used by the 401/403 redirect check).

Mount these once at your app root — they are singletons driven by the exported stores, not per-instance props/emits components:

<SelectSheet />
<DateSheet />
<ConfirmSheet />
<Toasts />

Optional Tailwind preset (maps the CSS-variable design tokens to Tailwind theme values):

// tailwind.config.js
import revenyuPreset from '@revenyu/ui/tailwind'
export default { presets: [revenyuPreset] }

What's in here

Every name below is exported from the package root: import { Badge, SideNav, AiRewriteSheet } from '@revenyu/ui'.

Primitives — form inputs

Button, Checkbox, Combobox, CustomSelect, DatePickerField, FormControl, FormLabel, IconButton, Password, Radio, Slider, Switch, TextInput, Textarea, TimePicker.

Primitives — overlays/floating

BottomSheet, ConfirmSheet, ContextMenu, DateSheet, Dialog, Dropdown, FileUploader, HoverCard, Popover, SelectSheet, SettingsDialog, Tooltip.

Primitives — feedback/loading

Alert, ErrorMessage, LoadingIndicator, LoadingText, Progress, Skeleton, Spinner, Toasts.

Primitives — layout/navigation

ActionBar, Breadcrumb, BreadcrumbChip, Breadcrumbs, CrmBreadcrumb, Divider, EmptyState, ItemListRow, ListToolbar, ListFilterFields, ListView, PageHeader, RangeSwitcher, ScrollArea, Section, SettingsMenu, Stepper, TabButtons, Tabs, Tree.

ListToolbar + ListFilterFields + ListView + BulkActionBar together are the "Revenyu list page" pattern — search, a responsive filter grid, a sortable/selectable table, column editing, pagination and a bulk-action bar. They render against @revenyu/ui/rv-list.css.

Primitives — misc

Avatar, Badge, Duration, EChart, Icon, KeyboardShortcut, KeyboardShortcutsDialog, KpiCard, Rating, RevenyuLogo, RevenyuUiProvider.

App shell

AppSwitcher, BottomNav, MoreDrawer, Notifications, PageCrumbs, ProfileMenu, Shell, ShellDesktop, Sidebar, SideNav, Topbar, TopbarDesktop, GlobalSearch.

Feature widgets

ActivityFeed, BulkActionBar, DeleteAction, DocViewers, EmailComposer, FieldValueInput, InterviewQuickCreate, LinkSelect, OnboardingFieldCard, OnboardingLauncher, TableColumnsEditor, TemplateEditor.

LinkSelect is a Link field's picker: everything CustomSelect does, plus a create action in the dropdown header, offered only to users the server says may create that doctype. The record it creates is selected and kept alongside the options the parent passed, so the surrounding form needs to know nothing about a record being created mid-edit.

AI wizards

AiActionButton, AiDescriptionWizard, AiOnboardingFieldsWizard, AiQuestionsWizard, AiRewriteSheet, AiSettingsSheet.

AiEmailTemplateWizard exists in the source but is intentionally not exported — its template only renders a permanent "Coming Soon" placeholder. See CHANGELOG.md.

Easily-confused pairs

Three pairs of similarly-named components are easy to mix up — pick based on this:

  • Breadcrumb (fixed parent→current pair) vs Breadcrumbs (an arbitrary-length, prop-driven trail) vs CrmBreadcrumb (a segmented chip trail, each segment clickable).
  • RangeSwitcher (hardcoded 7d/30d/90d/ytd/all date-range options) vs TabButtons (a generic segmented control taking any options array).
  • PageCrumbs (breadcrumb trail + subtitle, driven by this app's own route names) vs PageHeader (a generic title + subtitle + actions-slot header, unrelated to routing).

Imperative UI helpers

The singleton sheets are driven by function calls rather than props, so any code — including non-component code — can open them:

import { openSelect, openDate, askConfirm, askConfirmWithReason, notify } from '@revenyu/ui'

openSelect('Status', ['Open', 'Closed'], current, v => (current = v))
openDate(dateStr, v => (dateStr = v), { min: '2026-01-01', quickActions: true })

const ok = await askConfirm({ title: 'Delete job?', message: 'This cannot be undone.' })
const reason = await askConfirmWithReason({ title: 'Reject applicant?' }) // null if cancelled

notify('Job deleted', 'ok')
Function Signature / notes
openSelect (label, options, value, onChange, opts?) — options accepts plain strings or { value, label }; opts takes { createLabel, onCreate } to offer the same create action CustomSelect has on desktop
openDate (value, onChange, opts?) — opts takes { min, quickActions, anchor }
askConfirm (ConfirmOpts) => Promise<boolean>
askConfirmWithReason (ConfirmOpts) => Promise<string | null> — resolves to the typed reason, or null when cancelled
closeAll Closes the select/date/confirm sheets at once
notify (text, kind?, ms?, title?) — kind is 'ok' | 'err' (default 'ok'), ms defaults to 2200 for 'ok' / 6000 for 'err', title is an optional bold heading (used by error toasts to name the failure kind)
toggleSidebar Flips ui.sidebarCollapsed, persists it to localStorage, and sets the --sidebar-w custom property (68px collapsed / 220px expanded)

ConfirmOpts is { title, message?, confirmLabel?, cancelLabel?, danger?, icon?, reasonRequired?, reasonPlaceholder? }.

Stores

Plain Vue reactive() objects — no Pinia, nothing to install or register.

  • ui — the shared chrome state: the select/date/confirm sheets plus the open/closed flags every shell component reads (notifs, appSwitcher, profileMenu, more, search, aiSettings, aiDescriptionWizard, aiRewrite, aiEmailWizard, aiQuestionsWizard, sidebarCollapsed).
  • toasts — { items }, rendered by <Toasts />; push to it with notify().
  • userStore — { profile, loading, loaded }, with loadUser(force?), userInitial() and userImage() (which normalizes a Frappe user_image into a usable URL).
  • Notification count — unreadCount, plus refreshUnread(), setUnread(n), incUnread(by?) and decUnread(by?) so a screen that marks something read can correct the bell without a refetch.

Composables

  • useIsDesktop() — a ref<boolean>, true at window.innerWidth >= 900, initialised synchronously so the first render is already correct and kept current on resize.
  • useDialogLayer(step = 10) — returns a dialog's z-index and hands the next one down a layer. The depth travels down the component tree rather than through a module-level counter, so nested dialogs (a mandatory Link field inside a quick-create dialog opening another quick-create dialog) are ordered right by construction and closing one leaves nothing to unwind. MODAL_LAYER (2000) is the base; toasts stay above at 12000.
  • useRevenyuUiConfig() — reads the config provided by RevenyuUiProvider. Entirely optional: it falls back to built-in defaults when no provider is mounted.

Configuration

RevenyuUiProvider

Wrap your app to set library-wide defaults — toastDuration, iconSize, size. Every field is optional and components fall back to the defaults, so mounting the provider is never required.

configureNotifications

The shared Notifications panel and bell default to HR Recruiter's hiring-scoped backend and routes. Any other app calls this once at startup; partial overrides are merged over the default, so you can replace just routeFor and keep the rest:

import { configureNotifications } from '@revenyu/ui'

configureNotifications({
  list: limit => call('myapp.api.notifications.list', { limit }),
  routeFor: n => (n.document_type === 'Lead' ? `/leads/${n.document_name}` : null),
})

The full contract is NotificationsApi: list, unreadCount, markRead, markAllRead, dismiss, routeFor. getNotificationsApi() returns whatever is currently installed.

setCallRouter

Hands the lib/doppio fetch wrapper your router so a 401/403 from any API call can redirect to your app's own login route. Call it once at startup.

Exported types

ConfirmOpts, CrmBreadcrumbSegment, DropdownItem, ListFilterFieldDef, ListFilterOption, ListToolbarColumn, ListToolbarSortOption, ListViewColumn, NotificationRow, NotificationType, NotificationsApi, ProfileMenuItem, SearchEntityStyle, SearchResultGroup, SearchResultItem, SettingsMenuItem, StepperStep.

Design tokens and styles

src/style.css (exported as @revenyu/ui/style.css) defines the color/radius/shadow/typography CSS custom properties every component reads via var(--x). Import it once at your app root.

@revenyu/ui/rv-list.css is separate and opt-in: the list-page pattern (search + filter grid

  • sortable/checkbox table + popovers + pagination + bulk bar) ported verbatim from revenyu_v2's CRM Leads list, so every consuming app renders byte-identical output instead of hand-porting values per app. Its tokens are namespaced under --rv-*, so importing it never overwrites your app's own --primary, --border, and so on.

Shared TypeScript config

// tsconfig.json
{ "extends": "@revenyu/ui/tsconfig.base.json" }

Gives you the same strict, moduleResolution: "Bundler", isolatedModules baseline the library itself compiles under.

Known limitations

BottomNav, SideNav, MoreDrawer, and PageCrumbs are not yet generic — their nav items and page titles (paths, icons, labels — see src/utils/pageMeta.ts for PageCrumbs) are hardcoded to HR Recruiter's own routes (/jobs, /applicants, /interviews, /offers, /onboarding, /settings/team, …), not driven by props. A consumer app other than HR Recruiter that mounts Shell/ShellDesktop today will get HR Recruiter's nav and breadcrumbs pointing at routes it doesn't have. Treat these four as HR-Recruiter-only until they're made configurable (tracked in CHANGELOG.md).

Topbar, TopbarDesktop, AppSwitcher, Notifications, ProfileMenu, and GlobalSearch are not affected — they read their state from the shared stores, not from hardcoded nav/route data. Breadcrumb, Breadcrumbs, and CrmBreadcrumb take their items via props.

Component docs

yarn docs:dev runs a local VitePress site with a live, adjustable-prop playground for every component, plus a full composed page example. Not deployed anywhere yet — clone this repo and run it locally.

Development

yarn install
yarn lint         # eslint
yarn format:check # prettier
yarn type-check   # vue-tsc --noEmit
yarn test         # vitest
yarn pack:check   # confirm `exports`/`files` resolve correctly before publishing

Versioning

Semver: patch = fixes, minor = new components/props, major = breaking prop/behavior changes (e.g. what openSelect/askConfirm accept). Consumers bump with yarn upgrade @revenyu/ui.

Releasing: bump version in package.json in the same commit or later than the feature it ships, tag that commit vX.Y.Z, push the tag, npm publish, then cut a GitHub release. Bumping in an earlier commit than the feature is what produced the broken 0.14.0 described above.

Related Packages

forty-two-vue

A vue.js component library

594
@quidgest/ui

Quidgest's UI framework

22,574
low-component-lib

A Vue 3 component library

6,458
@unnovate-brains/form-builder

A Form Builder Component for Vue 3

1,309 6

Version History

Version Released Vue License
0.15.1 >=3.5.0 Custom
0.15.0 >=3.5.0 Custom
0.14.0 >=3.5.0 Custom
0.13.0 >=3.5.0 Custom
0.12.3 >=3.5.0 Custom