feat(FE-92-94): Slicing UI detail chickin & refactor number input chickin form

This commit is contained in:
randy-ar
2025-10-25 16:27:15 +07:00
parent f0f6ec53cb
commit 1e9d02b4b7
11 changed files with 595 additions and 48 deletions
+178
View File
@@ -0,0 +1,178 @@
"use client";
import { HTMLAttributes, ReactNode } from "react";
import { cn } from "@/lib/helper";
import Image from "next/image";
export interface CardProps
extends Omit<HTMLAttributes<HTMLDivElement>, "className"> {
title?: string;
subtitle?: string;
image?: string;
imageAlt?: string;
imageWidth?: number;
imageHeight?: number;
actions?: ReactNode;
footer?: ReactNode;
className?: {
wrapper?: string;
image?: string;
body?: string;
title?: string;
subtitle?: string;
actions?: string;
footer?: string;
};
variant?: "default" | "compact" | "bordered" | "shadow" | "image-full";
size?: "sm" | "md" | "lg";
}
const Card = ({
title,
subtitle,
image,
imageAlt,
imageWidth,
imageHeight,
actions,
footer,
className,
variant = "default",
size = "md",
children,
...props
}: CardProps) => {
const getCardClasses = () => {
const baseClasses = "card bg-base-100";
const variantClasses = {
default: "",
compact: "card-compact",
bordered: "border border-base-300",
shadow: "shadow-xl",
"image-full": "card-side card-compact shadow-xl",
};
const sizeClasses = {
sm: "w-64",
md: "w-96",
lg: "w-[28rem]",
};
return cn(
baseClasses,
variantClasses[variant],
variant !== "image-full" ? sizeClasses[size] : "",
className?.wrapper,
);
};
const getImageDimensions = () => {
if (variant === "image-full") {
return {
width: imageWidth || 128,
height: imageHeight || 128,
};
}
const cardWidths = {
sm: 256, // w-64
md: 384, // w-96
lg: 448, // w-[28rem]
};
return {
width: imageWidth || cardWidths[size],
height: imageHeight || 192,
};
};
const getImageClasses = () => {
if (variant === "image-full") {
return cn("object-cover", className?.image);
}
return cn("w-full object-cover", className?.image);
};
const getBodyClasses = () => {
const baseClasses = "card-body";
if (variant === "compact" || variant === "image-full") {
return cn(baseClasses, "p-4", className?.body);
}
return cn(baseClasses, "p-6", className?.body);
};
const getTitleClasses = () => {
const sizeClasses = {
sm: "text-lg",
md: "text-xl",
lg: "text-2xl",
};
return cn("card-title font-bold", sizeClasses[size], className?.title);
};
const getSubtitleClasses = () => {
return cn("text-base-content/70 text-sm mt-1", className?.subtitle);
};
const getActionsClasses = () => {
return cn("card-actions justify-end mt-4", className?.actions);
};
const getFooterClasses = () => {
return cn("border-t border-base-300 mt-4 pt-4", className?.footer);
};
if (variant === "image-full" && image) {
const imageDimensions = getImageDimensions();
return (
<div className={getCardClasses()} {...props}>
<figure>
<Image
src={image}
alt={imageAlt || title || "Card image"}
width={imageDimensions.width}
height={imageDimensions.height}
className={getImageClasses()}
/>
</figure>
<div className={getBodyClasses()}>
{title && <h2 className={getTitleClasses()}>{title}</h2>}
{subtitle && <p className={getSubtitleClasses()}>{subtitle}</p>}
{children}
{actions && <div className={getActionsClasses()}>{actions}</div>}
</div>
{footer && <div className={getFooterClasses()}>{footer}</div>}
</div>
);
}
return (
<div className={getCardClasses()} {...props}>
{image && (
<figure>
<Image
src={image}
alt={imageAlt || title || "Card image"}
width={getImageDimensions().width}
height={getImageDimensions().height}
className={getImageClasses()}
/>
</figure>
)}
<div className={getBodyClasses()}>
{title && <h2 className={getTitleClasses()}>{title}</h2>}
{subtitle && <p className={getSubtitleClasses()}>{subtitle}</p>}
{children}
{actions && <div className={getActionsClasses()}>{actions}</div>}
</div>
{footer && <div className={getFooterClasses()}>{footer}</div>}
</div>
);
};
export default Card;