@oplytics/shared-ui
Shared UI component library for all Oplytics.digital subdomains. Provides standardised layouts, hierarchy management, RBAC, reporting, hooks, and design tokens as reusable building blocks.
Installation
pnpm add @oplytics/shared-ui
Requires
.npmrcwith@oplytics:registry=https://npm.pkg.github.com
Modules
| Module | Import | Description |
|---|---|---|
| Layout | @oplytics/shared-ui/layout | Config-driven sidebar, page header, footer, layout factory |
| Hierarchy | @oplytics/shared-ui/hierarchy | Org hierarchy types, selection state, enterprise override |
| RBAC | @oplytics/shared-ui/rbac | 7-role model, permission checks, error constants |
| Reporting | @oplytics/shared-ui/reporting | Incident reporting and feedback toolbar types/config |
| Components | @oplytics/shared-ui/components | SharedFooter, NotFound, ErrorBoundary, ViewOnlyBadge, AcceptanceBadge types |
| Hooks | @oplytics/shared-ui/hooks | useMobile, useComposition, usePersistFn |
| Theme | @oplytics/shared-ui/theme | Design tokens, color palette, typography, CSS variables |
| Actions | @pablo2410/shared-ui/actions | Due-date status (overdue, due soon) and its chip, for actions and charters |
| I18n | @pablo2410/shared-ui/i18n | Translations: useT(), English fallback, language picker, formatDate |
Quick Start
// RBAC — check permissions
import { hasMinimumRole, ALL_ROLES } from "@oplytics/shared-ui/rbac";
if (hasMinimumRole(user.role, "enterprise_admin")) {
// show admin features
}
// Hierarchy — enterprise override
import { applyEnterpriseOverride } from "@oplytics/shared-ui/hierarchy";
const resolvedUser = applyEnterpriseOverride(user, req.headers.cookie);
// Layout — service config
import {
SERVICE_CONFIGS,
defineServiceLayout,
} from "@oplytics/shared-ui/layout";
const myConfig = SERVICE_CONFIGS.sqdcp;
// Theme — design tokens
import { OPLYTICS_COLORS, OPLYTICS_FONTS } from "@oplytics/shared-ui/theme";
// Hooks
import { useMobile, usePersistFn } from "@oplytics/shared-ui/hooks";
Opi tours
Every module gets its onboarding tour the same way. A module supplies a definition and marks the elements its spotlight steps point at — nothing else is per-module (no storage, no trigger, no narration code):
import {
useRegisterTour,
type TourDefinition,
} from "@pablo2410/shared-ui/tour";
// Module scope (or useMemo) so the object is stable across renders.
const tour: TourDefinition = {
id: "policy-deployment", // lowercase-kebab; never rename once shipped
steps: [
{ id: "welcome", kind: "intro", label: "Welcome", fallbackText: "…" },
{
id: "plans",
kind: "service",
label: "Policy plans",
fallbackText: "…",
spotlight: { selector: "plan-card" }, // <div data-tour-target="plan-card">
},
],
};
function PolicyPage() {
useRegisterTour(tour);
// …
}
What the shared code then does:
- Trigger —
DashboardLayout's header shows "Take the tour" only while a tour is registered, and replays it from step 1. - Progress — read and written through Portal's
onboarding.userTourState/setUserTourState, so it follows the user across modules. No module table. - Narration — each step calls Portal's
ai.askOpiwith the reservedonboardingservice (Portal's onboarding persona), falling back to the step'sfallbackTextif Opi is unavailable. - Auto-open — off by default; set
autoStart: trueon the definition to open for users who haven't seen the tour or left it part-way. Modules never mount their ownOpiTourHost—DashboardLayoutmounts the one.
Per-tour storage needs Portal to accept and echo a tourId on those two
endpoints. Until it does, progress for module tours is not saved (reads fail
safe, nothing auto-opens, the tour and its header replay still work), and the
Service Hub tour's own progress is never overwritten.
Translations
@pablo2410/shared-ui/i18n is the one translation set-up every app uses (i18next with react-i18next). English is the default and the fallback: a string missing in another language shows in English, never as its key or a blank. Keys read <app>.<page>.<thing>; shared-ui's own are the shared. keys in src/i18n/en.json and fr.json.
import { I18nProvider, createI18n, useT } from "@pablo2410/shared-ui/i18n";
// Once, around the app, with the app's own strings beside shared-ui's:
const i18n = createI18n({ language: user.language, resources: { en, fr } });
<I18nProvider i18n={i18n} onLanguageChange={saveLanguage}>
…
</I18nProvider>;
// In a component:
const t = useT();
t("portal.home.title");
- With no provider, shared-ui's components still render, in English or the language this browser picked.
- The language picker sits in the shell's user menu (English, Français). It hands the choice to
onLanguageChange(onDashboardLayoutorI18nProvider); with none, the browser remembers it.DashboardLayout'slanguageprop switches the shell to a language the app already knows. - An app that doesn't have text in every language yet says which it offers:
<I18nProvider languages={["en"]}>. The pickers then list only those, a language held back is never the active one (even one picked before), anduseLanguage().languagesgives the same list. Left unset, every language is offered (shared-ui#211). formatDateandformatNumber(oruseFormat()) format throughIntlin the current language; English isen-GB.- What customers type themselves is shown as typed and never translated.
- The service catalog stays in English for servers and the marketing site.
localizeService(service, t)(from/services) gives a service's tagline, description and onboarding text int's language. - A test fails if a component renders text that isn't in
en.json, if display text sits in code (alabel,titleordescriptionvalue, a prop default, a*_LABELmap), or iffr.jsondoesn't matchen.jsonkey for key.
Theme CSS
Import the canonical Oplytics theme variables:
@import "@oplytics/shared-ui/theme/oplytics-theme.css";
This provides all --oplytics-* CSS custom properties for backgrounds, text, accents, borders, spacing, radius, and shadows.
Development
pnpm install
pnpm build # Build with tsup
pnpm test # Run vitest
pnpm typecheck # TypeScript check
Architecture
The package is tree-shakeable with separate entry points per module. Each subdomain only imports what it needs:
src/
layout/ # SharedSidebar, createServiceLayout, serviceConfigs
hierarchy/ # orgHierarchy types, enterpriseOverride
rbac/ # Roles, permissions, error constants
reporting/ # ReportingToolbar types and config
components/ # SharedFooter, NotFound, ErrorBoundary types
hooks/ # useMobile, useComposition, usePersistFn
theme/ # Design tokens, CSS variables
actions/ # getDueDateStatus, DueDateChip (Action Manager and Policy Deployment)
i18n/ # i18next set-up, useT, en.json / fr.json, LanguagePicker, formatDate
Versioning
- v0.x — Types, configs, and pure utilities (no React component rendering)
- v1.0 — Full React components with shadcn/ui primitives bundled
License
UNLICENSED — Internal Oplytics.digital use only.