refactor(ui): arreglar componente Card

This commit is contained in:
Miguel Ortega committed 2026-09-11 21:12:22 -05:00
1 parent 703c608ac9
commit e399f22007
1 file changed
+131 -97
+131 -97
View File
@@ -1,163 +1,197 @@
"use client";
import React from 'react';
import {
Card as HeroUICard,
CardHeader as HeroUICardHeader,
CardFooter as HeroUICardFooter,
CardProps as HeroUICardProps,
} from '@heroui/react';
import { tv } from '@heroui/styles';
const cardStyles = tv({
// Desactivamos los estilos de sombra/borde base de HeroUI para tener control total
base: 'rounded-2xl overflow-hidden transition-colors border-none shadow-none',
variants: {
customVariant: {
default: 'bg-white dark:bg-slate-900/90 border border-slate-200 dark:border-slate-800/80 shadow-sm',
elevated: 'bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 shadow-xl shadow-slate-200/50 dark:shadow-black/40',
glass: 'bg-white/80 dark:bg-slate-900/60 backdrop-blur-xl border border-slate-200/70 dark:border-slate-800/70 shadow-lg',
outline: 'bg-transparent border border-slate-300 dark:border-slate-800 shadow-none',
interactive: 'bg-white dark:bg-slate-900/90 border border-slate-200 dark:border-slate-800 shadow-sm hover:shadow-xl hover:shadow-cyan-500/10 hover:border-cyan-500/50 dark:hover:border-cyan-500/40 cursor-pointer transform hover:-translate-y-1 transition-all duration-300 ease-out',
},
customPadding: {
none: 'p-0',
sm: 'p-4',
md: 'p-6',
lg: 'p-8',
}
},
defaultVariants: {
customVariant: 'default',
customPadding: 'none',
}
});
import React from "react";
import { Card as HeroUICard } from "@heroui/react";
// 1. Omitimos las props conflictivas y declaramos tu API
export interface CardProps extends Omit<HeroUICardProps, 'className' | 'children' | 'variant'> {
variant?: 'default' | 'elevated' | 'glass' | 'outline' | 'interactive';
padding?: 'none' | 'sm' | 'md' | 'lg';
export type CardVariant =
| "default"
| "elevated"
| "glass"
| "outline"
| "interactive";
export type CardPadding = "none" | "sm" | "md" | "lg";
export interface CardProps
extends Omit<
React.ComponentProps<typeof HeroUICard>,
"variant" | "className"
> {
variant?: CardVariant;
padding?: CardPadding;
className?: string;
children?: React.ReactNode;
}
export const Card = React.forwardRef<HTMLDivElement, CardProps>(
(
{
variant = 'default',
padding = 'none',
className = '',
const variantStyles: Record<CardVariant, string> = {
default:
"bg-white dark:bg-slate-900/90 border border-slate-200 dark:border-slate-800/80 shadow-sm",
elevated:
"bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 shadow-xl shadow-slate-200/50 dark:shadow-black/40",
glass:
"bg-white/80 dark:bg-slate-900/60 backdrop-blur-xl border border-slate-200/70 dark:border-slate-800/70 shadow-lg",
outline:
"bg-transparent border border-slate-300 dark:border-slate-800 shadow-none",
interactive:
"bg-white dark:bg-slate-900/90 border border-slate-200 dark:border-slate-800 shadow-sm " +
"hover:shadow-xl hover:shadow-cyan-500/10 " +
"hover:border-cyan-500/50 dark:hover:border-cyan-500/40 " +
"cursor-pointer transform hover:-translate-y-1 " +
"transition-all duration-300 ease-out",
};
const paddingStyles: Record<CardPadding, string> = {
none: "p-0",
sm: "p-4",
md: "p-6",
lg: "p-8",
};
export const Card = ({
variant = "default",
padding = "none",
className = "",
children,
...props
},
ref
) => {
}: CardProps) => {
return (
<HeroUICard
ref={ref}
className={cardStyles({
customVariant: variant,
customPadding: padding,
className
})}
// El radius interno de HeroUI se sobrescribe usando tu 'rounded-2xl'
{...props}
className={[
"rounded-2xl overflow-hidden transition-colors",
variantStyles[variant],
paddingStyles[padding],
className,
]
.filter(Boolean)
.join(" ")}
>
{children}
</HeroUICard>
);
}
);
Card.displayName = 'Card';
};
// 2. Subcomponentes: Mapeados a HeroUI
export interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
export interface CardHeaderProps
extends React.ComponentProps<typeof HeroUICard.Header> {
bordered?: boolean;
}
export const CardHeader: React.FC<CardHeaderProps> = ({
export const CardHeader = ({
bordered = false,
className = '',
className = "",
children,
...props
}) => {
}: CardHeaderProps) => {
return (
<HeroUICardHeader
className={`
p-5 sm:p-6 flex flex-col space-y-1.5
${bordered ? 'border-b border-slate-100 dark:border-slate-800/80' : ''}
${className}
`}
<HeroUICard.Header
{...props}
className={[
"p-5 sm:p-6 flex flex-col space-y-1.5",
bordered
? "border-b border-slate-100 dark:border-slate-800/80"
: "",
className,
]
.filter(Boolean)
.join(" ")}
>
{children}
</HeroUICardHeader>
</HeroUICard.Header>
);
};
// 3. CardTitle y CardDescription: Se mantienen como HTML estándar
export const CardTitle: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({
className = '',
export const CardTitle = ({
className = "",
children,
...props
}) => {
}: React.ComponentProps<typeof HeroUICard.Title>) => {
return (
<h3
className={`text-lg sm:text-xl font-bold tracking-tight text-slate-900 dark:text-white transition-colors ${className}`}
<HeroUICard.Title
{...props}
className={[
"text-lg sm:text-xl font-bold tracking-tight",
"text-slate-900 dark:text-white",
"transition-colors",
className,
]
.filter(Boolean)
.join(" ")}
>
{children}
</h3>
</HeroUICard.Title>
);
};
export const CardDescription: React.FC<React.HTMLAttributes<HTMLParagraphElement>> = ({
className = '',
export const CardDescription = ({
className = "",
children,
...props
}) => {
}: React.ComponentProps<typeof HeroUICard.Description>) => {
return (
<p
className={`text-xs sm:text-sm text-slate-500 dark:text-slate-400 leading-relaxed ${className}`}
<HeroUICard.Description
{...props}
className={[
"text-xs sm:text-sm",
"text-slate-500 dark:text-slate-400",
"leading-relaxed",
className,
]
.filter(Boolean)
.join(" ")}
>
{children}
</p>
</HeroUICard.Description>
);
};
// 4. CardContent: Mapeado a CardBody de HeroUI
export const CardContent: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({
className = '',
export const CardContent = ({
className = "",
children,
...props
}) => {
}: React.ComponentProps<typeof HeroUICard.Content>) => {
return (
<div className={`p-5 sm:p-6 overflow-visible ${className}`} {...props}>
<HeroUICard.Content
{...props}
className={[
"p-5 sm:p-6",
className,
]
.filter(Boolean)
.join(" ")}
>
{children}
</div>
</HeroUICard.Content>
);
};
export interface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> {
export interface CardFooterProps
extends React.ComponentProps<typeof HeroUICard.Footer> {
bordered?: boolean;
}
export const CardFooter: React.FC<CardFooterProps> = ({
export const CardFooter = ({
bordered = false,
className = '',
className = "",
children,
...props
}) => {
}: CardFooterProps) => {
return (
<HeroUICardFooter
className={`
p-5 sm:p-6 flex items-center justify-between
${bordered ? 'border-t border-slate-100 dark:border-slate-800/80' : ''}
${className}
`}
<HeroUICard.Footer
{...props}
className={[
"p-5 sm:p-6 flex items-center justify-between",
bordered
? "border-t border-slate-100 dark:border-slate-800/80"
: "",
className,
]
.filter(Boolean)
.join(" ")}
>
{children}
</HeroUICardFooter>
</HeroUICard.Footer>
);
};