feat: funciones frontend React/TS — componentes UI, hooks Wails, charts y tipos

Componentes React reutilizables: card, dialog, tabs, select, alert, badge, button, input, label,
skeleton, tooltip, progress_bar, page_header, form_field, settings_page, crud_page, analytics_page,
dashboard_layout. Charts: area, bar, line, sparkline, kpi_card, chart_container.
Hooks Wails: use_wails_query, use_wails_mutation, use_wails_stream, use_wails_event, use_animated_canvas.
Funciones core: cn, format_compact, chart_colors, get_series_color, wails_cache, theme_config_to_colors.
Tipos: chart_series, wails_ipc, theme_config, component_variants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-01 20:55:34 +02:00
parent e02a950ee0
commit dc78d8fea3
86 changed files with 5721 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
---
name: chart_colors
kind: function
lang: typescript
domain: core
version: "1.0.0"
purity: pure
signature: "getChartColor(index: number): string"
description: "Paleta de colores para gráficos basada en CSS variables del tema activo. Colores accesibles por índice cíclico."
tags: [chart, color, theme, palette, visualization]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "frontend/functions/core/chart_colors.ts"
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/Bl4cksmith/Frontend_Library"
source_license: "MIT"
source_file: "frontend/src/components/ui/charts/chart-base.tsx"
---
## Ejemplo
```typescript
getChartColor(0) // 'hsl(var(--chart-1, 220 70% 50%))'
getChartColor(7) // 'hsl(var(--chart-3, 30 80% 55%))' — cicla sobre 5 colores
```
## Notas
Usa CSS variables del tema con fallback hardcodeado. Los colores cambian automáticamente con el tema activo. También exporta `chartColors` (array) para uso directo.
+11
View File
@@ -0,0 +1,11 @@
export const chartColors = [
'hsl(var(--chart-1, 220 70% 50%))',
'hsl(var(--chart-2, 160 60% 45%))',
'hsl(var(--chart-3, 30 80% 55%))',
'hsl(var(--chart-4, 280 65% 60%))',
'hsl(var(--chart-5, 340 75% 55%))',
]
export function getChartColor(index: number): string {
return chartColors[index % chartColors.length]
}
+36
View File
@@ -0,0 +1,36 @@
---
name: cn
kind: function
lang: typescript
domain: core
version: "1.0.0"
purity: pure
signature: "cn(...inputs: ClassValue[]): string"
description: "Combina clases CSS con clsx y resuelve conflictos Tailwind con tailwind-merge. Utilidad fundamental para composición de estilos."
tags: [css, tailwind, classname, merge, utility]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: [clsx, tailwind-merge]
tested: false
tests: []
test_file_path: ""
file_path: "frontend/functions/core/cn.ts"
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/Bl4cksmith/Frontend_Library"
source_license: "MIT"
source_file: "frontend/src/lib/utils.ts"
---
## Ejemplo
```typescript
cn("px-4 py-2", "px-6") // "px-6 py-2" (tailwind-merge resuelve conflicto)
cn("text-red-500", false && "hidden") // "text-red-500" (clsx filtra falsy)
cn("rounded-lg", className) // composición con className externo
```
## Notas
Base de todo el sistema de estilos. Todos los componentes la usan para componer className.
+6
View File
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs))
}
+36
View File
@@ -0,0 +1,36 @@
---
name: format_compact
kind: function
lang: typescript
domain: core
version: "1.0.0"
purity: pure
signature: "formatCompact(n: number, decimals?: number): string"
description: "Familia de funciones de formato compacto: números (K/M/B), frecuencia (Hz/KHz/MHz), bytes (KB/MB/GB), duración (ms/s/min/h)."
tags: [format, number, compact, utility, display]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "frontend/functions/core/format_compact.ts"
---
## Ejemplo
```typescript
formatCompact(1234) // '1.2K'
formatCompact(1500000) // '1.5M'
formatHz(44100) // '44.1 KHz'
formatBytes(1073741824) // '1.0 GB'
formatDuration(3500) // '3.5s'
formatDuration(0.5) // '500µs'
```
## Notas
Todas son funciones puras sin dependencias. Útiles en dashboards, KPI cards, tablas y tooltips.
+42
View File
@@ -0,0 +1,42 @@
/**
* Formatea un número en formato compacto (1K, 1.2M, etc.)
* Soporta sufijos personalizados.
*/
export function formatCompact(n: number, decimals: number = 1): string {
if (Math.abs(n) >= 1_000_000_000) return (n / 1_000_000_000).toFixed(decimals) + 'B'
if (Math.abs(n) >= 1_000_000) return (n / 1_000_000).toFixed(decimals) + 'M'
if (Math.abs(n) >= 1_000) return (n / 1_000).toFixed(decimals) + 'K'
return n.toString()
}
/**
* Formatea frecuencia en Hz/KHz/MHz/GHz.
*/
export function formatHz(hz: number, decimals: number = 1): string {
if (hz >= 1_000_000_000) return (hz / 1_000_000_000).toFixed(decimals) + ' GHz'
if (hz >= 1_000_000) return (hz / 1_000_000).toFixed(decimals) + ' MHz'
if (hz >= 1_000) return (hz / 1_000).toFixed(decimals) + ' KHz'
return hz + ' Hz'
}
/**
* Formatea bytes en KB/MB/GB/TB.
*/
export function formatBytes(bytes: number, decimals: number = 1): string {
if (bytes >= 1_099_511_627_776) return (bytes / 1_099_511_627_776).toFixed(decimals) + ' TB'
if (bytes >= 1_073_741_824) return (bytes / 1_073_741_824).toFixed(decimals) + ' GB'
if (bytes >= 1_048_576) return (bytes / 1_048_576).toFixed(decimals) + ' MB'
if (bytes >= 1_024) return (bytes / 1_024).toFixed(decimals) + ' KB'
return bytes + ' B'
}
/**
* Formatea duración en ms/s/min/h.
*/
export function formatDuration(ms: number): string {
if (ms >= 3_600_000) return (ms / 3_600_000).toFixed(1) + 'h'
if (ms >= 60_000) return (ms / 60_000).toFixed(1) + 'min'
if (ms >= 1_000) return (ms / 1_000).toFixed(1) + 's'
if (ms >= 1) return ms.toFixed(0) + 'ms'
return (ms * 1000).toFixed(0) + 'µs'
}
@@ -0,0 +1,36 @@
---
name: get_series_color
kind: function
lang: typescript
domain: core
version: "1.0.0"
purity: pure
signature: "getSeriesColor(index: number, color?: string): string"
description: "Devuelve color para una serie de gráfico por índice cíclico, o el color explícito si se proporciona."
tags: [chart, color, series, visualization]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "frontend/functions/core/get_series_color.ts"
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/Bl4cksmith/Frontend_Library"
source_license: "MIT"
source_file: "frontend/src/components/ui/charts/chart-base.tsx"
---
## Ejemplo
```typescript
getSeriesColor(0) // '#3b82f6'
getSeriesColor(5) // '#3b82f6' (cicla sobre 5 colores)
getSeriesColor(0, '#ff0000') // '#ff0000' (usa el explícito)
```
## Notas
Paleta fija de 5 colores: azul, verde, ámbar, violeta, rosa. También exporta `defaultColors` para uso directo.
@@ -0,0 +1,7 @@
const defaultColors = ['#3b82f6', '#22c55e', '#f59e0b', '#8b5cf6', '#ec4899']
export function getSeriesColor(index: number, color?: string): string {
return color || defaultColors[index % defaultColors.length]
}
export { defaultColors }
@@ -0,0 +1,37 @@
---
name: theme_config_to_colors
kind: function
lang: typescript
domain: core
version: "1.0.0"
purity: pure
signature: "themeConfigToColors(config: ThemeConfig): ThemeColors"
description: "Convierte un ThemeConfig completo a ThemeColors plano para inyectar como CSS variables. Mapea tokens semánticos a variables CSS."
tags: [theme, colors, css-variables, conversion]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "frontend/functions/core/theme_config_to_colors.ts"
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/Bl4cksmith/Frontend_Library"
source_license: "MIT"
source_file: "frontend/src/themes/types.ts"
---
## Ejemplo
```typescript
const colors = themeConfigToColors(darkThemeConfig)
// { background: '...', foreground: '...', primary: '...', ... }
```
## Notas
Puente entre el sistema de temas estructurado (ThemeConfig) y el sistema plano de CSS variables que consumen los componentes.
Depende de los tipos ThemeConfig y ThemeColors definidos en `frontend/types/ui/theme_config.ts`. El tipo aún no está indexado en la BD (pendiente añadir theme_config.md para que fn index lo registre).
@@ -0,0 +1,49 @@
import type { ThemeConfig, ThemeColors } from "../../types/ui/theme_config"
export function themeConfigToColors(config: ThemeConfig): ThemeColors {
const { colors } = config
return {
background: colors.background.default,
foreground: colors.foreground.default,
card: colors.surface.raised,
cardForeground: colors.foreground.default,
popover: colors.surface.overlay,
popoverForeground: colors.foreground.default,
primary: colors.brand.primary,
primaryForeground: colors.brand.primaryForeground,
secondary: colors.brand.secondary,
secondaryForeground: colors.brand.secondaryForeground,
muted: colors.background.muted,
mutedForeground: colors.foreground.muted,
accent: colors.brand.accent,
accentForeground: colors.brand.accentForeground,
destructive: colors.status.error,
destructiveForeground: colors.status.errorForeground,
success: colors.status.success,
successForeground: colors.status.successForeground,
warning: colors.status.warning,
warningForeground: colors.status.warningForeground,
info: colors.status.info,
infoForeground: colors.status.infoForeground,
surface: colors.surface.raised,
surfaceHover: colors.background.subtle,
overlay: colors.surface.overlay,
border: colors.border.default,
input: colors.border.default,
ring: colors.ring,
chart1: colors.chart[1],
chart2: colors.chart[2],
chart3: colors.chart[3],
chart4: colors.chart[4],
chart5: colors.chart[5],
sidebar: colors.sidebar.background,
sidebarForeground: colors.sidebar.foreground,
sidebarPrimary: colors.brand.primary,
sidebarPrimaryForeground: colors.brand.primaryForeground,
sidebarAccent: colors.sidebar.accent,
sidebarAccentForeground: colors.sidebar.accentForeground,
sidebarBorder: colors.sidebar.border,
sidebarRing: colors.sidebar.ring,
}
}
+39
View File
@@ -0,0 +1,39 @@
---
name: wails_cache
kind: function
lang: typescript
domain: core
version: "1.0.0"
purity: pure
signature: "class WailsCache { get<T>(key: string[]): T | null; set<T>(key: string[], data: T): void; invalidate(key: string[]): void; subscribe(key: string[], cb: () => void): () => void }"
description: "Cache reactivo para IPC Wails con invalidación por prefijo, suscripción a cambios y tracking de staleness. Singleton global."
tags: [wails, cache, ipc, reactive, state]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
tested: false
tests: []
test_file_path: ""
file_path: "frontend/functions/core/wails_cache.ts"
source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/Bl4cksmith/Frontend_Library"
source_license: "MIT"
source_file: "frontend/src/lib/wails/cache.ts"
---
## Ejemplo
```typescript
import { wailsCache } from './wails_cache'
wailsCache.set(['users', '123'], userData)
const user = wailsCache.get<User>(['users', '123'])
wailsCache.invalidate(['users']) // invalida users:*
const unsub = wailsCache.subscribe(['users'], () => console.log('changed'))
```
## Notas
Key como string[] permite invalidación jerárquica: `invalidate(['users'])` invalida `users`, `users:123`, `users:456`, etc.
+99
View File
@@ -0,0 +1,99 @@
interface CacheEntry {
data: unknown
timestamp: Date
}
export class WailsCache {
private cache = new Map<string, CacheEntry>()
private subscribers = new Map<string, Set<() => void>>()
/** Generar key string desde array */
private getKey(queryKey: string[]): string {
return queryKey.join(':')
}
/** Obtener dato del cache */
get<T>(queryKey: string[]): T | null {
const entry = this.cache.get(this.getKey(queryKey))
return (entry?.data as T) ?? null
}
/** Guardar dato en cache */
set<T>(queryKey: string[], data: T): void {
const key = this.getKey(queryKey)
this.cache.set(key, { data, timestamp: new Date() })
this.notifySubscribers(key)
}
/** Verificar si existe en cache */
has(queryKey: string[]): boolean {
return this.cache.has(this.getKey(queryKey))
}
/** Obtener timestamp de última actualización */
getTimestamp(queryKey: string[]): Date | null {
const entry = this.cache.get(this.getKey(queryKey))
return entry?.timestamp ?? null
}
/** Verificar si los datos están stale */
isStale(queryKey: string[], staleTime: number): boolean {
const entry = this.cache.get(this.getKey(queryKey))
if (!entry) return true
return Date.now() - entry.timestamp.getTime() > staleTime
}
/** Invalidar cache (esta key y todas las que empiezan igual) */
invalidate(queryKey: string[]): void {
const prefix = this.getKey(queryKey)
const keysToDelete: string[] = []
for (const key of this.cache.keys()) {
if (key === prefix || key.startsWith(prefix + ':')) {
keysToDelete.push(key)
}
}
keysToDelete.forEach((key) => {
this.cache.delete(key)
this.notifySubscribers(key)
})
}
/** Limpiar todo el cache */
clear(): void {
this.cache.clear()
this.subscribers.forEach((_, key) => this.notifySubscribers(key))
}
/** Subscribirse a cambios en una key */
subscribe(queryKey: string[], callback: () => void): () => void {
const key = this.getKey(queryKey)
if (!this.subscribers.has(key)) {
this.subscribers.set(key, new Set())
}
this.subscribers.get(key)!.add(callback)
return () => {
this.subscribers.get(key)?.delete(callback)
}
}
/** Notificar a subscribers */
private notifySubscribers(key: string): void {
this.subscribers.get(key)?.forEach((callback) => callback())
}
/** Obtener tamaño del cache */
get size(): number {
return this.cache.size
}
/** Obtener todas las keys */
keys(): string[] {
return Array.from(this.cache.keys())
}
}
// Singleton global
export const wailsCache = new WailsCache()