pgx-theme-resolver
v1.0.0
Published
Dynamic theme resolver library for PGX projects. Converts API theme tokens into CSS custom properties with a 3-layer architecture (primitives, semantic, Tailwind).
Maintainers
Readme
pgx-theme-resolver
Biblioteca dinâmica de resolução de temas para projetos PGX. Converte tokens de tema vindos de API ou JSON em CSS custom properties com arquitetura de 3 camadas (primitivos, semânticos, Tailwind).
Arquitetura
┌─────────────────────────────────────────────────────────────┐
│ API / JSON │
│ { "Colors": { "CTA Colors": { "50": { hex: "#f5f4fe" } }} │
└──────────────────────────┬──────────────────────────────────┘
│
normalizePrimitiveTokens()
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Camada 1: CSS Vars Primitivos │
│ --cta-50: #f5f4fe --brand-950: #0b0b0b │
│ --cta-op-50: rgba(245,244,254,0.5) │
└──────────────────────────┬──────────────────────────────────┘
│
referenciados via var()
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Camada 2: CSS Vars Semânticos │
│ --bg-page: var(--brand-950) │
│ --text-primary: var(--brand-50) │
│ --action-primary: var(--cta-600) │
└──────────────────────────┬──────────────────────────────────┘
│
@theme inline / config
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Camada 3: Tailwind Utilities │
│ bg-bg-page text-text-primary border-action-primary │
└─────────────────────────────────────────────────────────────┘Quando os primitivos mudam (via API), os semânticos atualizam automaticamente pela cascata CSS.
Instalação
# Via npm/pnpm/yarn
pnpm add pgx-theme-resolver
# Ou link local (desenvolvimento)
cd pgx-theme-resolver && pnpm install && pnpm build
cd ../seu-projeto && pnpm add ../pgx-theme-resolverCLI — Geração de arquivos estáticos
Para projetos que preferem ter os arquivos de tema gerados estaticamente (sem resolução dinâmica), a lib oferece dois comandos CLI via npx:
npx pgx-theme generate-styles
Gera um arquivo CSS completo com :root vars (primitivos + semânticos + estáticos + fades + shadows). Se Tailwind CSS 4 for detectado no projeto (ou --tw4 for passado), inclui automaticamente o bloco @theme inline com os mapeamentos de cores.
# Gera src/styles/pgx-theme.css (detecta Tailwind automaticamente)
npx pgx-theme generate-styles
# Força Tailwind 4 (@theme inline incluído)
npx pgx-theme generate-styles --tw4
# Gera apenas :root vars, sem Tailwind
npx pgx-theme generate-styles --no-tailwind
# Output customizado
npx pgx-theme generate-styles --out=src/theme/variables.cssArquivo gerado (com --tw4):
/* pgx-theme-resolver — Generated CSS */
:root {
/* ── Primitivos ── */
--cta-50: #f5f4fe;
--cta-600: #754dda;
--brand-950: #0b0b0b;
/* ... 60+ primitivos ... */
/* ── Semânticos ── */
--bg-page: var(--brand-950);
--text-primary: var(--brand-50);
--action-primary: var(--cta-600);
/* ... 50+ semânticos ... */
/* ── Fades / Shadows ── */
--fade-primary-default: linear-gradient(180deg, var(--cta-600) 0%, var(--cta-500) 100%);
--shadow-primary: 0px 10px 15px var(--cta-op-700);
/* ... */
}
/* ── Tailwind CSS 4 ── */
@theme inline {
--color-bg-page: var(--bg-page);
--color-text-primary: var(--text-primary);
--color-cta-600: var(--cta-600);
/* ... 120+ cores utilitárias ... */
}Depois basta importar no seu globals.css:
@import "tailwindcss";
@import "./pgx-theme.css";npx pgx-theme generate-tw-config
Gera um arquivo TypeScript (ou JavaScript) com o objeto de cores para Tailwind CSS 3. Detecta automaticamente se o projeto usa TS ou JS.
# Gera config/tailwind-theme-colors-config.ts (ou .js)
npx pgx-theme generate-tw-config
# Força JavaScript
npx pgx-theme generate-tw-config --js
# Output customizado
npx pgx-theme generate-tw-config --out=src/config/theme-colors.tsArquivo gerado (TypeScript):
/* pgx-theme-resolver — Tailwind CSS 3 Color Config */
export const themeColors = {
/* ── bg ── */
"bg-page": "var(--bg-page)",
"bg-surface-primary": "var(--bg-surface-primary)",
/* ── text ── */
"text-primary": "var(--text-primary)",
/* ── action ── */
"action-primary": "var(--action-primary)",
/* ── cta (primitivos) ── */
"cta-50": "var(--cta-50)",
"cta-600": "var(--cta-600)",
/* ... 122 cores no total ... */
} as const;
export type ThemeColorKey = keyof typeof themeColors;Uso no tailwind.config.ts:
import { themeColors } from "./config/tailwind-theme-colors-config";
export default {
theme: {
extend: {
colors: themeColors,
},
},
};Flags da CLI
| Comando | Flag | Descrição |
|---------|------|-----------|
| generate-styles | --out=<path> | Caminho do output (default: src/styles/pgx-theme.css) |
| | --tw4 | Força inclusão do @theme inline |
| | --tw3 | Não inclui @theme inline |
| | --no-tailwind | Gera apenas :root vars |
| generate-tw-config | --out=<path> | Caminho do output (default: config/tailwind-theme-colors-config.ts) |
| | --ts | Força TypeScript |
| | --js | Força JavaScript |
Quick Start
1. Básico — Aplicar tema da API no DOM
import {
resolveThemeFromApi,
applyTheme,
} from "pgx-theme-resolver";
// Dados vindos da API (ou mock)
const apiData = {
Colors: {
"CTA Colors": {
"50": { hex: "#f5f4fe" },
"100": { hex: "#edebfc" },
"600": { hex: "#754dda" },
"900": { hex: "#472a88" },
},
"Brand Colors": {
"50": { hex: "#fafafa" },
"900": { hex: "#141414" },
"950": { hex: "#0b0b0b" },
},
},
"Opacity Colors": {
"CTA Colors": {
"600": { hex: "#754dda", alpha: 0.5 },
},
},
};
// Resolve → aplica
const vars = resolveThemeFromApi(apiData);
applyTheme(vars);2. SSR (Next.js App Router)
// app/layout.tsx
import {
fetchAndResolveTheme,
getDefaultTheme,
} from "pgx-theme-resolver";
import { ThemeStyle } from "pgx-theme-resolver/react";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const vars =
(await fetchAndResolveTheme({
url: `${process.env.API_URL}/themes`,
})) ?? getDefaultTheme();
return (
<html>
<head>
<ThemeStyle vars={vars} id="pgx-theme" />
</head>
<body>{children}</body>
</html>
);
}3. React Hook (Client-side)
import { useTheme } from "pgx-theme-resolver/react";
function ThemeLoader() {
const { setTheme } = useTheme();
useEffect(() => {
fetch("/api/themes")
.then((r) => r.json())
.then((data) => setTheme(data.value));
}, [setTheme]);
return null;
}4. Gerar <style> tag como string (SSR genérico)
import {
resolveThemeFromApi,
buildStyleTag,
} from "pgx-theme-resolver";
const vars = resolveThemeFromApi(apiData);
const css = buildStyleTag(vars);
// → ":root{--cta-50:#f5f4fe;--brand-950:#0b0b0b;...}"
// Injetar no HTML:
const html = `<html><head><style>${css}</style></head>...</html>`;5. Formato legado/simplificado (JSON plano)
import { resolveThemeFromJson, applyTheme } from "pgx-theme-resolver";
const json = {
bg: { page: "#080816", surfacePrimary: "#1F1F40" },
text: { primary: "#E6E6F8" },
action: { primary: "#0062FF" },
};
const vars = resolveThemeFromJson(json);
applyTheme(vars);6. Resposta completa da API
import {
resolveThemeFromResponse,
applyTheme,
} from "pgx-theme-resolver";
const response = await fetch("/api/themes").then((r) => r.json());
// response = { id, name, active, value: {...}, ... }
const vars = resolveThemeFromResponse(response);
applyTheme(vars);Integração com Tailwind CSS
Tailwind CSS 4 (@theme inline)
import { generateTailwindThemeBlock } from "pgx-theme-resolver";
const block = generateTailwindThemeBlock();
console.log(block);Cole o output no seu globals.css:
@import "tailwindcss";
/* ... seus :root vars ... */
@theme inline {
/* output do generateTailwindThemeBlock() */
--color-bg-page: var(--bg-page);
--color-text-primary: var(--text-primary);
--color-action-primary: var(--action-primary);
--color-cta-600: var(--cta-600);
/* ... */
}Agora você pode usar: bg-bg-page, text-text-primary, bg-cta-600, etc.
Tailwind CSS 3 (tailwind.config.js)
const { generateTailwindV3Colors } = require("pgx-theme-resolver");
module.exports = {
theme: {
extend: {
colors: generateTailwindV3Colors(),
},
},
};API Reference
Resolvers
| Função | Input | Descrição |
|--------|-------|-----------|
| resolveThemeFromApi(value, options?) | ThemeApiValue | Resolve tokens do formato da API PGX |
| resolveThemeFromResponse(response, options?) | ThemeApiResponse | Resolve a partir da resposta completa do endpoint |
| resolveThemeFromJson(json, options?) | ThemeJson | Resolve a partir do formato legado/simplificado |
| getDefaultTheme(overrides?) | — | Retorna tema padrão (fallback) |
DOM
| Função | Descrição |
|--------|-----------|
| applyTheme(vars, options?) | Aplica CSS vars via style.setProperty() |
| clearTheme(target?) | Remove todas as CSS vars inline |
| buildStyleTag(vars, selector?) | Gera string CSS (:root{...}) |
| buildStyleElement(vars, id?, selector?) | Cria <style> element |
Fetch
| Função | Descrição |
|--------|-----------|
| fetchAndResolveTheme(options) | Busca + resolve em uma chamada |
| fetchThemeValue(options) | Busca apenas o ThemeApiValue bruto |
Normalize (low-level)
| Função | Descrição |
|--------|-----------|
| normalizePrimitiveTokens(json, groupMap?) | Converte formato API → CSS vars |
| normalizeThemeJson(json) | Converte formato legado → CSS vars |
Tailwind
| Função | Descrição |
|--------|-----------|
| generateTailwindThemeBlock(extra?) | Gera @theme inline { ... } para TW4 |
| generateTailwindV3Colors() | Gera objeto de cores para TW3 config |
React (pgx-theme-resolver/react)
| Export | Tipo | Descrição |
|--------|------|-----------|
| useTheme(options?) | Hook | Resolve + aplica temas dinamicamente |
| ThemeStyle | Component | <style> tag para SSR |
CLI
| Comando | Descrição |
|---------|-----------|
| npx pgx-theme generate-styles | Gera CSS com :root + @theme inline (TW4) |
| npx pgx-theme generate-tw-config | Gera arquivo de cores para tailwind.config (TW3) |
Constantes exportadas
| Constante | Descrição |
|-----------|-----------|
| DEFAULT_GROUP_MAP | Mapeamento de grupos API → prefixos CSS |
| DEFAULT_SEMANTIC_TOKENS | Tokens semânticos padrão (camada 2) |
| DEFAULT_PRIMITIVES | Cores primitivas padrão (fallback) |
| STATIC_TOKENS | Tokens estáticos (gamification) |
| TAILWIND_COLOR_MAP | Mapeamento semântico → Tailwind |
Customização
Mapeamento de grupos customizado
Se a sua API usa nomes de grupo diferentes:
const vars = resolveThemeFromApi(apiData, {
groupMap: {
"Primary Colors": "cta", // mapeia "Primary Colors" → --cta-*
"Neutral Colors": "brand", // mapeia "Neutral Colors" → --brand-*
},
});Tokens semânticos customizados
Sobrescreva ou adicione tokens semânticos:
const vars = resolveThemeFromApi(apiData, {
semanticTokens: {
"--bg-page": "var(--cta-1100)", // sobrescreve
"--bg-custom": "var(--brand-800)", // adiciona novo
},
});Aplicar em elemento específico
applyTheme(vars, {
target: document.getElementById("my-app")!,
cleanPrevious: true,
});Formato dos dados da API
Formato principal (ThemeApiValue)
{
"Colors": {
"CTA Colors": {
"50": { "hex": "#f5f4fe" },
"100": { "hex": "#edebfc" },
"600": { "hex": "#754dda" },
"900": { "hex": "#472a88" }
},
"Brand Colors": {
"50": { "hex": "#fafafa" },
"950": { "hex": "#0b0b0b" }
},
"Green": {
"400": { "hex": "#10b981" }
}
},
"Opacity Colors": {
"CTA Colors": {
"600": { "hex": "#754dda", "alpha": 0.5 }
}
}
}Formato legado (ThemeJson)
{
"bg": {
"page": "#080816",
"surfacePrimary": "#1F1F40"
},
"text": {
"primary": "#E6E6F8"
},
"action": {
"primary": "#0062FF"
}
}Resposta completa (ThemeApiResponse)
{
"id": "uuid",
"name": "dark-purple",
"active": true,
"allowedUsers": null,
"value": { /* ThemeApiValue */ },
"createdAt": "2024-01-01T00:00:00Z",
"updatedAt": "2024-01-01T00:00:00Z"
}Tokens disponíveis
Primitivos (Camada 1)
| Grupo | Shades | Exemplo |
|-------|--------|---------|
| CTA | 50-1100 (14 shades) | --cta-600: #754dda |
| Brand | 50-950 (11 shades) | --brand-950: #0b0b0b |
| Green | 50, 200, 400, 800 | --green-400: #10b981 |
| Yellow | 100, 300, 500, 800 | --yellow-500: #ffc801 |
| Special | 100, 300, 500, 800 | --special-300: #fcbb4d |
| Red | 100, 300, 500, 800 | --red-500: #ef4444 |
Cada grupo tem variantes de opacidade: --cta-op-600, --brand-op-800, etc.
Semânticos (Camada 2)
| Categoria | Tokens |
|-----------|--------|
| Background | --bg-page, --bg-surface-primary, --bg-surface-secondary, --bg-on-surface-primary, --bg-border-primary, --bg-border-secondary, --bg-page-border |
| Text | --text-primary, --text-secondary, --text-tertiary, --text-inverse, --text-disabled, --text-special |
| Icon | --icon-primary, --icon-secondary, --icon-disabled, --icon-special |
| Success | --semantic-success, -container, -on-container, -border-container |
| Warning | --semantic-warning, -container, -on-container, -border-container |
| Error | --semantic-error, -container, -on-container, -border-container |
| Info | --semantic-info, -container, -on-container, -border-container |
| Action Primary | --action-primary, -on-primary, -hover, -pressed, -disabled, -border |
| Action Secondary | --action-secondary, -active, -on-secondary, -hover, -pressed, -disabled, -border |
| Action Special | --action-special, -on-special, -hover, -pressed, -border |
| Input | --input-rest-primary, -rest-secondary, -hover, -active, -border-*, -on-container-* |
Estáticos
| Categoria | Tokens |
|-----------|--------|
| Gamification Bronze | --gamification-bronze, -container, -border-container, -on-container |
| Gamification Prata | --gamification-prata, -container, -border-container, -on-container |
| Gamification Ouro | --gamification-ouro, -container, -border-container, -on-container |
Uso com Mock (desenvolvimento)
import {
getDefaultTheme,
applyTheme,
DEFAULT_PRIMITIVES,
} from "pgx-theme-resolver";
// Usa o tema padrão completo
applyTheme(getDefaultTheme());
// Ou sobrescreve apenas alguns primitivos
applyTheme({
...getDefaultTheme(),
"--cta-600": "#ff0000",
"--brand-950": "#000033",
});TypeScript
Todos os tipos são exportados:
import type {
ThemeApiValue,
ThemeApiResponse,
ThemeJson,
CssVarMap,
SemanticTokenMap,
GroupMap,
ThemeResolverOptions,
FetchThemeOptions,
} from "pgx-theme-resolver";Playground
O projeto inclui um playground interativo com Vite para testar os 4 temas mock em tempo real, com 3 variantes de switcher (select, tabs, radio) e preview visual de todos os tokens.
pnpm install
pnpm playground
# → http://localhost:5173O playground mostra:
- Select — dropdown para trocar de tema
- Switcher (Tabs) — botões side-by-side com highlight no ativo
- Radio Buttons — com nome e descrição de cada tema
- Theme Preview — textos, backgrounds, paletas CTA/Brand, botões de ação, estados semânticos, gradients, shadows, inputs, icons e gamification
Todas as cores atualizam em tempo real ao trocar de tema.
Build
pnpm install
pnpm build # Gera dist/ (CJS + ESM + types + CLI)
pnpm typecheck # Verifica tipos
pnpm test # Executa testes
pnpm playground # Abre playground interativo (Vite)License
MIT
