@frankn11/flux-core
v1.0.4
Published
Flux Dashboard Framework — core engine
Maintainers
Readme
⚡ Flux Dashboard Framework
Build production-ready dashboards in minutes with a declarative, slot-based layout engine and pluggable widget system.
Construye dashboards listos para producción en minutos con un motor de layout declarativo basado en slots y un sistema de widgets enchufables.
📦 Packages / Paquetes
| Package | Description | Descripción | Version |
|---------|-------------|-------------|---------|
| @frankn11/flux-core | Core engine — schema, registry, slot parser | Motor — schema, registry, parser de slots | |
|
@frankn11/flux-widgets | UI widgets — MetricCard, LineChart, BarChart, DataTable | Widgets UI | |
|
@frankn11/flux-layout | Layout — Sidebar, Topbar, SlotGrid, useTheme | Componentes de layout | |
|
@frankn11/flux-auth | Auth — Login, Register, ForgotPassword + adapters | Auth con adaptadores de backend | |
🚀 Installation / Instalación
Full stack (recommended / recomendado)
# npm
npm install @frankn11/flux-core @frankn11/flux-widgets @frankn11/flux-layout @frankn11/flux-auth
# pnpm
pnpm add @frankn11/flux-core @frankn11/flux-widgets @frankn11/flux-layout @frankn11/flux-auth
# yarn
yarn add @frankn11/flux-core @frankn11/flux-widgets @frankn11/flux-layout @frankn11/flux-authÀ la carte
# Solo el motor / Just the core engine
pnpm add @frankn11/flux-core
# Motor + widgets / Core + widgets
pnpm add @frankn11/flux-core @frankn11/flux-widgets
# Dashboard completo sin auth / Full dashboard without auth
pnpm add @frankn11/flux-core @frankn11/flux-widgets @frankn11/flux-layout
# Stack completo con auth / Full stack with auth
pnpm add @frankn11/flux-core @frankn11/flux-widgets @frankn11/flux-layout @frankn11/flux-auth⚙️ Setup / Configuración
1. Vite + Tailwind
Install Tailwind / Instala Tailwind:
pnpm add -D tailwindcss @tailwindcss/vitevite.config.js:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss(), react()],
})2. CSS Variables
Add to your global CSS file / Agrega en tu archivo CSS global (src/index.css):
@import "tailwindcss";
/* Tell Tailwind to scan Flux packages / Decirle a Tailwind que escanee los paquetes */
@source "../node_modules/@frankn11/flux-auth/src";
@source "../node_modules/@frankn11/flux-widgets/src";
@source "../node_modules/@frankn11/flux-layout/src";
@layer base {
:root {
/* Dark theme (default) / Tema oscuro (por defecto) */
--bg: #0f1117;
--surface: #1a1d27;
--border: #2a2e42;
--text: #e2e8f0;
--muted: #64748b;
--accent: #6366f1;
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
/* Aliases required by Flux packages / Aliases requeridos por los paquetes Flux */
--flux-bg: var(--bg);
--flux-surface: var(--surface);
--flux-border: var(--border);
--flux-text: var(--text);
--flux-muted: var(--muted);
--flux-accent: var(--accent);
--flux-success: var(--success);
--flux-warning: var(--warning);
--flux-danger: var(--danger);
}
body {
background: var(--bg);
color: var(--text);
font-family: system-ui, sans-serif;
min-height: 100vh;
}
}
/* Responsive grid / Grid responsive */
@layer utilities {
.slot-grid-wrapper > div { min-width: 0; }
@media (max-width: 900px) {
.slot-grid-wrapper {
grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
}
.slot-grid-wrapper > div {
grid-column: auto !important;
grid-row: auto !important;
}
}
@media (max-width: 560px) {
.slot-grid-wrapper { grid-template-columns: 1fr !important; }
}
}🧩 Quick Start / Inicio rápido
import { useState } from 'react';
import { registerWidget } from '@frankn11/flux-core';
import { MetricCard, LineChart } from '@frankn11/flux-widgets';
import { SlotGrid } from '@frankn11/flux-layout';
import { Login, Register, ForgotPassword } from '@frankn11/flux-auth';
import Layout from './components/Layout.jsx';
// 1. Register widgets / Registrar widgets
registerWidget('metric', MetricCard);
registerWidget('line-chart', LineChart);
// 2. Define dashboard config / Definir config del dashboard
const WIDGETS = [
{ id: 'kpi-1', type: 'metric', slot: 'A1', props: { title: 'Users', value: '4,231', color: 'accent' } },
{ id: 'kpi-2', type: 'metric', slot: 'B1', props: { title: 'Revenue', value: '$18.5k', color: 'success' } },
{ id: 'kpi-3', type: 'metric', slot: 'C1', props: { title: 'Errors', value: '0.42%', color: 'danger' } },
{ id: 'kpi-4', type: 'metric', slot: 'D1', props: { title: 'Latency', value: '124ms', color: 'warning' } },
{ id: 'ch-1', type: 'line-chart', slot: 'A2:D2', props: { title: 'Revenue trend', data: [] } },
];
export default function App() {
const [auth, setAuth] = useState('login');
const [isLoggedIn, setLoggedIn] = useState(false);
// Auth flow / Flujo de auth
if (!isLoggedIn) {
if (auth === 'register') return <Register onNavigate={setAuth} onSuccess={() => setLoggedIn(true)} />;
if (auth === 'forgot') return <ForgotPassword onNavigate={setAuth} />;
return <Login onNavigate={setAuth} onSuccess={() => setLoggedIn(true)} />;
}
// Dashboard
return (
<Layout activePage="overview" onNavigate={() => {}}>
<SlotGrid cols={4} widgets={WIDGETS} />
</Layout>
);
}🔐 Auth Adapters / Adaptadores de Auth
Mock (no backend needed / sin backend)
// auth prop is optional — uses 1s mock delay
// el prop auth es opcional — usa 1s de delay mock
<Login onSuccess={() => setLoggedIn(true)} onNavigate={setPage} />Supabase
pnpm add @supabase/supabase-jsimport { Login } from '@frankn11/flux-auth';
import { useSupabaseAuth } from '@frankn11/flux-auth/supabase';
const auth = useSupabaseAuth({
url: 'https://your-project.supabase.co',
key: 'your-anon-key',
});
<Login auth={auth} onSuccess={() => navigate('/dashboard')} onNavigate={setPage} />Firebase
pnpm add firebaseimport { Login } from '@frankn11/flux-auth';
import { useFirebaseAuth } from '@frankn11/flux-auth/firebase';
import { initializeApp } from 'firebase/app';
const app = initializeApp({ /* firebase config */ });
const auth = useFirebaseAuth({ app });
<Login auth={auth} onSuccess={() => navigate('/dashboard')} onNavigate={setPage} />Custom API / API personalizada
import { Login } from '@frankn11/flux-auth';
import { useCustomAuth } from '@frankn11/flux-auth/custom';
const auth = useCustomAuth({
baseUrl: 'https://api.yourapp.com',
headers: { 'X-API-Key': 'your-key' },
// Expected endpoints / Endpoints esperados:
// POST /auth/login { email, password }
// POST /auth/register { email, password, name }
// POST /auth/forgot-password { email }
// POST /auth/logout
});
<Login auth={auth} onSuccess={() => navigate('/dashboard')} onNavigate={setPage} />📐 Slot System / Sistema de Slots
Describe widget positions with a spreadsheet-like notation. Describe la posición de widgets con notación tipo hoja de cálculo.
Columns / Columnas: A B C D
Row 1: [ A1 B1 C1 D1 ]
Row 2: [ A2 B2 C2 D2 ]
Row 3: [ A3 B3 C3 D3 ]| Slot | Result / Resultado |
|------|-------------------|
| "A1" | Column 1, Row 1 — 1×1 cell |
| "B1" | Column 2, Row 1 — 1×1 cell |
| "A1:B1" | Columns 1-2, Row 1 — 2×1 span |
| "A1:D1" | Columns 1-4, Row 1 — full width |
| "A2:B3" | Columns 1-2, Rows 2-3 — 2×2 block |
<SlotGrid cols={4} widgets={[
{ id: 'kpi-1', type: 'metric', slot: 'A1', props: { title: 'Users', value: '1,234' } },
{ id: 'kpi-2', type: 'metric', slot: 'B1', props: { title: 'Revenue', value: '$9k' } },
{ id: 'chart', type: 'line-chart', slot: 'A2:D2', props: { title: 'Trend', data: [] } },
]} />🎨 Widgets
MetricCard
<MetricCard
title="Active Users" // Label shown above / Etiqueta superior
value="4,231" // Main value / Valor principal
unit="k" // Optional unit suffix / Unidad opcional
trend={12.4} // % change (positive = up) / % cambio
trendLabel="vs yesterday"
color="accent" // accent | success | warning | danger
/>LineChart
<LineChart
title="Monthly Revenue"
data={[
{ label: 'Jan', revenue: 12400, users: 3100 },
{ label: 'Feb', revenue: 14800, users: 3400 },
]}
lines={[
{ key: 'revenue', label: 'Revenue', color: '#6366f1' },
{ key: 'users', label: 'Users', color: '#10b981' },
]}
/>BarChart
<BarChart
title="New vs Cancelled"
data={[{ label: 'Pro', new: 320, cancelled: 40 }]}
bars={[
{ key: 'new', label: 'New', color: '#6366f1' },
{ key: 'cancelled', label: 'Cancelled', color: '#ef4444' },
]}
stacked={false}
/>DataTable
<DataTable
title="Products"
columns={[
{ key: 'name', label: 'Product' },
{ key: 'revenue', label: 'Revenue', align: 'right', render: v => `$${v}` },
{ key: 'status', label: 'Status', sortable: false,
render: v => <span style={{ color: v === 'Active' ? '#10b981' : '#f59e0b' }}>{v}</span> },
]}
data={[
{ name: 'Pro Plan', revenue: 9200, status: 'Active' },
]}
/>🔧 Core API
registerWidget(type, component)
import { registerWidget } from '@frankn11/flux-core';
registerWidget('my-chart', MyChartComponent);getWidget(type)
import { getWidget } from '@frankn11/flux-core';
const Chart = getWidget('my-chart');parseSlot(slot)
import { parseSlot } from '@frankn11/flux-core';
parseSlot('A2:C3');
// { colStart: 1, rowStart: 2, colEnd: 3, rowEnd: 3 }validateConfig(config)
import { validateConfig } from '@frankn11/flux-core';
const { valid, errors } = validateConfig({ title: 'My Dashboard', layout: 'sidebar-top', widgets: [] });listWidgets()
import { listWidgets } from '@frankn11/flux-core';
listWidgets(); // ['metric', 'line-chart', 'bar-chart', 'data-table']🌗 Theme / Tema
import { useTheme } from '@frankn11/flux-layout';
const { theme, toggle } = useTheme();
// theme: 'dark' | 'light'
// toggle: switches and persists to localStorage
// toggle: cambia y persiste en localStorage📁 Repository / Repositorio
github.com/frankn11/flux-framework
📄 License / Licencia
MIT © frankn11
