prolonreactcomponents
v3.2.0
Published
Reusable components for prolon BMSnet solution
Downloads
1,811
Readme
prolonreactcomponents
Shared React components, models and helpers for the BMSnet applications (BMSnetAdmin, the identity provider client, the alarm UI). This file is the one description of how an application consumes the library; the applications are expected to follow it, so that they all look and behave the same and a change lands in one place.
What you get
- App shell:
AuthorizationProvider,Topbar,MenuDrawer,ApplicationMenuand themenuresolver,createProlonTheme. - Screens:
ActionBar,ProlonDataGrid/DataGridBase,SidesheetBasewithSidesheetSection,PropertyList,Tabs,SplitView,Breadcrumb. - Inputs:
InputField,SearchField,Select,Autocomplete,CheckBox,CoordField,DateRangePicker,YearPicker, the tag editors,FormulaEditor,TimeValueCreator. - Models and helpers:
EntityViewModel,EntityTreeModel,Tenant,Tagand the tag kinds,User,fetchState,CustomError,isValidGuid,samePageLinkNavigation.
Everything is exported from the package root. dist/index.d.ts is the authoritative list.
Requirements
- ESM only (since 3.0.0). The library is published as ES modules, which every Vite application consumes natively. There is no CommonJS build and there will not be one: a CommonJS library resolves to the CommonJS copy of dual-build packages such as react-oidc-context while an ESM application resolves to the ESM copy, and React contexts do not cross that line. That is why the shared
AuthorizationProvidercould not be used before 3.0.0. - Peer dependencies: React 19,
@mui/material7,@mui/icons-material7, MUI X 8 (@mui/x-data-grid-pro,@mui/x-date-pickers-pro,@mui/x-license),react-router-dom7,react-oidc-context3,oidc-client-ts3,@emotion/reactand@emotion/styled,date-fns4,dayjs,moment. Keep the application on the same majors;npm installrefuses otherwise, and--legacy-peer-depsis not the answer, since it silences every future mismatch too.
Install
npm install prolonreactcomponents@^3Importing
Import from the package root:
import { ActionBar, SidesheetBase, SidesheetSection, fetchState } from 'prolonreactcomponents';The build is ESM with sideEffects: false, so bundlers drop what you do not use. The deep-path form (prolonreactcomponents/dist/Sidesheet/SidesheetBase) still resolves and older code need not change, but there is no longer a reason to write it.
Do the same with MUI: @mui/icons-material/Add rather than the icons barrel, since MUI's icon barrel is not tree-shaken in development.
The app shell
Every BMSnet application is built the same way. Top to bottom:
<AppSettingsProvider> {/* the app's own: fetches /api/settings?clientId=… */}
<BrowserRouter basename={basePath}>
<AuthorizationProvider …> {/* library */}
<ThemeProvider theme={createProlonTheme(daDK, dataGridDaDK)} defaultMode="system">
<CssBaseline enableColorScheme />
<Topbar … /> {/* library, mode from useColorScheme */}
<MenuDrawer … /> {/* library */}
<Outlet />
</ThemeProvider>
</AuthorizationProvider>
</BrowserRouter>
</AppSettingsProvider>Settings
Settings come from the settings service, anonymously, before sign-in: GET {origin}/api/settings?clientId=<client> returns the authority, the API base URLs, the theme colour, the sibling application paths and the application menu. Fetch it once at the top of the tree and expose it through a context. The library does not do this for you, because each application has its own client id and settings shape, but it expects the fields below to be present.
Authorization
import { AuthorizationProvider } from 'prolonreactcomponents';
import { AuthProviderProps } from 'react-oidc-context';
const oidcConfig: AuthProviderProps = {
authority: settings.authority,
client_id: clientId,
scope: "bmsnetdataapi offline_access openid bmsnetiot",
redirect_uri: `${window.location.origin}${basePath}/authentication/callback`,
silent_redirect_uri: `${window.location.origin}${basePath}/authentication/signin-callback`,
post_logout_redirect_uri: `${window.location.origin}${basePath}`,
response_type: "code",
automaticSilentRenew: true,
loadUserInfo: true,
// Return to where the user was before the sign-in round trip. The handler stores it.
onSigninCallback: () => {
const stored = window.sessionStorage.getItem("location");
if (stored) navigate(JSON.parse(stored), { replace: true });
},
};
<AuthorizationProvider clientId={clientId} authority={settings} oidcConfig={oidcConfig} basePath={basePath}>
{children}
</AuthorizationProvider>The provider wraps react-oidc-context's AuthProvider and a handler that shows a spinner while loading, sends an unauthenticated user to sign-in once per page load after remembering where they were, and on an authentication error shows the message with a retry button rather than redirecting again (an automatic redirect on error loops against a failing identity server). Pass labels to translate the error texts; the defaults are Danish. Code outside React (an axios interceptor, a SignalR token factory) can read the current token through useAuth() in a small component that publishes it, or through the exported auth object. Anywhere below it, useAuth() from react-oidc-context gives the user and the access token. Until settings.authority has arrived, render children without the provider; the identity provider is not known yet.
The OAuth client's redirect URIs name the exact origin, so a dev server must run on a fixed port.
Theme and colour mode
createProlonTheme(...localization) returns the BMSnet theme with both colour schemes and the component overrides (square controls, dense grids, list and tab sizing). Pass the MUI and MUI X locale objects for the language.
Colour mode is owned by MUI: render ThemeProvider with defaultMode="system" and drive the Topbar's Lys / System / Mørk switch from useColorScheme(), in a component rendered inside the provider:
function ThemedTopbar(props: Omit<TopbarProps, 'mode' | 'setMode'>) {
const { mode, setMode } = useColorScheme();
return <Topbar {...props} mode={mode ?? 'system'} setMode={setMode} />;
}Do not keep a light/dark state of your own: with colour schemes declared, the provider ignores palette.mode, and MUI persists the user's choice for you.
Topbar, drawer and application switcher
Topbartakes the title, an optionallogoslot (the application switcher button goes here), the user block (name, company, email,signOut,userClick) and the mode props above.MenuDrawertakesbuttons: NavButton[]anduserAccess. ANavButtonwithdividerstarts a group;requiresAnyOfhides an entry from users without one of the named permissions.- The switcher between BMSnet applications is served by the settings service as
menu, a JSON-encoded string. Always go throughparseMenu, never a length check, thenresolveMenuwith ahasPermissioncallback backed by the access token's role claims, then render withApplicationMenu. Navigate withlocation.assign, notreplace, so Back returns to the application.defaultMenuexists only as a fallback for the service being unreachable.
Sidesheets
SidesheetBase is the panel: title, subtitle, action bar, alerts, bottom buttons, unsaved-changes dialog. Group its content with variant="grouped" and SidesheetSection:
<SidesheetBase title={entity.dis} variant="grouped" …>
<SidesheetSection title="Tenant">…</SidesheetSection>
<SidesheetSection title="Værdi tags" collapsible>…</SidesheetSection>
</SidesheetBase>A collapsible section gets a heading and a chevron; a plain one gets a small label. PropertyList / PropertyRow render read-only facts as label left, value right, with an optional copy button. The variant is opt-in, so a sheet without it looks as it always did.
Data grid selection
MUI X 8's rowSelectionModel is { type: 'include' | 'exclude', ids: Set }. Never read .ids as the selection: an exclude model, which the header checkbox produces, lists what is not selected. Keep application state as an id array and convert at the grid boundary.
Versioning and publishing
- Semantic versioning. Additive changes are a minor bump, a change that alters what existing consumers get is a major bump. 3.0.0 is the ESM switch.
- Publishing is automatic: every push to
masterbuildssrc/componentsand publishes it to npm. Bumpsrc/components/package.jsonin the same push as the change it releases. A push without a bump fails the publish harmlessly; a bump pushed before the change publishes the old code under the new number. - Never
npm publishby hand. - Record the change in
CHANGELOG.mdnext to this file.
Upgrading from 2.x to 3.0.0
npm install prolonreactcomponents@^3.- Nothing to change for imports; deep paths keep working.
- If the application kept a local copy of
AuthorizationProviderto work around the context duplication, replace it with the library's as shown above and delete the copy. - If the application drove the Topbar's mode switch from its own state, move it to
useColorSchemeas shown above.
