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:
@@ -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.
|
||||
@@ -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]
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user