npm package: @se-studio/core-ui
Description: Shared React UI component library with Tailwind CSS v4 for SE Studio applications
Latest version: 1.0.126

# @se-studio/core-ui

Shared React UI component library with Tailwind CSS v4 and CMS infrastructure for SE Studio applications.

## Features

- **React 19** - Built with the latest React
- **TypeScript** - Full type safety
- **CMS Infrastructure** - Complete system for mapping Contentful content to React components
- **Tailwind CSS v4** - Modern utility-first CSS framework with custom breakpoints
- **Visual Components** - Optimized image, video, and animation components
- **Rich Text Rendering** - RTF (Rich Text Field) support with embedded content
- **CMS Showcase** - Shared developer tools for testing and previewing CMS components
- **Tree-shakeable** - Import only what you need
- **Dual exports** - ESM and CJS support
- **Fully tested** - Comprehensive test coverage with Vitest

## Installation

```bash
pnpm add @se-studio/core-ui
```

## Quick Start

### Basic Component Usage

```tsx
import { Visual } from '@se-studio/core-ui';
import type { IAsset } from '@se-studio/core-data-types';

const image: IAsset = {
  src: 'https://images.ctfassets.net/...',
  alt: 'Hero image',
  width: 1920,
  height: 1080
};

export default function MyPage() {
  return <Visual image={image} className="w-full h-auto" />;
}
```

### CMS Infrastructure Usage

For content-driven applications, use the CMS infrastructure:

```tsx
import { CmsContent } from '@se-studio/core-ui';
import { contentfulPageRest } from '@se-studio/contentful-rest-api';

// Fetch page from Contentful
const page = await contentfulPageRest(config, 'home');

// Automatically render the correct components
export default function Page() {
  return <CmsContent content={page} config={rendererConfig} />;
}
```

See the [CMS Infrastructure Guide](./CMS_INFRASTRUCTURE.md) for detailed documentation.

### Using Tailwind Configuration

Import the Tailwind configuration in your `tailwind.config.ts`:

```typescript
import baseConfig from '@se-studio/core-ui/tailwind-config';
import type { Config } from 'tailwindcss';

const config: Config = {
  ...baseConfig,
  content: [
    './src/**/*.{ts,tsx}',
    './node_modules/@se-studio/core-ui/dist/**/*.{js,jsx}',
  ],
  // Add your customizations here
};

export default config;
```

### Importing Styles

In your Next.js app layout or root component:

```tsx
import '@se-studio/core-ui/styles';
```

### Analytics Integration

Core UI ships analytics primitives but intentionally does not select a vendor.
Wrap your app with `AnalyticsProvider` and pass an adapter that implements the
`AnalyticsAdapter` interface.

```tsx
import { AnalyticsProvider, ConsoleAnalyticsAdapter } from '@se-studio/core-ui';

const analyticsAdapter = new ConsoleAnalyticsAdapter(); // example adapter

export function AppProviders({ children }: { children: React.ReactNode }) {
  return <AnalyticsProvider adapter={analyticsAdapter}>{children}</AnalyticsProvider>;
}
```

Use the `useAnalytics` hook anywhere within the provider to trigger events:

```tsx
import { useAnalytics } from '@se-studio/core-ui';

export function NewsletterCta() {
  const { trackClick } = useAnalytics();

  return (
    <button
      type="button"
      onClick={() =>
        trackClick('Button', 'Sign up', {
          page_title: 'Home',
          page_type: 'landing',
          slug: 'home',
        })
      }
    >
      Sign up
    </button>
  );
}
```

The provided `ConsoleAnalyticsAdapter` simply logs events for development and
documentation purposes. Each project should supply an adapter that forwards
events to its analytics platform of choice.

## Components

### CMS Infrastructure Components

#### CmsContent

Main component for rendering CMS content with automatic component mapping.

**Props:**
- `content` - Page or entry content from Contentful
- `config` - `CmsRendererConfig` - Component and collection mapping configuration
- `context?` - Optional context data to pass to components

**Example:**
```tsx
import { CmsContent } from '@se-studio/core-ui';

<CmsContent content={page} config={rendererConfig} />
```

`CmsContent` also routes **`HtmlComponent`** entries (raw HTML from Contentful) to the `HtmlComponent` wrapper. Use `CmsRendererConfig.htmlComponentFullWidthClassName` for full-bleed layout; see `CMS_INFRASTRUCTURE.md`.

#### CmsComponent

Renders individual CMS components with type-based routing.

**Props:**
- `component` - Component data from Contentful
- `config` - Component mapping configuration
- `context?` - Optional context

#### CmsCollection

Renders CMS collections (arrays of components) with type-based routing.

**Props:**
- `collection` - Array of components from Contentful
- `config` - Collection mapping configuration
- `context?` - Optional context

### Visual Components

#### Visual

Optimized image/video component with Next.js Image integration.

**Props:**
- `image` - `IAsset` - Image data with src, alt, width, height
- `video?` - `IAsset` - Optional video data
- `animation?` - `IAsset` - Optional animation/Lottie data
- `className?` - Additional CSS classes
- `priority?` - Enable priority loading for above-the-fold content
- `fill?` - Use Next.js Image fill mode

**Example:**
```tsx
import { Visual } from '@se-studio/core-ui';

<Visual
  image={imageData}
  className="w-full h-auto"
  priority
/>
```

### Rich Text Components

#### RTF (Rich Text Field)

Renders Contentful rich text with support for embedded entries and assets.

**Props:**
- `document` - Contentful rich text document
- `embeddedComponents?` - Configuration for embedded components
- `embeddedCollections?` - Configuration for embedded collections
- `renderMark?` - Custom mark renderers (e.g. `MARKS.CODE` for inline shortcodes)

**Example:**
```tsx
import { RTF } from '@se-studio/core-ui';

<RTF
  document={richTextField}
  embeddedComponents={componentConfig}
/>
```

### Utility Components

#### UnusedChecker

Development utility to detect unused CMS content types in your configuration.

**Props:**
- `config` - Your CMS renderer configuration
- `pages` - Array of pages to check against
- `allowedUnused?` - Array of content types allowed to be unused

**Example:**
```tsx
import { UnusedChecker } from '@se-studio/core-ui';

<UnusedChecker
  config={rendererConfig}
  pages={allPages}
/>
```

#### CMS Showcase

The library provides a set of components and utilities to build a CMS component showcase for your project.

- **`ShowcasePage`** - The main interface for selecting components and adjusting mock data.
- **`ShowcaseRenderPage`** - The renderer component for the preview iframe.
- **`mockFactory`** - Utilities for generating mock data from CMS registrations.

See the [CMS Showcase Infrastructure Guide](./src/showcase/README.md) for more details.

### Animation & Monitoring Components

#### ClientMonitor

Monitors page elements for scroll-based animations and visibility tracking. Automatically tracks elements with `data-component` attributes, videos, and Lottie animations using IntersectionObserver.

**Props:**
- `defaultThreshold?` - Number (0-1) - Default intersection threshold for triggering animations (default: 0.2)
- `enableDebug?` - Boolean - Enable debug logging in development mode (default: false)

**Example:**
```tsx
import { ClientMonitor } from '@se-studio/core-ui';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <ClientMonitor defaultThreshold={0.2} enableDebug={false} />
        {children}
      </body>
    </html>
  );
}
```

**How it works:**
- Automatically finds and observes all elements with `data-component` attributes
- Tracks video elements for play/pause based on visibility
- Tracks Lottie animation elements for play/pause based on visibility
- Sets `data-seen="true"` and `data-visible="true"` attributes on elements when they enter the viewport
- Uses scroll progress calculation for tall elements that exceed viewport height

**Excluding components from animation:**

To exclude a component from animation tracking, add the `data-exclude-animation` attribute:

```tsx
<div data-component="MyComponent" data-exclude-animation>
  {/* This component will not be tracked by ClientMonitor */}
  {/* It will not receive data-seen or data-visible attributes */}
</div>
```

**Custom thresholds:**

You can set a custom intersection threshold for individual elements using the `data-intersection-threshold` attribute:

```tsx
<div data-component="MyComponent" data-intersection-threshold="0.5">
  {/* This component will trigger when 50% visible */}
</div>
```

## Development

```bash
# Install dependencies
pnpm install

# Run tests
pnpm test

# Run tests in watch mode
pnpm test:watch

# Build the library
pnpm build

# Type check
pnpm type-check

# Lint
pnpm lint
```

## Tailwind Configuration

### Custom Breakpoints

The library includes custom breakpoints optimized for modern responsive design:

- `tablet`: 768px
- `laptop`: 1024px
- `desktop`: 1440px

Use them in your Tailwind classes:

```tsx
<div className="hidden tablet:block laptop:grid laptop:grid-cols-2 desktop:grid-cols-3">
  {/* Content */}
</div>
```

### Typography Utilities

Custom typography styles are available:

- `text-h1` - Heading 1 style
- `text-h2` - Heading 2 style
- `text-h3` - Heading 3 style
- `text-h4` - Heading 4 style
- `text-body` - Body text style
- `text-body-sm` - Small body text

## Configuration System

The package exports several configuration utilities:

### createCmsRendererConfig

Creates a type-safe CMS renderer configuration:

```tsx
import { createCmsRendererConfig } from '@se-studio/core-ui';

const config = createCmsRendererConfig({
  components: {
    heroSection: HeroComponent,
    featureSection: FeatureComponent
  },
  collections: {
    cardGrid: CardGridCollection
  }
});
```

### mergeCmsRendererConfig

Merges multiple configurations (useful for environment-specific or preview mode configs):

```tsx
import { mergeCmsRendererConfig } from '@se-studio/core-ui';

const previewConfig = mergeCmsRendererConfig(
  baseConfig,
  previewOverrides
);
```

## API Reference

### Main Exports

#### Components
- **`CmsContent`** - Main component for rendering CMS content with automatic component mapping
- **`CmsComponent`** - Renders individual CMS components with type-based routing
- **`CmsCollection`** - Renders CMS collections (arrays of components)
- **`Visual`** - Optimized image/video/animation component with Next.js Image integration
- **`RTF`** - Renders Contentful rich text with support for embedded entries and assets
- **`UnusedChecker`** - Development utility to detect unused CMS content types

#### Hooks
- **`useAnalytics`** - Hook for accessing analytics functions (trackEvent, trackPage, trackClick)
- **`useClickTracking`** - Hook for tracking click events with analytics
- **`useDocumentVisible`** - Hook for tracking document visibility

#### Utilities
- **`buildPageMetadata`** - Generates Next.js metadata object from CMS page model
- **`handleCmsError`** - Error handling utility for CMS operations
- **`getRelatedArticles`** - Utility to fetch related articles for collections
- **`getRelatedPeople`** - Utility to fetch related people for collections
- **`cn`** - Utility function for merging Tailwind CSS classes

#### Sitemap Utilities
- **`buildSitemap`** - Builds a Next.js sitemap from multiple async sources; calls each source in parallel, deduplicates by URL, and converts to `MetadataRoute.Sitemap`
- **`sitemapToXml`** - Converts a `MetadataRoute.Sitemap` array to sitemap XML string for Route Handlers (e.g. `/sitemap-unindexed.xml`)
- **`generateNextSitemap`** - Converts sitemap entries to Next.js format with baseUrl normalization
- **`deduplicateSitemapEntries`** - Deduplicates entries by URL (keeps most recent `lastModified`)
- **`sortSitemapEntries`** - Sorts by priority then lastModified

#### Types
- **`CmsRendererConfig`** - Complete renderer configuration type
- **`MarketingSiteSitemapConfig`** - Config for `getSitemapEntries` when using createAppHelpers (optional articlesSlug when enableArticleTypeIndex, tagsSlug, enableArticleTypeIndex, other feature flags, optional additionalSources)
- **`AppHelpersWithSitemap`** - Return type of `createAppHelpers` when `marketingSiteSitemap` is provided; use when you need to type helpers that include `getSitemapEntries`
- **`SitemapEntry`**, **`SitemapSource`**, **`GenerateSitemapOptions`** - Types for building custom sitemap sources

For detailed JSDoc documentation on all exports, see the TypeScript declaration files (`.d.ts`) in the package.

### Sitemap Usage

Use `buildSitemap` with source functions for a thin `sitemap.ts`:

```ts
// app/sitemap.ts
import { buildSitemap } from '@se-studio/core-ui';
import { baseUrl } from '@/lib/server-config';
import { getSitemapEntries } from '@/lib/sitemap-sources';

export default async function sitemap() {
  return buildSitemap(
    [() => getSitemapEntries({ includeUnindexed: false })],
    { baseUrl, defaultPriority: 0.5, defaultChangeFrequency: 'monthly', trailingSlash: true },
  );
}
```

For a custom route (e.g. unindexed pages sitemap), use `sitemapToXml`:

```ts
// app/sitemap-unindexed.xml/route.ts
import { buildSitemap, sitemapToXml } from '@se-studio/core-ui';
import { NextResponse } from 'next/server';
import { baseUrl } from '@/lib/server-config';
import { getSitemapEntries } from '@/lib/sitemap-sources';

export async function GET() {
  const entries = await buildSitemap(
    [() => getSitemapEntries({ includeUnindexed: true })],
    { baseUrl, defaultPriority: 0.5, defaultChangeFrequency: 'monthly', trailingSlash: true },
  );
  return new NextResponse(sitemapToXml(entries), {
    headers: { 'Content-Type': 'application/xml' },
  });
}
```

### Marketing Site Sitemap (createAppHelpers)

When using `createAppHelpers`, pass `marketingSiteSitemap` to enable `getSitemapEntries` for sites with articles, tags, and people. When provided, `getSitemapEntries` is guaranteed and correctly typed (return type is `AppHelpersWithSitemap`), so you can re-export it from a `sitemap-sources` module without type assertions.

```ts
// lib/cms-server.ts
const { getSitemapEntries, ... } = createAppHelpers({
  converterContext,
  getConfig: getContentfulConfig,
  buildOptions,
  marketingSiteSitemap: {
    ...(enableArticleTypeIndex && { articlesSlug: ARTICLES_SLUG }),
    tagsSlug: TAGS_SLUG,
    enableArticleTypeIndex,
    enableArticleTypeTagIndex,
    enablePeopleIndex,
    enablePerson,
    enableTag,
    enableTagsIndex,
    additionalSources: [
      () => getDrivePageSitemapEntries(),  // custom content, e.g. drive pages
    ],
  },
});
```

`additionalSources` are merged into the main sitemap only (not the unindexed sitemap). For custom content from another Contentful table, add async functions that return `SitemapEntry[]`.

### `@se-studio/core-ui/server` – Route Handlers Factory

The `/server` entry point (server-only) provides `createRouteHandlers`, a factory that centralises all `generate*Page` and `generate*Metadata` functions for CMS-driven Next.js apps.

Each app wires it once in `src/lib/route-handlers.ts`:

```typescript
import 'server-only';
import { createRouteHandlers } from '@se-studio/core-ui/server';
import BasicLayout from '@/project/BasicLayout';
import { buildOptions, getBannersWithErrors, getPageWithErrors, ... , projectRendererConfig } from './cms-server';
import { buildInformation } from './converter-context';
import { ARTICLES_SLUG, TAGS_SLUG, PEOPLE_SLUG, articlesCustomTypeSlug,
         requireCustomTypeForArticleTypes, enableTag, enablePerson, enableTagsIndex } from './constants';

export const {
  generatePage, generatePageMetadata,
  generateArticlePage, generateArticleMetadata,
  generateArticleTypePage, generateArticleTypeMetadata,
  generateArticleTypeTagPage, generateArticleTypeTagMetadata,
  generateArticleTypesIndexPage, generateArticleTypesIndexMetadata,
  generateTagPage, generateTagMetadata,
  generateTagsIndexPage, generateTagsIndexMetadata,
  generatePersonPage, generatePersonMetadata,
  generateTeamIndexPage, generateTeamIndexMetadata,
  generateCustomTypePage, generateCustomTypeMetadata,
  buildLocaleAlternates,
} = createRouteHandlers({
  rendererConfig: projectRendererConfig,
  buildOptions,
  buildInformation,
  getBannersWithErrors,
  getPageWithErrors,
  // ... remaining fetch helpers
  LayoutComponent: BasicLayout,
  constants: { ARTICLES_SLUG, TAGS_SLUG, PEOPLE_SLUG, articlesCustomTypeSlug,
               requireCustomTypeForArticleTypes, enableTag, enablePerson, enableTagsIndex },
});
```

Route files then import directly from `@/lib/route-handlers`.

#### Optional `prepare*` hooks (async `pageContext` enrichment)

`RouteHandlersConfig` may include any of these optional callbacks. Each runs **after** the route’s fetch and **after** the usual “will render” guards, and **before** `CmsContent` is rendered:

| Hook | Runs with |
|------|-----------|
| `preparePage` | `generatePage` |
| `prepareArticlePage` | `generateArticlePage` |
| `prepareArticleTypePage` | `generateArticleTypePage` |
| `prepareArticleTypeTagPage` | `generateArticleTypeTagPage` |
| `prepareArticleTypesIndexPage` | `generateArticleTypesIndexPage` |
| `prepareTagPage` | `generateTagPage` |
| `prepareTagsIndexPage` | `generateTagsIndexPage` |
| `preparePersonPage` | `generatePersonPage` |
| `prepareTeamIndexPage` | `generateTeamIndexPage` |
| `prepareCustomTypePage` | `generateCustomTypePage` |

Each hook returns `Promise<CmsPagePreparationResult>`, where `pageContext` is an optional object of extra keys to merge into `CmsContent`’s `pageContext`. **Merge order:** hook keys are applied first, then the route’s canonical keys (`page`, `article`, `tag`, `articleType`, `person`, `customType` as applicable). Canonical keys **always win**, so the hook cannot replace the loaded CMS models.

Hook inputs mirror what that route already has (models, `errors`, `path`, `locale`, `routeConfig`, route-specific slugs). `banners` is only passed when that route’s data loader already fetches banners.

Read merged values from components and rich text via `contentContext.pageContext` (and from `rendererConfig.processText(text, contentContext)` when used). Types are exported from `@se-studio/core-ui/server` (`CmsPagePreparationResult`, `PreparePageInput`, `PrepareArticlePageInput`, etc.).

Apps that bypass `createRouteHandlers` for a custom page should build the same `pageContext` merge locally if they need the same pattern.

#### Tag index and `TAGS_SLUG` custom type

`generateTagPage` / `generateTagMetadata` resolve the tag via `getTagWithErrors` (which may merge template content from the `TAGS_SLUG` entry inside the REST layer). They also call `getCustomTypeWithErrors(TAGS_SLUG)` for the shell row used in `pageContext` and metadata.

- **Default (relaxed):** If `constants.tagIndexRequiresCustomType` is omitted or `false`, a tag with non-empty `contents` still renders when the shell row is missing or returns null (for example when the entry is **hidden** in production and `getCustomTypeWithErrors` strips it). In that case `pageContext.customType` may be omitted or undefined; `IPageContext.customType` is already optional.
- **Strict (legacy):** Set `tagIndexRequiresCustomType: true` to require both the tag and a resolved shell custom type, matching the old behaviour (404 if the shell is absent).

The same flag applies to **`generateArticleTypeTagPage`** / **`generateArticleTypeTagMetadata`** for the tag shell fetched with `TAGS_SLUG`.

The `/server` entry also exports `canRenderTagIndex` and `canRenderArticleTypeTagRoute` (pure helpers used by the factory), `CmsRouteConfig`, `BreadcrumbOptions`, `toBreadcrumbOptions`, `BasicLayoutProps`, and the `prepare*`-related types (`CmsPagePreparationResult`, `PreparePageInput`, `RouteHandlersPrepareHooks`, etc.). The types (but not `createRouteHandlers`) are also re-exported from the main package entry for client-safe use where applicable.

## Advanced Usage

For advanced CMS infrastructure usage, including:
- Configuration composition patterns
- Embeddable components
- Context passing
- Preview mode setup

See the [CMS Infrastructure Guide](./CMS_INFRASTRUCTURE.md).

## Examples

Check out the [example-se2026](../../apps/example-se2026), [example-brightline](../../apps/example-brightline), or [example-om1](../../apps/example-om1) apps for complete implementations using all core-ui features.

## Learn More

- [CMS Infrastructure Guide](./CMS_INFRASTRUCTURE.md) - Comprehensive guide to the CMS system
- [Analytics Integration Guide](./ANALYTICS.md) - Analytics and GTM setup
- [Consent Management Guide](./CONSENT.md) - GDPR/CCPA compliance setup
- [Core Data Types](../core-data-types/README.md) - Shared TypeScript types
- [Contentful REST API](../contentful-rest-api/README.md) - API client for Contentful

## License

MIT