npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@woodylab/payload

v0.0.203

Published

**UIMS — User Interface Management System.** Sistema di rendering della UI guidato dal CMS per applicazioni **Payload CMS 3 + Next.js (App Router)**.

Readme

@woodylab/payload

UIMS — User Interface Management System. Sistema di rendering della UI guidato dal CMS per applicazioni Payload CMS 3 + Next.js (App Router).

La struttura, il layout, gli stili e il tema delle pagine sono definiti in Payload (collections, globals, blocks, ViewModes); il pacchetto li risolve e li renderizza dinamicamente in componenti React. I componenti sono privi di logica (dumb): non eseguono fetch né business logic e renderizzano esclusivamente ciò che il motore UIMS fornisce loro.

Documentazione completa del pacchetto: overview, requisiti e guida all'integrazione.

Indice

Requisiti

Il pacchetto è ESM-only e si integra in un'applicazione Payload + Next moderna. Le versioni seguono l'ecosistema Payload, che ne determina i vincoli.

| Peer dependency | Versione | | --- | --- | | payload | ^3.54.0 | | @payloadcms/ui | ^3.54.0 | | @payloadcms/richtext-lexical | ^3.54.0 | | next | ^15.2.3 | | react / react-dom | ^19 |

Installazione

npm install @woodylab/payload

Le peer dependencies sono in genere già presenti in un progetto Payload + Next.

Entrypoint e design subpath-only

| Import | Contenuto | | --- | --- | | @woodylab/payload/config | Schema Payload: collections, globals, blocks, fields, hooks, override dei plugin | | @woodylab/payload/uims | Motore: createUIMSContext, uimsResolvers, resolver e mapper | | @woodylab/payload/react | Componenti React (dumb), uimsComponents, provider eventi | | @woodylab/payload/utils | Helper: generateCssVars, getCachedGlobal, … | | @woodylab/payload/uimsManifest | Dizionario di classi pre-generato (default export) | | @woodylab/payload/seeders | Seeder (syncStylesToDB, syncViewModesToDB, seedEvents) + viewModeDefaults |

Il pacchetto è subpath-only: non espone un export root .. Si tratta di una scelta deliberata. Un barrel di root unirebbe codice server (la configurazione Payload, che importa payload, il database e API Node) e codice client (i componenti React marcati "use client") nello stesso modulo, con conseguenti conflitti di risoluzione delle dipendenze e violazione dei confini server/client di RSC. I subpath isolano ogni responsabilità.

Importare sempre dai subpath pubblici elencati sopra. I percorsi interni (@woodylab/payload/dist/...) sono incapsulati dal campo exports e non vanno usati.

Quick start

L'integrazione lato consumer si articola in tre passi, dettagliati di seguito:

  1. Stili Tailwind — inclusione della safelist generata dal pacchetto e iniezione delle variabili del tema (§1).
  2. Config Payload — registrazione di collections, globals e plugin form (§2).
  3. uimsContext — creazione del contesto che collega componenti, stili e resolver, e suo utilizzo in fase di rendering (§3–§4).

1. Stili Tailwind

UIMS adotta Tailwind CSS v4 con due specificità:

  • le classi strutturali dei componenti non compaiono nei file .tsx del progetto consumer (provengono dal CMS a runtime) e vanno pertanto preservate tramite safelist;
  • i colori del tema sono modificabili dal CMS (global theme) e iniettati come variabili CSS a runtime.

1.1 La safelist e il manifest (lato pacchetto)

La build del pacchetto (npm run build → step build:manifest) genera in dist/:

| File | Cos'è | Come si usa | | --- | --- | --- | | uims-safelist.json | Array di tutte le classi Tailwind ammesse | Letto da Tailwind via @source | | uimsManifest.js | Dizionario { classe: classe } (default export) | Passato a createUIMSContext come stylesDict (§3) |

uimsManifest — natura e scopo. File generato dalla build: un insieme di classi Tailwind a supporto del bootstrap dell'applicazione, prima che il progetto definisca la propria lista di stili. Lo stesso insieme alimenta tre elementi: (1) il context (stylesDict), (2) la safelist Tailwind, (3) il seeder degli stili. Non corrisponde all'importMap di Payload: è una mappa di classi CSS, non di componenti dell'admin.

Il dizionario copre la palette semantica (primary, secondary, accent, info, success, warning, danger, dark, light, foreground) con shade -10…-90 e sotto-varianti -light-10…40 / -dark-10…40, oltre a spaziature, flex/grid, posizionamento, trasformazioni, transizioni, animazioni tailwindcss-animate e stati data-[state=…] di Radix. La safelist è inclusa nel pacchetto e non richiede generazione manuale.

1.2 Configurazione del CSS (lato consumer)

Nel file CSS globale dell'applicazione (es. src/app/(frontend)/styles.css):

@import "tailwindcss";
@plugin "tailwindcss-animate";

/* Preserva tutte le classi strutturali del pacchetto UIMS.
   La profondità del path relativo va adattata alla posizione del file CSS. */
@source "../../../node_modules/@woodylab/payload/dist/uims-safelist.json";

@theme {
  /* Font */
  --font-heading: var(--font-heading);
  --font-text: var(--font-text);

  /* Mappa i token semantici sulle variabili runtime (--tw-source-*) iniettate dal CMS.
     Esempio completo per "primary"; lo schema si ripete per le altre famiglie. */
  --color-primary: var(--tw-source-primary);
  --color-primary-10: var(--tw-source-primary-10);
  /* … -20 … -90 … */
  --color-primary-light-10: var(--tw-source-primary-light-10);
  /* … -light-20 … -light-40 … */
  --color-primary-dark-10: var(--tw-source-primary-dark-10);
  /* … -dark-20 … -dark-40 … */

  /* Lo stesso blocco si ripete per: secondary, accent, info, success, warning, danger, dark, light */
  --color-foreground: var(--tw-source-foreground);
}

@layer base {
  :root { color-scheme: light; }
  [data-theme="dark"] { color-scheme: dark; }

  body {
    background-color: var(--color-foreground);
    color: var(--color-dark);
    font-family: var(--font-text);
  }
  h1, h2, h3, h4, h5, h6 { font-family: var(--font-heading); }
}

Note:

  • @source istruisce Tailwind v4 a includere le classi del JSON anche quando non compaiono nel codice. Il path è relativo al file CSS; in un monorepo con il pacchetto in locale diventa, ad esempio, ../../../@woodylab/payload/dist/uims-safelist.json.
  • Il blocco @theme non assegna colori fissi: mappa ogni token (--color-primary) su una variabile runtime (--tw-source-primary), popolata dal CMS al passo successivo.

1.3 Iniezione delle variabili a runtime (layout.tsx)

Le variabili --tw-source-* sono generate dal global theme tramite generateCssVars e iniettate nell'<head>:

import { getPayload } from 'payload'
import payloadConfig from '@payload-config'
import { generateCssVars, getCachedGlobal } from '@woodylab/payload/utils'

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const payload = await getPayload({ config: payloadConfig })

  const theme = await getCachedGlobal(payload, 'theme')   // 1. tema dal CMS
  const cssVars = generateCssVars(theme)                  // 2. stringa CSS --tw-source-*

  return (
    <html lang="en" data-theme="light">
      <head>
        {/* 3. iniezione delle variabili prima del body */}
        <style dangerouslySetInnerHTML={{ __html: cssVars }} />
      </head>
      <body className="bg-foreground">{children}</body>
    </html>
  )
}

Il tema chiaro/scuro è pilotato dall'attributo data-theme su <html> (light / dark).


2. Configurazioni Payload

Lo schema di dominio si importa da /config, mantenendolo separato dal bundle frontend.

  • uimsCollections → array con collectionStyles, collectionViewModes, collectionBlocks, collectionSections, collectionViews, collectionMenus, collectionEvents, collectionPages.
  • uimsGlobals → array con globalTheme, globalSettings, globalFooter, globalHeader.
  • uimsFormOverrides / uimsFormSubmissionOverrides → override per @payloadcms/plugin-form-builder.
import { buildConfig } from 'payload'
import { formBuilderPlugin } from '@payloadcms/plugin-form-builder'
import {
  uimsCollections,
  uimsGlobals,
  uimsFormOverrides,
  uimsFormSubmissionOverrides,
} from '@woodylab/payload/config'

export default buildConfig({
  collections: [
    ...uimsCollections,
    // …collection applicative (Users, Media, …)
  ],
  globals: [...uimsGlobals],
  plugins: [
    formBuilderPlugin({
      formOverrides: uimsFormOverrides as any,
      formSubmissionOverrides: uimsFormSubmissionOverrides as any,
      fields: {},
    }),
  ],
  // …resto della configurazione (db, editor, secret, admin, …)
})

Le singole entità sono importabili anche individualmente (collectionViewModes, globalTheme, …), qualora si preferisca comporre gli array manualmente.

Seeding (opzionale)

I seeder del pacchetto popolano il CMS con i dati di base. L'ordine di esecuzione (tipicamente in onInit) è il seguente:

import uimsManifest from '@woodylab/payload/uimsManifest'
import { syncStylesToDB, syncViewModesToDB, seedEvents, viewModeDefaults } from '@woodylab/payload/seeders'

// in onInit(payload):
await syncStylesToDB(payload, uimsManifest)         // 1. Styles
await syncViewModesToDB(payload, viewModeDefaults)  // 2. ViewModes
await seedEvents(payload, eventNames)               // 3. Events

3. uimsContext

L'uimsContext collega l'istanza Payload, i componenti React, il dizionario degli stili e i resolver; espone resolve() per trasformare i dati del CMS in elementi renderizzabili.

3.1 La firma

createUIMSContext(
  payload: BasePayload,
  uiComponents: Record<string, any>,
  stylesDict: Record<string, string> = {},
  resolvers: Record<string, ResolverFunction> = {},
): Promise<UIMSContext>

| Argomento | Origine | Ruolo | | --- | --- | --- | | payload | getPayload({ config }) | Istanza Payload per la lettura dei dati | | uiComponents | uimsComponents da /react | Mappa tipo → componente React | | stylesDict | uimsManifest da /uimsManifest | Dizionario classi pre-generato | | resolvers | uimsResolvers da /uims | Logica di risoluzione per tipo di blocco |

Il context espone resolve(block), che restituisce { Component, ...props }. A partire dalla 0.0.197 i resolver di default (uimsResolvers) sono inclusi automaticamente anche in assenza del parametro (merge { ...uimsResolvers, ...resolvers }).

3.2 Configurazione di riferimento (uimsContext.ts)

import { getPayload } from 'payload'
import configPromise from '@payload-config'
import { uimsComponents } from '@woodylab/payload/react'
import uimsManifest from '@woodylab/payload/uimsManifest'
import { createUIMSContext, uimsResolvers } from '@woodylab/payload/uims'

const payload = await getPayload({ config: configPromise })

const uiComponents = { ...uimsComponents }   // estendibile con componenti applicativi
const mergedStyles = { ...uimsManifest }     // il manifesto fornisce gli stili anche in fase di init
const resolvers = { ...uimsResolvers }       // estendibile con resolver applicativi

export const UIMS = await createUIMSContext(payload, uiComponents, mergedStyles, resolvers)

Nota sul manifest. @woodylab/payload/uimsManifest è un artefatto di build (presente solo in dist/). In un'applicazione installata da npm la risoluzione avviene tramite exports. In un monorepo che consuma i sorgenti tramite alias, il nome va mappato sul file generato con un path TS dedicato:

// tsconfig.json → compilerOptions.paths
"@woodylab/payload/uimsManifest": ["./@woodylab/payload/dist/uimsManifest"]

3.3 Estendere il context

uiComponents, mergedStyles e resolvers sono oggetti estendibili: è possibile aggiungere le proprie voci senza modificare il pacchetto.

const uiComponents = { ...uimsComponents, Hero: MyHero }
const resolvers = { ...uimsResolvers, myType: myCustomResolver }

3.4 Utilizzo del context

resolve() accetta un global o un blocco e restituisce { Component, ...props }. I pattern completi per global e pagine sono descritti nella §4.

3.5 Interattività ed eventi

I componenti interattivi (azioni, dialog, form, accordion) richiedono il provider eventi; in sua assenza l'integrazione lato consumer è incompleta. L'orchestrazione si articola in tre file.

// app-events.ts — non è un client component, così è condivisibile tra seeder (server) e wrapper (client)
import { uimsEventHandlers } from '@woodylab/payload/react'

export const customHandlers = {
  // handler applicativi, es: 'alertDialog:confirm': async (event) => { … }
}
export const mergedHandlers = { ...uimsEventHandlers, ...customHandlers }
// uimsEventsWrapper.tsx — wrapper client che monta il provider
'use client'
import { UIMSEventProvider } from '@woodylab/payload/react'
import { mergedHandlers } from './app-events'

const defaultHandler = async (event: any) => console.log('Evento non mappato:', event)

export function UIMSEventsWrapper({ children }: { children: React.ReactNode }) {
  return (
    <UIMSEventProvider handlers={mergedHandlers} defaultHandler={defaultHandler}>
      {children}
    </UIMSEventProvider>
  )
}
// layout.tsx — il provider avvolge l'albero dell'app
<UIMSEventsWrapper>{/* header, children, footer */}</UIMSEventsWrapper>

Note:

  • UIMSEventProvider accetta handlers (mappa tipo evento → handler) e un defaultHandler (fallback per eventi non mappati).
  • Il provider è un client component e va isolato in un wrapper dedicato ('use client'), così da poter essere utilizzato dal layout.tsx lato server.
  • Gli stessi mergedHandlers alimentano il seeder degli eventi: seedEvents(payload, Object.keys(mergedHandlers)).
  • Nei componenti, useUIMSEvents() espone dispatch(event) per l'invio delle azioni.

4. Rendering: globals e pagine

Prerequisito. Né un global né una pagina sono renderizzabili senza un context UIMS correttamente inizializzato (§3): sono necessari stylesDict (il manifest), i resolvers e i uiComponents. In loro assenza UIMS.resolve() non è in grado di mappare i tipi e i componenti risultano vuoti.

| | Global | Pagina | | --- | --- | --- | | Fonte | getCachedGlobal(payload, slug) | payload.find({ collection, where: { slug } }) | | resolve restituisce | un singolo { Component, ...props } | un array di blocchi (layout) risolti | | Rendering | <Component {...props} /> diretto | <LayoutRenderer resolvedLayout={…} /> |

4.1 Global + resolve

Un global UIMS (es. header, footer) va prima registrato in configurazione (uimsGlobals — §2) e popolato dal CMS. A runtime viene letto, risolto e reso disponibile come componente:

// app/(frontend)/layout.tsx
import { getPayload } from 'payload'
import payloadConfig from '@payload-config'
import { getCachedGlobal } from '@woodylab/payload/utils'
import { UIMS } from '@/uimsContext'

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const payload = await getPayload({ config: payloadConfig })

  const header = await getCachedGlobal(payload, 'header')
  const footer = await getCachedGlobal(payload, 'footer')

  const { Component: Header, ...headerProps } = await UIMS.resolve(header)
  const { Component: Footer, ...footerProps } = await UIMS.resolve(footer)

  return (
    <body>
      <Header {...headerProps} />
      {children}
      <Footer {...footerProps} />
    </body>
  )
}
  • getCachedGlobal(payload, slug) recupera il global con tutte le sue property.
  • UIMS.resolve(global) applica il resolver associato al globalType e restituisce { Component, ...props } (le props includono gli eventuali slots).
  • Il componente viene reso direttamente: <Header {...headerProps} />.

Il global theme costituisce un'eccezione: non viene renderizzato ma passato a generateCssVars per le variabili CSS (§1.3).

4.2 Pagina + resolve

Una pagina è un documento di una collection (es. pages) con uno slug e un campo layout costituito da un array di blocchi UIMS.

UIMS fornisce una collection pages pronta all'uso (collectionPages, inclusa in uimsCollections — §2): il relativo campo layout impiega i blocchi sezione UIMS. In alternativa è possibile gestire la collection lato consumer, purché il campo layout utilizzi uimsSections:

import { uimsSections } from '@woodylab/payload/config'

// campo `layout` della collection Pages:
{
  name: 'layout',
  type: 'blocks',
  blocks: uimsSections, // = blockSectionInline + blockSectionReference
}

I blocchi dei template Payload (CallToAction, Content, …) non sono risolvibili da UIMS: il loro blockType non dispone di un resolver e non viene renderizzato. Vanno sostituiti con uimsSections.

Il route dinamico di Next mappa lo slug → documento → blocchi risolti:

// app/(frontend)/[slug]/page.tsx
import { getPayload } from 'payload'
import configPromise from '@payload-config'
import { UIMS } from '@/uimsContext'
import { LayoutRenderer } from '@woodylab/payload/react'

export default async function Page({ params }: { params: Promise<{ slug?: string }> }) {
  const { slug = 'home' } = await params
  const payload = await getPayload({ config: configPromise })

  // 1. Recupero della pagina per slug (depth elevato = relazioni popolate a fondo)
  const result = await payload.find({
    collection: 'pages',
    depth: 99,
    limit: 1,
    pagination: false,
    where: { slug: { equals: slug } },
  })
  const page = result.docs?.[0] || null

  // 2. Risoluzione di ogni blocco del layout
  const layout = page?.layout
  const resolvedLayout = Array.isArray(layout)
    ? await Promise.all(layout.map((block: any) => UIMS.resolve(block)))
    : []

  // 3. Rendering dell'array risolto
  return (
    <main>
      <LayoutRenderer resolvedLayout={resolvedLayout} />
    </main>
  )
}
  • depth: 99 popola a fondo le relazioni (reference, viewMode, styles) richieste dai resolver.
  • UIMS.resolve(block) viene invocato per ogni blocco del layout, producendo l'array resolvedLayout.
  • Per la SSG, generateStaticParams elenca gli slug della collection pages. La home corrisponde alla pagina con slug home.

4.3 LayoutRenderer e BlockRenderer

LayoutRenderer (da /react) è l'entrypoint per un array di blocchi risolti: itera sull'array e delega a BlockRenderer, che gestisce due forme di blocco:

  • Standard{ Component, slots, ...props }<Component slots={slots} {...props} />.
  • Section annidata{ section, container, content }<Section><Container>…content (ricorsivo)…</Container></Section>.

Per un singolo elemento (come un global) è possibile bypassare LayoutRenderer e renderizzare direttamente { Component, ...props }, come nella §4.1.


Riferimento rapido degli import

// Config Payload
import { uimsCollections, uimsGlobals, collectionPages, uimsSections,
         uimsFormOverrides, uimsFormSubmissionOverrides } from '@woodylab/payload/config'

// Motore UIMS
import { createUIMSContext, uimsResolvers } from '@woodylab/payload/uims'

// React
import { uimsComponents, LayoutRenderer, UIMSEventProvider, useUIMSEvents, uimsEventHandlers } from '@woodylab/payload/react'

// Utils
import { generateCssVars, getCachedGlobal } from '@woodylab/payload/utils'

// Seeders
import { syncStylesToDB, syncViewModesToDB, seedEvents, viewModeDefaults } from '@woodylab/payload/seeders'

// Manifest (default export)
import uimsManifest from '@woodylab/payload/uimsManifest'