@havena/esm-core-components
v1.0.1
Published
Core ESM components library
Readme
@havena/esm-core-components
Core ESM components library providing reusable React components, UI widgets, and utilities for building Haven ESM platform applications.
Installation
npm install @havena/esm-core-components
# or
yarn add @havena/esm-core-componentsOverview
This package provides a comprehensive set of React components and utilities for building applications on the Haven ESM platform:
- UI Components: Color scheme toggle, icons, headers, banners
- Data Display: Data tables with sorting, filtering, and pagination
- State Management: Loading skeletons, empty states, error states
- Access Control: Authentication and authorization wrappers
- Layout Components: Dashboard headers, workspace wrappers
- Utilities: Icon picker, conditional rendering helpers
Core Features
Color Scheme Toggle
A theme toggle component that cycles through light, dark, and auto color schemes.
import { ColorSchemeToggle } from "@havena/esm-core-components";
function Header() {
return (
<Group>
<ColorSchemeToggle />
</Group>
);
}Features:
- Cycles through light → dark → auto → light
- Uses Mantine's color scheme system
- Accessible with proper ARIA labels
Tabler Icons
Icon components and utilities for using Tabler Icons in your application.
Basic Icon Usage
import { TablerIcon } from "@havena/esm-core-components";
function MyComponent() {
return (
<div>
<TablerIcon name="user" size={24} stroke={2} color="blue" />
<TablerIcon name="settings" size={20} />
</div>
);
}Icon Picker
Interactive icon picker with search and pagination:
import { TablerIconPicker } from "@havena/esm-core-components";
function IconSelector() {
const [selectedIcon, setSelectedIcon] = useState("user");
return (
<TablerIconPicker
initialIcon={selectedIcon}
onIconSelect={(icon) => {
setSelectedIcon(icon);
console.log("Selected icon:", icon);
}}
renderTriggerComponent={({ onTrigger }) => (
<Button onClick={onTrigger} leftSection={<TablerIcon name={selectedIcon} />}>
Choose Icon
</Button>
)}
/>
);
}Icon Utilities:
import { getAllTablerIconNames, getTablerIconCategories } from "@havena/esm-core-components";
// Get all available icon names
const allIcons = getAllTablerIconNames();
// Get icons grouped by category
const categories = getTablerIconCategories();Component Skeletons
Loading skeleton components for better UX during data fetching.
Table Skeleton
import { TableSkeleton } from "@havena/esm-core-components";
function LoadingTable() {
return <TableSkeleton rows={10} columns={5} />;
}Input Skeleton
import { InputSkeleton } from "@havena/esm-core-components";
function LoadingForm() {
return (
<Stack>
<InputSkeleton />
<InputSkeleton />
<InputSkeleton />
</Stack>
);
}State Widgets
Components for handling different application states.
Empty State
Display when there's no data to show:
import { EmptyState } from "@havena/esm-core-components";
function UserList({ users }) {
if (users.length === 0) {
return (
<EmptyState
title="No users found"
message="Get started by creating your first user"
icon="users"
onAdd={() => navigate("/users/new")}
/>
);
}
// ... render users
}Error State
Display error messages with retry functionality:
import { ErrorState } from "@havena/esm-core-components";
function DataView({ error, onRetry }) {
if (error) {
return (
<ErrorState
title="Failed to load data"
message="We encountered an error while loading the data"
error={error}
onRetry={onRetry}
/>
);
}
// ... render data
}When Component
Conditional rendering helper for async states:
import { When } from "@havena/esm-core-components";
function UserProfile() {
const { data, error, isLoading } = useApi("/users/me");
return (
<When
asyncState={{ data, error, isLoading }}
loading={() => <TableSkeleton />}
error={(err) => <ErrorState error={err} />}
success={(user) => (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)}
elseRender={() => <EmptyState message="No user data" />}
/>
);
}Header Link
Navigation link component for headers and navigation menus:
import { HeaderLink } from "@havena/esm-core-components";
function Navigation() {
return (
<Group>
<HeaderLink to="/dashboard" label="Dashboard" icon="dashboard" />
<HeaderLink to="/users" label="Users" icon="users" />
<HeaderLink
to="/settings"
label="Settings"
icon="settings"
activeWhen={(path) => path.startsWith("/settings")}
/>
</Group>
);
}Drawer Mode:
function DrawerNavigation({ onClose }) {
return (
<Stack>
<HeaderLink
to="/dashboard"
label="Dashboard"
icon="dashboard"
onClose={onClose}
/>
<HeaderLink
to="/users"
label="Users"
icon="users"
onClose={onClose}
/>
</Stack>
);
}Data Tables
Powerful data table component built on TanStack Table with sorting, filtering, and pagination.
Basic Data Table
import { DataTable } from "@havena/esm-core-components";
import { ColumnDef } from "@tanstack/react-table";
type User = {
id: string;
name: string;
email: string;
};
const columns: ColumnDef<User>[] = [
{
accessorKey: "name",
header: "Name",
},
{
accessorKey: "email",
header: "Email",
},
];
function UserTable() {
const users = useApi("/users");
return (
<DataTable
columns={columns}
data={users.data?.results || []}
title="Users"
/>
);
}Stateful Data Table
Data table with built-in loading, error, and empty states:
import { StateFullDataTable } from "@havena/esm-core-components";
function UserTable() {
const { data, error, isLoading } = useApi("/users");
return (
<StateFullDataTable
columns={columns}
data={data?.results || []}
isLoading={isLoading}
error={error}
onAdd={() => navigate("/users/new")}
retryOnError={() => refetch()}
title="Users"
nothingFoundMessage="No users found. Create your first user!"
/>
);
}Advanced Data Table Features
function AdvancedTable() {
return (
<DataTable
columns={columns}
data={data}
title="Users"
withColumnViewOptions={true}
renderActions={(table) => (
<Group>
<Button onClick={() => handleBulkDelete(table)}>
Delete Selected ({table.getFilteredSelectedRowModel().rows.length})
</Button>
<Button onClick={handleExport}>Export</Button>
</Group>
)}
renderPaginator={(table) => (
<Pagination
page={table.getState().pagination.pageIndex + 1}
total={table.getPageCount()}
onChange={(page) => table.setPageIndex(page - 1)}
/>
)}
renderExpandedRow={(row) => (
<div>
<p>User ID: {row.original.id}</p>
<p>Created: {row.original.createdAt}</p>
</div>
)}
/>
);
}Multiple Views
function TableWithViews() {
return (
<StateFullDataTable
columns={columns}
data={data}
views={{
grid: (table) => <GridView data={table.getRowModel().rows} />,
list: (table) => <ListView data={table.getRowModel().rows} />,
}}
renderViewTabItem={(view) => {
const labels = { grid: "Grid", list: "List", table: "Table" };
return labels[view] || view;
}}
defaultView="table"
/>
);
}Data Table Sub-components:
import {
DataTableColumnHeader,
DataTableHeader,
DataTableViewOptions,
DataTablePagination,
} from "@havena/esm-core-components";
// Custom column header with sorting
const sortableColumn: ColumnDef<User> = {
accessorKey: "name",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Name" />
),
};
// Column visibility options
<DataTableViewOptions table={table} getOptionLabel={(col) => col.id} />
// Custom pagination
<DataTablePagination table={table} />Access Control Components
Components for handling authentication and authorization.
Authenticated Component
Wrapper component that controls rendering based on authentication state:
import { Authenticated } from "@havena/esm-core-components";
// Require authenticated user
function ProtectedPage() {
return (
<Authenticated
unauthenticatedAction={{ type: "redirect", path: "/login" }}
>
<DashboardContent />
</Authenticated>
);
}
// Render function with session data
function ProfilePage() {
return (
<Authenticated>
{(session, user) => (
<div>
<h1>Welcome, {user.name}!</h1>
<p>Session ID: {session.id}</p>
</div>
)}
</Authenticated>
);
}
// Require guest (not logged in)
function LoginPage() {
return (
<Authenticated
requireSession={false}
unauthenticatedAction={{
type: "redirect",
path: "/dashboard",
}}
>
<LoginForm />
</Authenticated>
);
}Higher Order Component:
import { withAuthentication } from "@havena/esm-core-components";
const Dashboard = () => <div>Dashboard</div>;
const ProtectedDashboard = withAuthentication(Dashboard, {
unauthenticatedAction: { type: "redirect", path: "/login" },
injectSession: true, // Inject session and user as props
});
// Usage with injected props
const Profile = ({ session, user }) => (
<div>Hello, {user.name}</div>
);
const ProfileWithAuth = withAuthentication(Profile, {
injectSession: true,
});Require Guest Component
Component that only renders when user is NOT authenticated:
import { RequireGuest } from "@havena/esm-core-components";
function LoginPage() {
return (
<RequireGuest
callbackParamName="next"
afterAuthRedirect="/dashboard"
>
<LoginForm />
</RequireGuest>
);
}Higher Order Component:
import { withRequireGuest } from "@havena/esm-core-components";
const LoginPage = () => <LoginForm />;
export default withRequireGuest(LoginPage, {
callbackParamName: "next",
afterAuthRedirect: "/dashboard",
});Authorization Components
Components for permission-based access control.
System-Level Authorization:
import { Authorized } from "@havena/esm-core-components";
function AdminPanel() {
return (
<Authorized
level="system"
permissions={{ user: ["impersonate", "delete"] }}
unauthorizedAction={{ type: "hide" }}
>
<ImpersonateButton />
<DeleteUserButton />
</Authorized>
);
}Organization-Level Authorization:
function PropertyManagement() {
return (
<Authorized
level="organization"
permissions={{ property: ["create", "update"] }}
unauthorizedAction={{
type: "fallback",
component: <Text>Upgrade to access this feature</Text>,
}}
>
<CreatePropertyButton />
</Authorized>
);
}Higher Order Component:
import { withAuthorization } from "@havena/esm-core-components";
const AdminDashboard = () => <div>Admin Content</div>;
const ProtectedAdminDashboard = withAuthorization(AdminDashboard, {
level: "system",
permissions: { user: ["list", "create", "delete"] },
unauthorizedAction: { type: "redirect", path: "/dashboard" },
});Organization Context
Component for handling organization context and requirements:
import { OrganizationContext } from "@havena/esm-core-components";
function OrganizationPage() {
return (
<OrganizationContext
requireOrganization={true}
noOrganizationAction={{
type: "redirect",
path: "/organizations/select",
}}
>
{(organization) => (
<div>
<h1>{organization.name}</h1>
<p>{organization.description}</p>
</div>
)}
</OrganizationContext>
);
}Higher Order Component:
import { withOrganizationContext } from "@havena/esm-core-components";
const OrganizationSettings = ({ organization }) => (
<div>Settings for {organization.name}</div>
);
const ProtectedSettings = withOrganizationContext(OrganizationSettings, {
requireOrganization: true,
injectOrganization: true,
noOrganizationAction: {
type: "redirect",
path: "/organizations/select",
},
});Contextual Banner
A flexible banner component for displaying entity information with actions:
import { ContextualBanner } from "@havena/esm-core-components";
function PropertyBanner({ propertyId }) {
const { data, error, isLoading } = useApi(`/properties/${propertyId}`);
return (
<ContextualBanner
data={data}
error={error}
isLoading={isLoading}
icon="home"
color="blue"
renderTitle={(property) => property.name}
renderId={(property) => property.id}
renderTimeStamp={(property) => formatDate(property.createdAt)}
renderBadges={(property) => (
<Badge color={property.status === "active" ? "green" : "gray"}>
{property.status}
</Badge>
)}
renderMenuExtensionSlot={(property) => (
<Menu>
<Menu.Item onClick={() => handleEdit(property)}>Edit</Menu.Item>
<Menu.Item onClick={() => handleDelete(property)}>Delete</Menu.Item>
</Menu>
)}
renderExpandedSection={(property) => (
<Stack>
<Text>{property.description}</Text>
<Group>
<Text>Address: {property.address}</Text>
<Text>Price: {property.price}</Text>
</Group>
</Stack>
)}
/>
);
}Dashboard Components
Dashboard Page Header
Standardized page header for dashboard pages:
import { DashboardPageHeader } from "@havena/esm-core-components";
function UsersPage() {
return (
<>
<DashboardPageHeader
title="Users"
subTitle="Manage your organization's users"
icon="users"
/>
{/* Page content */}
</>
);
}
// With custom subtitle
function CustomPage() {
return (
<DashboardPageHeader
title="Analytics"
subTitle={() => (
<Group>
<Text>Last updated: {new Date().toLocaleString()}</Text>
<Button size="xs">Refresh</Button>
</Group>
)}
icon={() => <CustomIcon />}
/>
);
}Workspace Components
Workspace system for managing side panels and overlays.
Workspace Wrapper
import { WorkspaceWrapper } from "@havena/esm-core-components";
function UserDetailsWorkspace({ userId, onClose }) {
const { data } = useApi(`/users/${userId}`);
return (
<WorkspaceWrapper
id={`user-${userId}`}
title="User Details"
onClose={onClose}
expandable={true}
>
<Stack p="md">
<Text>Name: {data?.name}</Text>
<Text>Email: {data?.email}</Text>
</Stack>
</WorkspaceWrapper>
);
}Workspace Store
import {
useWorkspaceStore,
useWorkspaces,
useWorkspace,
} from "@havena/esm-core-components";
function WorkspaceManager() {
const { workspaces, dismiss } = useWorkspaces();
const updateWorkspaces = useWorkspaceStore((state) => state.updateWorkspaces);
// Open a new workspace
const openWorkspace = (id: string, title: string) => {
const newWorkspace = {
id,
title,
expanded: false,
width: "wide",
};
updateWorkspaces([...workspaces, newWorkspace]);
};
// Get the latest workspace
const { workspace, dismiss: dismissLatest } = useWorkspace();
return (
<div>
{workspaces.map((ws) => (
<WorkspaceWrapper
key={ws.id}
id={ws.id}
title={ws.title}
onClose={() => dismiss(ws.id)}
>
{/* Workspace content */}
</WorkspaceWrapper>
))}
</div>
);
}API Reference
UI Components
ColorSchemeToggle- Theme toggle buttonTablerIcon- Icon componentTablerIconPicker- Interactive icon pickerHeaderLink- Navigation link component
Data Display
DataTable<TData, TValue>- Main data table componentStateFullDataTable<TData, TValue>- Data table with statesDataTableColumnHeader- Sortable column headerDataTableHeader- Table header with actionsDataTableViewOptions- Column visibility toggleDataTablePagination- Pagination component
State Widgets
EmptyState- Empty state componentErrorState- Error state componentWhen<TData, TError>- Conditional rendering helperTableSkeleton- Table loading skeletonInputSkeleton- Input loading skeleton
Access Control
Authenticated- Authentication wrapper componentwithAuthentication- Authentication HOCRequireGuest- Guest-only wrapper componentwithRequireGuest- Guest-only HOCAuthorized- Permission-based wrapper componentwithAuthorization- Authorization HOCOrganizationContext- Organization context wrapperwithOrganizationContext- Organization context HOC
Layout Components
ContextualBanner<TData>- Entity information bannerDashboardPageHeader- Dashboard page headerWorkspaceWrapper- Workspace panel wrapper
Utilities
getAllTablerIconNames()- Get all available icon namesgetTablerIconCategories()- Get icons grouped by categoryuseWorkspaceStore- Workspace Zustand storeuseWorkspaces()- Get all workspacesuseWorkspace()- Get latest workspace
Peer Dependencies
This package requires the following peer dependencies:
react^18.3.1react-dom^18.3.1react-router^5.3.4react-router-dom^5.3.4@havena/esm-core-api*@mantine/core^8.3.4@mantine/hooks^8.3.4@mantine/modals^8.3.4@mantine/notifications^8.3.4@mantine/spotlight^8.3.4@tabler/icons-react^3.35.0@tanstack/react-table^8.21.3zustand^5.0.8
TypeScript Support
All components are fully typed with TypeScript. The package exports type definitions for:
TablerIconName- Type-safe icon namesTablerIconProps- Icon component propsDataTableProps<TData, TValue>- Data table propsAuthorizedProps- Authorization component propsAuthenticatedProps- Authentication component props- And more...
Best Practices
Access Control: Always use server-side permission checks for security. Client-side checks are for UI optimization only.
Error Handling: Use
ErrorStatecomponent for consistent error display across your application.Loading States: Use skeleton components (
TableSkeleton,InputSkeleton) for better perceived performance.Data Tables: Use
StateFullDataTablewhen you need built-in loading, error, and empty states.Icons: Use
TablerIconfor consistent icon rendering. UseTablerIconPickerfor user-selectable icons.Workspaces: Use workspace components for side panels and overlays that need to be managed independently.
