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
+140 -106
+140 -106
View File
@@ -1,163 +1,197 @@
"use client"; "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({ import React from "react";
// Desactivamos los estilos de sombra/borde base de HeroUI para tener control total import { Card as HeroUICard } from "@heroui/react";
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',
}
});
// 1. Omitimos las props conflictivas y declaramos tu API export type CardVariant =
export interface CardProps extends Omit<HeroUICardProps, 'className' | 'children' | 'variant'> { | "default"
variant?: 'default' | 'elevated' | 'glass' | 'outline' | 'interactive'; | "elevated"
padding?: 'none' | 'sm' | 'md' | 'lg'; | "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; className?: string;
children?: React.ReactNode;
} }
export const Card = React.forwardRef<HTMLDivElement, CardProps>( const variantStyles: Record<CardVariant, string> = {
( default:
{ "bg-white dark:bg-slate-900/90 border border-slate-200 dark:border-slate-800/80 shadow-sm",
variant = 'default',
padding = 'none',
className = '',
children,
...props
},
ref
) => {
return (
<HeroUICard
ref={ref}
className={cardStyles({
customVariant: variant,
customPadding: padding,
className
})}
// El radius interno de HeroUI se sobrescribe usando tu 'rounded-2xl'
{...props}
>
{children}
</HeroUICard>
);
}
);
Card.displayName = 'Card';
// 2. Subcomponentes: Mapeados a HeroUI elevated:
export interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> { "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
}: CardProps) => {
return (
<HeroUICard
{...props}
className={[
"rounded-2xl overflow-hidden transition-colors",
variantStyles[variant],
paddingStyles[padding],
className,
]
.filter(Boolean)
.join(" ")}
>
{children}
</HeroUICard>
);
};
export interface CardHeaderProps
extends React.ComponentProps<typeof HeroUICard.Header> {
bordered?: boolean; bordered?: boolean;
} }
export const CardHeader: React.FC<CardHeaderProps> = ({ export const CardHeader = ({
bordered = false, bordered = false,
className = '', className = "",
children, children,
...props ...props
}) => { }: CardHeaderProps) => {
return ( return (
<HeroUICardHeader <HeroUICard.Header
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}
`}
{...props} {...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} {children}
</HeroUICardHeader> </HeroUICard.Header>
); );
}; };
// 3. CardTitle y CardDescription: Se mantienen como HTML estándar export const CardTitle = ({
export const CardTitle: React.FC<React.HTMLAttributes<HTMLHeadingElement>> = ({ className = "",
className = '',
children, children,
...props ...props
}) => { }: React.ComponentProps<typeof HeroUICard.Title>) => {
return ( return (
<h3 <HeroUICard.Title
className={`text-lg sm:text-xl font-bold tracking-tight text-slate-900 dark:text-white transition-colors ${className}`}
{...props} {...props}
className={[
"text-lg sm:text-xl font-bold tracking-tight",
"text-slate-900 dark:text-white",
"transition-colors",
className,
]
.filter(Boolean)
.join(" ")}
> >
{children} {children}
</h3> </HeroUICard.Title>
); );
}; };
export const CardDescription: React.FC<React.HTMLAttributes<HTMLParagraphElement>> = ({ export const CardDescription = ({
className = '', className = "",
children, children,
...props ...props
}) => { }: React.ComponentProps<typeof HeroUICard.Description>) => {
return ( return (
<p <HeroUICard.Description
className={`text-xs sm:text-sm text-slate-500 dark:text-slate-400 leading-relaxed ${className}`}
{...props} {...props}
className={[
"text-xs sm:text-sm",
"text-slate-500 dark:text-slate-400",
"leading-relaxed",
className,
]
.filter(Boolean)
.join(" ")}
> >
{children} {children}
</p> </HeroUICard.Description>
); );
}; };
// 4. CardContent: Mapeado a CardBody de HeroUI export const CardContent = ({
export const CardContent: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className = "",
className = '',
children, children,
...props ...props
}) => { }: React.ComponentProps<typeof HeroUICard.Content>) => {
return ( 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} {children}
</div> </HeroUICard.Content>
); );
}; };
export interface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> { export interface CardFooterProps
extends React.ComponentProps<typeof HeroUICard.Footer> {
bordered?: boolean; bordered?: boolean;
} }
export const CardFooter: React.FC<CardFooterProps> = ({ export const CardFooter = ({
bordered = false, bordered = false,
className = '', className = "",
children, children,
...props ...props
}) => { }: CardFooterProps) => {
return ( return (
<HeroUICardFooter <HeroUICard.Footer
className={`
p-5 sm:p-6 flex items-center justify-between
${bordered ? 'border-t border-slate-100 dark:border-slate-800/80' : ''}
${className}
`}
{...props} {...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} {children}
</HeroUICardFooter> </HeroUICard.Footer>
); );
}; };