npm package: @havena/esm-core-components
Description: Core ESM components library
Latest version: 1.0.1

# @havena/esm-core-components

Core ESM components library providing reusable React components, UI widgets, and utilities for building Haven ESM platform applications.

## Installation

```bash
npm install @havena/esm-core-components
# or
yarn add @havena/esm-core-components
```

## Overview

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.

```typescript
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

```typescript
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:

```typescript
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:**

```typescript
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

```typescript
import { TableSkeleton } from "@havena/esm-core-components";

function LoadingTable() {
  return <TableSkeleton rows={10} columns={5} />;
}
```

#### Input Skeleton

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:

```typescript
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:**

```typescript
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

```typescript
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:

```typescript
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

```typescript
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

```typescript
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:**

```typescript
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:

```typescript
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:**

```typescript
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:

```typescript
import { RequireGuest } from "@havena/esm-core-components";

function LoginPage() {
  return (
    <RequireGuest
      callbackParamName="next"
      afterAuthRedirect="/dashboard"
    >
      <LoginForm />
    </RequireGuest>
  );
}
```

**Higher Order Component:**

```typescript
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:**

```typescript
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:**

```typescript
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:**

```typescript
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:

```typescript
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:**

```typescript
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:

```typescript
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:

```typescript
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

```typescript
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

```typescript
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 button
- `TablerIcon` - Icon component
- `TablerIconPicker` - Interactive icon picker
- `HeaderLink` - Navigation link component

### Data Display

- `DataTable<TData, TValue>` - Main data table component
- `StateFullDataTable<TData, TValue>` - Data table with states
- `DataTableColumnHeader` - Sortable column header
- `DataTableHeader` - Table header with actions
- `DataTableViewOptions` - Column visibility toggle
- `DataTablePagination` - Pagination component

### State Widgets

- `EmptyState` - Empty state component
- `ErrorState` - Error state component
- `When<TData, TError>` - Conditional rendering helper
- `TableSkeleton` - Table loading skeleton
- `InputSkeleton` - Input loading skeleton

### Access Control

- `Authenticated` - Authentication wrapper component
- `withAuthentication` - Authentication HOC
- `RequireGuest` - Guest-only wrapper component
- `withRequireGuest` - Guest-only HOC
- `Authorized` - Permission-based wrapper component
- `withAuthorization` - Authorization HOC
- `OrganizationContext` - Organization context wrapper
- `withOrganizationContext` - Organization context HOC

### Layout Components

- `ContextualBanner<TData>` - Entity information banner
- `DashboardPageHeader` - Dashboard page header
- `WorkspaceWrapper` - Workspace panel wrapper

### Utilities

- `getAllTablerIconNames()` - Get all available icon names
- `getTablerIconCategories()` - Get icons grouped by category
- `useWorkspaceStore` - Workspace Zustand store
- `useWorkspaces()` - Get all workspaces
- `useWorkspace()` - Get latest workspace

## Peer Dependencies

This package requires the following peer dependencies:

- `react` ^18.3.1
- `react-dom` ^18.3.1
- `react-router` ^5.3.4
- `react-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.3
- `zustand` ^5.0.8

## TypeScript Support

All components are fully typed with TypeScript. The package exports type definitions for:

- `TablerIconName` - Type-safe icon names
- `TablerIconProps` - Icon component props
- `DataTableProps<TData, TValue>` - Data table props
- `AuthorizedProps` - Authorization component props
- `AuthenticatedProps` - Authentication component props
- And more...

## Best Practices

1. **Access Control**: Always use server-side permission checks for security. Client-side checks are for UI optimization only.

2. **Error Handling**: Use `ErrorState` component for consistent error display across your application.

3. **Loading States**: Use skeleton components (`TableSkeleton`, `InputSkeleton`) for better perceived performance.

4. **Data Tables**: Use `StateFullDataTable` when you need built-in loading, error, and empty states.

5. **Icons**: Use `TablerIcon` for consistent icon rendering. Use `TablerIconPicker` for user-selectable icons.

6. **Workspaces**: Use workspace components for side panels and overlays that need to be managed independently.

## License