diff --git a/src/components/Card.tsx b/src/components/Card.tsx index 7b022971..d3ff80b1 100644 --- a/src/components/Card.tsx +++ b/src/components/Card.tsx @@ -1,9 +1,11 @@ 'use client'; -import { HTMLAttributes, ReactNode } from 'react'; +import { HTMLAttributes, ReactNode, useState } from 'react'; import { cn } from '@/lib/helper'; import Image from 'next/image'; +import Collapse from './Collapse'; +import { Icon } from '@iconify/react'; export interface CardProps extends Omit, 'className'> { @@ -11,8 +13,13 @@ export interface CardProps subtitle?: string; image?: string; imageAlt?: string; + imageWidth?: number; + imageHeight?: number; actions?: ReactNode; footer?: ReactNode; + collapsible?: boolean; + defaultCollapsed?: boolean; + onCollapsedChange?: (collapsed: boolean) => void; className?: { wrapper?: string; image?: string; @@ -21,6 +28,7 @@ export interface CardProps subtitle?: string; actions?: string; footer?: string; + collapsible?: string; }; variant?: 'default' | 'compact' | 'bordered' | 'shadow' | 'image-full'; size?: 'sm' | 'md' | 'lg'; @@ -31,14 +39,27 @@ const Card = ({ subtitle, image, imageAlt, + imageWidth, + imageHeight, actions, footer, + collapsible, + defaultCollapsed = false, + onCollapsedChange, className, variant = 'default', size = 'md', children, ...props }: CardProps) => { + const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed); + + const handleCollapsedChange = (open: boolean) => { + const collapsed = !open; + setIsCollapsed(collapsed); + onCollapsedChange?.(collapsed); + }; + const getCardClasses = () => { const baseClasses = 'card bg-base-100'; @@ -64,11 +85,31 @@ const Card = ({ ); }; + 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('w-32 h-32 object-cover', className?.image); + return cn('object-cover', className?.image); } - return cn('h-48 object-cover', className?.image); + return cn('w-full object-cover', className?.image); }; const getBodyClasses = () => { @@ -103,45 +144,98 @@ const Card = ({ return cn('border-t border-base-300 mt-4 pt-4', className?.footer); }; + const renderCardContent = () => { + const hasContent = children || actions || footer; + + const titleContent = ( +
+
+ {title &&

{title}

} + {subtitle &&

{subtitle}

} +
+ {collapsible && ( + + )} +
+ ); + + const cardContent = ( +
+ {children} + {actions &&
{actions}
} + {footer &&
{footer}
} +
+ ); + + return ( + <> + {image && ( +
+ {imageAlt +
+ )} +
+ {collapsible && hasContent ? ( + + {cardContent} + + ) : ( + <> + {(title || subtitle) && ( +
+ {title &&

{title}

} + {subtitle && ( +

{subtitle}

+ )} +
+ )} + {hasContent && cardContent} + + )} +
+ + ); + }; + if (variant === 'image-full' && image) { return (
-
- {imageAlt -
-
- {title &&

{title}

} - {subtitle &&

{subtitle}

} - {children} - {actions &&
{actions}
} -
- {footer &&
{footer}
} + {renderCardContent()}
); } return (
- {image && ( -
- {imageAlt -
- )} -
- {title &&

{title}

} - {subtitle &&

{subtitle}

} - {children} - {actions &&
{actions}
} -
- {footer &&
{footer}
} + {renderCardContent()}
); }; diff --git a/src/components/Collapse.tsx b/src/components/Collapse.tsx index 8506f65c..50d68017 100644 --- a/src/components/Collapse.tsx +++ b/src/components/Collapse.tsx @@ -26,6 +26,9 @@ export type CollapseProps = { disabled?: boolean; /** Allow only one open at a time by switching to radio input */ asRadio?: boolean; + /** Force full width instead of auto-fit when collapsed + * (Khusus justify-between dan justify-end) */ + fullWidth?: boolean; /** Extra classnames */ className?: string; titleClassName?: string; @@ -44,6 +47,7 @@ export const Collapse = ({ bordered, disabled, asRadio = false, + fullWidth, className, titleClassName, contentClassName, @@ -68,9 +72,9 @@ export const Collapse = ({ 'collapse', variant === 'arrow' && 'collapse-arrow', variant === 'plus' && 'collapse-plus', - bordered && 'border base-content/20 border-opacity-20 rounded', + bordered && 'border base-content/20 border-opacity-20 rounded-box', disabled && 'opacity-60 pointer-events-none', - !open && 'w-fit', + !fullWidth && !open && 'w-fit', className );