diff --git a/frontend/functions/ui/bar_chart.md b/frontend/functions/ui/bar_chart.md index 0857994f..5496e582 100644 --- a/frontend/functions/ui/bar_chart.md +++ b/frontend/functions/ui/bar_chart.md @@ -3,7 +3,7 @@ name: bar_chart kind: component lang: typescript domain: ui -version: "1.0.0" +version: "1.1.0" purity: impure signature: "BarChart(props: BarChartProps): JSX.Element" description: "Gráfico de barras Recharts con multi-series, orientación horizontal/vertical, tooltips temáticos y bordes redondeados." @@ -50,3 +50,7 @@ source_file: "frontend/src/components/ui/charts/bar-chart.tsx" ``` + +## Notas + +En modo `horizontal=true`: el layout de Recharts es `'vertical'`, YAxis recibe `dataKey={xKey}` con `type="category"` (categorías en eje Y), XAxis recibe `type="number"` (valores en eje X). El radius de las barras se ajusta a `[0, 4, 4, 0]` para redondear la punta derecha. Este intercambio de ejes es obligatorio — sin él las barras horizontales no se renderizan. diff --git a/frontend/functions/ui/bar_chart.tsx b/frontend/functions/ui/bar_chart.tsx index 2a37f122..24cfc219 100644 --- a/frontend/functions/ui/bar_chart.tsx +++ b/frontend/functions/ui/bar_chart.tsx @@ -43,7 +43,7 @@ function BarChartComponent({ )} } cursor={{ fill: 'hsl(var(--muted) / 0.3)' }} /> {showLegend && } - {bars.map((bar) => )} + {bars.map((bar) => )} ) diff --git a/frontend/functions/ui/card.md b/frontend/functions/ui/card.md index 7de9a260..28c6c774 100644 --- a/frontend/functions/ui/card.md +++ b/frontend/functions/ui/card.md @@ -3,11 +3,11 @@ name: card kind: component lang: ts domain: ui -version: "1.0.0" +version: "1.1.0" purity: impure -signature: "Card(props: { size?: 'default' | 'sm'; className?: string; children: ReactNode }): JSX.Element" -description: "Contenedor card con header, title, description, action, content y footer. Sistema de slots composable." -tags: [card, container, layout, component, ui] +signature: "Card(props: { size?: 'default' | 'sm'; variant?: 'default' | 'borderless' | 'ghost'; className?: string; children: ReactNode }): JSX.Element" +description: "Contenedor card con header, title, description, action, content y footer. Sistema de slots composable. Variantes default, borderless y ghost para dashboards dark." +tags: [card, container, layout, component, ui, dashboard, dark] uses_functions: [cn_typescript_core] uses_types: [] returns: [] @@ -23,6 +23,10 @@ props: type: "'default' | 'sm'" required: false description: "Tamaño del card" + - name: variant + type: "'default' | 'borderless' | 'ghost'" + required: false + description: "Variante visual. borderless quita borde/shadow, ghost además hace bg transparente" - name: className type: "string" required: false @@ -30,7 +34,7 @@ props: emits: [] has_state: false framework: react -variant: [default, sm] +variant: [default, sm, borderless, ghost] source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/Bl4cksmith/Frontend_Library" source_license: "MIT" source_file: "frontend/src/components/ui/card.tsx" @@ -47,8 +51,20 @@ source_file: "frontend/src/components/ui/card.tsx" Contenido Footer + +{/* Dashboard dark — sin bordes */} + + Widget sin marco + + +{/* Completamente transparente */} + + Sin fondo ni borde + ``` ## Notas Sistema de slots via data-slot attributes. Card detecta automáticamente la presencia de CardFooter y ajusta el padding. Exporta 7 subcomponentes composables. + +Las variantes `borderless` y `ghost` eliminan el `ring-1` del borde por defecto. `ghost` además hace el fondo transparente. Alternativa con CSS global: `[data-slot="card"] { --tw-ring-opacity: 0; }` o `[data-variant="borderless"] { ring: 0 }` via `data-variant` attribute expuesto. diff --git a/frontend/functions/ui/card.tsx b/frontend/functions/ui/card.tsx index 7f5a24df..ec1261f4 100644 --- a/frontend/functions/ui/card.tsx +++ b/frontend/functions/ui/card.tsx @@ -1,17 +1,24 @@ import * as React from "react" import { cn } from "../core/cn" +type CardVariant = "default" | "borderless" | "ghost" + function Card({ className, size = "default", + variant = "default", ...props -}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) { +}: React.ComponentProps<"div"> & { size?: "default" | "sm"; variant?: CardVariant }) { return (
img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", + "group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", + variant === "default" && "ring-1 ring-foreground/10", + variant === "borderless" && "ring-0 shadow-none", + variant === "ghost" && "ring-0 shadow-none bg-transparent", className )} {...props} diff --git a/frontend/functions/ui/kpi_card.md b/frontend/functions/ui/kpi_card.md index b22b2965..9b8a37b1 100644 --- a/frontend/functions/ui/kpi_card.md +++ b/frontend/functions/ui/kpi_card.md @@ -3,11 +3,11 @@ name: kpi_card kind: component lang: typescript domain: ui -version: "1.0.0" +version: "2.0.0" purity: impure signature: "KPICard(props: KPICardProps): JSX.Element" -description: "Card de KPI con label, valor, delta porcentual con color semántico, icono y subtítulo. 3 tamaños." -tags: [kpi, card, metrics, dashboard, component, ui] +description: "Card de KPI con label, valor+unidad, delta descriptivo con color semántico, icono, slot de chart inline y action. 3 tamaños." +tags: [kpi, card, metrics, dashboard, component, ui, sparkline] uses_functions: [cn_typescript_core] uses_types: [] returns: [] @@ -27,14 +27,26 @@ props: type: "string | number" required: true description: "Valor principal" - - name: delta - type: "{ value: number; isPositive: boolean }" + - name: unit + type: "string" required: false - description: "Cambio porcentual con dirección" + description: "Unidad junto al valor en font menor (ej: k, ms, %)" + - name: delta + type: "{ value: number; isPositive: boolean; label?: string; suffix?: string }" + required: false + description: "Cambio con dirección, label descriptivo y sufijo" - name: icon type: "ReactNode" required: false - description: "Icono decorativo" + description: "Icono a la izquierda del label" + - name: action + type: "ReactNode" + required: false + description: "Slot top-right para menú o acciones" + - name: chart + type: "ReactNode" + required: false + description: "Slot para mini chart inline junto al valor" - name: size type: "'sm' | 'default' | 'lg'" required: false @@ -51,6 +63,31 @@ source_file: "frontend/src/components/ui/kpi-card.tsx" ## Ejemplo ```tsx +import { KPICard, Sparkline } from '@anthropic/frontend-lib' + +{/* Básico */} -} /> + +{/* Con unidad separada, delta descriptivo, y mini barras */} +} + delta={{ value: 15, isPositive: true, label: "Prompts Increased by", suffix: "vs yesterday" }} + chart={} + action={} +/> + +{/* Dashboard dark sin bordes */} + ``` + +## Notas + +- El icono ahora se renderiza a la **izquierda** del label (antes estaba a la derecha). +- `unit` separa la unidad del valor con font menor para el efecto "124 k" del diseño. +- `delta.label` y `delta.suffix` permiten texto descriptivo: "Increased by ▲ +15% vs yesterday". +- `chart` es un slot genérico — pasar un `` para mini barras multicolor. +- `action` es un slot top-right para menú contextual. +- Usa `cn()` para merge de clases. `className="border-0 shadow-none"` para dashboards dark. diff --git a/frontend/functions/ui/kpi_card.tsx b/frontend/functions/ui/kpi_card.tsx index d45a50b8..4d774c55 100644 --- a/frontend/functions/ui/kpi_card.tsx +++ b/frontend/functions/ui/kpi_card.tsx @@ -6,25 +6,35 @@ type KPICardSize = 'sm' | 'default' | 'lg' interface Delta { value: number isPositive: boolean + /** Descriptive label before value, e.g. "Increased by" */ + label?: string + /** Suffix after value, e.g. "vs yesterday" */ + suffix?: string } interface KPICardProps extends React.HTMLAttributes { label: string value: string | number + /** Unit displayed next to value in smaller font, e.g. "k", "ms", "%" */ + unit?: string delta?: Delta icon?: React.ReactNode + /** Action slot rendered top-right, e.g. a menu button */ + action?: React.ReactNode + /** Inline chart slot rendered to the right of the value */ + chart?: React.ReactNode subtitle?: string size?: KPICardSize } -const sizeStyles: Record = { - sm: { value: 'text-2xl font-bold', label: 'text-xs' }, - default: { value: 'text-3xl font-bold', label: 'text-sm' }, - lg: { value: 'text-4xl font-bold', label: 'text-base' }, +const sizeStyles: Record = { + sm: { value: 'text-2xl font-bold', unit: 'text-base font-medium', label: 'text-xs' }, + default: { value: 'text-3xl font-bold', unit: 'text-lg font-medium', label: 'text-sm' }, + lg: { value: 'text-4xl font-bold', unit: 'text-xl font-medium', label: 'text-base' }, } const KPICard = React.forwardRef( - ({ label, value, delta, icon, subtitle, size = 'default', className, ...props }, ref) => { + ({ label, value, unit, delta, icon, action, chart, subtitle, size = 'default', className, ...props }, ref) => { const styles = sizeStyles[size] const deltaColor = delta ? delta.value === 0 ? 'text-muted-foreground' @@ -35,21 +45,32 @@ const KPICard = React.forwardRef( return (
-
-

{label}

- {subtitle &&

{subtitle}

} +
+ {icon &&
{icon}
} +
+

{label}

+ {subtitle &&

{subtitle}

} +
- {icon &&
{icon}
} + {action &&
{action}
}
-

{value}

+
+ {value} + {unit && {unit}} +
{delta && ( -
- {delta.value > 0 ? '+' : ''}{delta.value}% +
+ {delta.label && {delta.label}} + + {delta.isPositive ? '▲' : '▼'} {delta.value > 0 ? '+' : ''}{delta.value}{delta.label ? '' : '%'} + + {delta.suffix && {delta.suffix}}
)}
+ {chart &&
{chart}
}
) @@ -57,4 +78,5 @@ const KPICard = React.forwardRef( ) KPICard.displayName = 'KPICard' -export { KPICard, type KPICardProps, type Delta, type KPICardSize } +export { KPICard } +export type { KPICardProps, Delta, KPICardSize } diff --git a/frontend/functions/ui/sparkline.md b/frontend/functions/ui/sparkline.md index fefb9543..a86fd23f 100644 --- a/frontend/functions/ui/sparkline.md +++ b/frontend/functions/ui/sparkline.md @@ -31,6 +31,10 @@ props: type: "string" required: false description: "Color del gráfico" + - name: colors + type: "string[]" + required: false + description: "Colores por barra para variant 'bar'. Cicla si es más corto que data." - name: width type: "number" required: false diff --git a/frontend/functions/ui/sparkline.tsx b/frontend/functions/ui/sparkline.tsx index 2a758798..75d09714 100644 --- a/frontend/functions/ui/sparkline.tsx +++ b/frontend/functions/ui/sparkline.tsx @@ -7,6 +7,8 @@ interface SparklineProps extends React.SVGAttributes { data: number[] variant?: SparklineVariant color?: string + /** Per-bar colors for 'bar' variant. Cycles if shorter than data. */ + colors?: string[] width?: number height?: number strokeWidth?: number @@ -30,7 +32,7 @@ function getPath(data: number[], width: number, height: number, padding: number } const Sparkline = React.forwardRef( - ({ data, variant = 'line', color = 'currentColor', width = 80, height = 24, strokeWidth = 1.5, showLastPoint = true, className, ...props }, ref) => { + ({ data, variant = 'line', color = 'currentColor', colors, width = 80, height = 24, strokeWidth = 1.5, showLastPoint = true, className, ...props }, ref) => { if (data.length === 0) return if (variant === 'bar') { @@ -46,7 +48,8 @@ const Sparkline = React.forwardRef( const bh = ((value - min) / range) * eh const x = p + index * ((width - p * 2) / data.length) + 0.5 const y = p + eh - bh - return + const barColor = colors ? colors[index % colors.length] : color + return })} )