UNPKG

lumir-design-system-01

Version:

Lumir Design System 01 - Professional & Clean

955 lines (945 loc) 100 kB
'use strict'; var jsxRuntime = require('react/jsx-runtime'); var lumirDesignSystemShared = require('lumir-design-system-shared'); var React = require('react'); const Badge = ({ color, size = 'md', layout = 'only text', style = 'filled', type = 'round-square', text, iconName, iconType = 'line', className, disabled, }) => { // 크기별 텍스트 variant 매핑 const getTextVariant = (size) => { switch (size) { case 'lg': return 'label-1'; // label-1 medium case 'md': return 'label-2'; // label-2 medium case 'sm': return 'caption-2'; // caption-2 medium } }; // 크기별 아이콘-텍스트 간격 매핑 (Frame gap) const getContentGap = (size) => { switch (size) { case 'lg': return 'xxs'; // 4px (Figma gap 값) case 'md': return 'xxs'; // 4px (Figma gap 값) case 'sm': return 'xxs'; // 4px (Figma gap 값) } }; // 스타일별 배경색 결정 const getBackgroundColor = () => { if (style === 'filled') { switch (color) { case 'primary': return 'primary-system01-1-rest'; case 'secondary': return 'secondary-system01-2-rest'; case 'cta': return 'cta-system01-1-rest'; case 'error': return 'error-1-rest'; case 'warning': return 'warning-1-rest'; default: return 'primary-system01-1-rest'; } } return undefined; // outlined, transparent 스타일은 배경 투명 }; // 스타일별 테두리 색상 결정 const getBorderColor = () => { if (style === 'outlined') { switch (color) { case 'primary': return 'primary-system01-1-rest'; case 'secondary': return 'secondary-system01-1-rest'; case 'cta': return 'cta-system01-1-rest'; case 'error': return 'error-1-rest'; case 'warning': return 'warning-1-rest'; default: return 'primary-system01-1-rest'; } } return undefined; // filled, transparent 스타일은 테두리 없음 }; // 스타일별 전경색 결정 (텍스트와 아이콘 모두 적용) const getForegroundColor = () => { // disabled 상태 처리 if (disabled) return 'secondary-system01-1-disabled'; if (style === 'filled') { switch (color) { case 'primary': case 'cta': // primary, cta filled일 때는 oncolor 사용 return 'primary-system01-oncolor'; case 'error': return 'error-oncolor'; // error 배경에 oncolor 적용 case 'warning': return 'warning-oncolor'; // warning 배경에 oncolor 적용 case 'secondary': return 'secondary-system01-2-rest'; default: return 'primary-system01-oncolor'; } } else { // outlined, transparent 스타일 switch (color) { case 'primary': return 'primary-system01-1-rest'; case 'secondary': return 'secondary-system01-1-rest'; case 'cta': return 'cta-system01-1-rest'; case 'error': return 'error-1-rest'; case 'warning': return 'warning-1-rest'; default: return 'secondary-system01-1-rest'; } } }; // 형태별 border radius 매핑 (System-01: 직각에 가까운 모서리) const getBorderRadius = (type) => { return type === 'circle' ? 'circular' : 'sm'; // System-01 가이드라인: sm 사용 }; // 크기별 아이콘 크기 매핑 const getIconSize = (size) => { switch (size) { case 'lg': return 'sm'; // 16px case 'md': return 'sm'; // 16px case 'sm': return 'xs'; // 12px } }; const backgroundColor = getBackgroundColor(); const borderColor = getBorderColor(); const foregroundColor = getForegroundColor(); const textVariant = getTextVariant(size); const iconSize = getIconSize(size); const contentGap = getContentGap(size); const borderRadius = getBorderRadius(type); return (jsxRuntime.jsx(lumirDesignSystemShared.Surface, { background: backgroundColor, foreground: foregroundColor, borderColor: borderColor, borderWidth: borderColor ? 'thin' : undefined, borderStyle: borderColor ? 'solid' : 'none', borderRadius: borderRadius, className: className, style: { width: 'fit-content', height: 'fit-content' }, children: jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", direction: "row", align: "center", justify: "center", gap: contentGap, padding: "xxs", children: [(layout === 'text+icon' || layout === 'only icon') && iconName && (jsxRuntime.jsx(lumirDesignSystemShared.Icon, { name: iconName, size: iconSize })), (layout === 'text+icon' || layout === 'only text') && text && (jsxRuntime.jsx(lumirDesignSystemShared.Text, { variant: textVariant, weight: "medium", children: text }))] }) })); }; /** * Button 컴포넌트는 사용자 상호작용을 위한 기본적인 요소입니다. * Lumir Design System의 스타일링을 따르며, 시맨틱 토큰을 사용하여 일관된 디자인을 제공합니다. */ const Button = React.forwardRef(({ variant = 'filled', buttonType = 'text-only', colorScheme = 'primary', size = 'md', isFullWidth = false, textAlign = 'center', isLoading = false, isSelected = false, disabled = false, leftIcon, rightIcon, className, children, ...rest }, ref) => { // 마우스 상태 관리 추가 const [isHovered, setIsHovered] = React.useState(false); const [isPressed, setIsPressed] = React.useState(false); // 마우스 이벤트 핸들러들 const handleMouseEnter = () => { if (!disabled && !isLoading) { setIsHovered(true); } }; const handleMouseLeave = () => { setIsHovered(false); setIsPressed(false); }; const handleMouseDown = () => { if (!disabled && !isLoading) { setIsPressed(true); } }; const handleMouseUp = () => { setIsPressed(false); }; const resolveButtonType = () => { if (buttonType === 'icon-only' && (leftIcon || rightIcon)) { return 'icon-only'; } if (buttonType === 'text-icon' && (leftIcon || rightIcon)) { return 'text-icon'; } return 'text-only'; }; const actualButtonType = resolveButtonType(); // 크기별 높이 토큰 결정 (Sizing 컴포넌트용) const getSizingHeight = () => { switch (size) { case 'xs': return 'sm'; // 24px case 'sm': return 'md'; // 28px case 'md': return 'lg'; // 32px case 'lg': return 'xl'; // 36px case 'xl': return 'xxl'; // 40px default: return 'lg'; } }; // 크기별 텍스트 variant 결정 (텍스트: xl(16), lg(16), md(14), sm(14), xs(12)) const getTextVariant = () => { switch (size) { case 'xs': return 'label-2'; // 12px case 'sm': return 'body-2'; // 14px case 'md': return 'body-2'; // 14px case 'lg': return 'body-1'; // 16px case 'xl': return 'body-1'; // 16px default: return 'body-2'; } }; // 크기별 아이콘 크기 결정 (아이콘: xl(24), lg(20), md(16), sm(16), xs(12)) const getIconSize = () => { switch (size) { case 'xs': return 'xs'; // 16px (12px 토큰이 없어서 최소값 사용) case 'sm': return 'xs'; // 16px case 'md': return 'xs'; // 16px case 'lg': return 'sm'; // 20px case 'xl': return 'md'; // 24px default: return 'xs'; } }; // 크기별 패딩 결정 (System-01: 타이트하지만 기능성 유지) const getPadding = () => { switch (size) { case 'xs': return 'xs'; case 'sm': return 'xs'; case 'md': return 'sm'; case 'lg': return 'sm'; // System-01: 타이트한 느낌으로 조정 case 'xl': return 'md'; // System-01: 타이트한 느낌으로 조정 default: return 'sm'; } }; // 배경색 결정 (hover/active 상태 포함) const getBackgroundColor = () => { const currentState = getCurrentState(); if (variant === 'filled') { if (colorScheme === 'primary') { if (currentState === 'disabled') return 'primary-system01-1-disabled'; if (currentState === 'pressed') return 'primary-system01-1-pressed'; if (currentState === 'hovered') return 'primary-system01-1-hovered'; return 'primary-system01-1-rest'; } if (colorScheme === 'secondary') { if (currentState === 'disabled') return 'secondary-system01-2-disabled'; if (currentState === 'pressed') return 'secondary-system01-2-pressed'; if (currentState === 'hovered') return 'secondary-system01-2-hovered'; return 'secondary-system01-2-rest'; } if (colorScheme === 'cta') { if (currentState === 'disabled') return 'cta-system01-1-disabled'; if (currentState === 'pressed') return 'cta-system01-1-pressed'; if (currentState === 'hovered') return 'cta-system01-1-hovered'; return 'cta-system01-1-rest'; } } if (variant === 'outlined') { // outlined는 연한 배경색 위계 사용 (background-3 = 더 subtle) if (currentState === 'rest' || currentState === 'disabled') return undefined; if (currentState === 'hovered') { if (colorScheme === 'primary') return 'secondary-system01-3-hovered'; // 연한 배경 if (colorScheme === 'secondary') return 'secondary-system01-3-hovered'; if (colorScheme === 'cta') return 'secondary-system01-3-hovered'; } if (currentState === 'pressed') { if (colorScheme === 'primary') return 'secondary-system01-3-pressed'; // 연한 배경 if (colorScheme === 'secondary') return 'secondary-system01-3-pressed'; if (colorScheme === 'cta') return 'secondary-system01-3-pressed'; } return undefined; } if (variant === 'transparent') { // transparent는 항상 배경색 없음 (텍스트 색상만 변경) return undefined; } return 'primary-system01-1-rest'; }; // 전경색 결정 (Surface에서 사용) const getForegroundColor = () => { const currentState = getCurrentState(); if (variant === 'filled') { if (colorScheme === 'primary') { if (currentState === 'disabled') return 'secondary-system01-3-disabled'; // 대비되는 disabled 색상 사용 // filled에서는 모든 상태에서 oncolor 사용 (배경색이 진하므로) return 'primary-system01-oncolor'; // onColor 사용 } if (colorScheme === 'secondary') { if (currentState === 'disabled') return 'secondary-system01-1-disabled'; // 대비되는 색상 사용 // filled에서는 모든 상태에서 oncolor 사용 (배경색이 진하므로) return 'secondary-system01-2-rest'; // onColor 사용 } if (colorScheme === 'cta') { if (currentState === 'disabled') return 'secondary-system01-3-disabled'; // 대비되는 disabled 색상 사용 // filled에서는 모든 상태에서 oncolor 사용 (배경색이 진하므로) return 'cta-system01-oncolor'; // onColor 사용 } } if (variant === 'outlined') { if (currentState === 'disabled') { return 'secondary-system01-2-disabled'; } // outlined hover/pressed 시에는 onColor가 아닌 더 연한 색상 사용 if (currentState === 'hovered' || currentState === 'pressed') { if (colorScheme === 'primary') return 'primary-system01-1-hovered'; // 연한 색상 위계 if (colorScheme === 'secondary') return 'secondary-system01-1-hovered'; if (colorScheme === 'cta') return 'cta-system01-1-hovered'; } // rest 상태에서는 일반 foreground 사용 if (colorScheme === 'primary') return 'primary-system01-1-rest'; if (colorScheme === 'secondary') return 'secondary-system01-2-rest'; if (colorScheme === 'cta') return 'cta-system01-1-rest'; } if (variant === 'transparent') { if (currentState === 'disabled') { return 'secondary-system01-2-disabled'; } // transparent는 더 강한 색상 변화로 시각적 피드백 제공 if (currentState === 'hovered') { if (colorScheme === 'primary') return 'primary-system01-1-hovered'; if (colorScheme === 'secondary') return 'secondary-system01-2-hovered'; if (colorScheme === 'cta') return 'cta-system01-1-hovered'; } if (currentState === 'pressed') { if (colorScheme === 'primary') return 'primary-system01-1-pressed'; if (colorScheme === 'secondary') return 'secondary-system01-2-pressed'; if (colorScheme === 'cta') return 'cta-system01-1-pressed'; } // rest 상태 if (colorScheme === 'primary') return 'primary-system01-1-rest'; if (colorScheme === 'secondary') return 'secondary-system01-2-rest'; if (colorScheme === 'cta') return 'cta-system01-1-rest'; } return 'primary-system01-1-rest'; }; // 테두리 설정 (outlined 가시성 개선) const getBorderProps = () => { const currentState = getCurrentState(); if (variant === 'outlined') { let borderColor = 'secondary-system01-2-rest'; if (colorScheme === 'primary') { if (currentState === 'disabled') borderColor = 'secondary-system01-3-disabled'; else if (currentState === 'pressed') borderColor = 'primary-system01-1-pressed'; else if (currentState === 'hovered') borderColor = 'primary-system01-1-hovered'; else borderColor = 'primary-system01-1-rest'; } else if (colorScheme === 'secondary') { if (currentState === 'disabled') borderColor = 'secondary-system01-2-disabled'; else if (currentState === 'pressed') borderColor = 'secondary-system01-1-pressed'; else if (currentState === 'hovered') borderColor = 'secondary-system01-1-hovered'; else borderColor = 'secondary-system01-1-rest'; } else if (colorScheme === 'cta') { if (currentState === 'disabled') borderColor = 'secondary-system01-3-disabled'; else if (currentState === 'pressed') borderColor = 'cta-system01-1-pressed'; else if (currentState === 'hovered') borderColor = 'cta-system01-1-hovered'; else borderColor = 'cta-system01-1-rest'; } return { borderWidth: 'thin', borderColor }; } return {}; }; // 현재 상태 결정 const getCurrentState = () => { if (disabled || isLoading) return 'disabled'; if (isPressed) return 'pressed'; if (isHovered) return 'hovered'; return 'rest'; }; // full width일 때 정렬 방식 결정 const getJustifyContent = () => { if (!isFullWidth) return 'center'; switch (textAlign) { case 'left': return 'flex-start'; case 'right': return 'flex-end'; case 'center': default: return 'center'; } }; // onClick과 style을 제외한 나머지 props const { onClick, style: customStyle, color, ...otherProps } = rest; const borderProps = getBorderProps(); return (jsxRuntime.jsx(lumirDesignSystemShared.Surface, { ref: ref, background: getBackgroundColor(), foreground: getForegroundColor(), borderRadius: "sm", borderWidth: borderProps.borderWidth, borderColor: borderProps.borderColor, borderStyle: borderProps.borderWidth ? 'solid' : 'none', className: className, onClick: disabled || isLoading ? undefined : onClick, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, style: { cursor: disabled || isLoading ? 'not-allowed' : 'pointer', ...customStyle }, ...otherProps, children: jsxRuntime.jsx(lumirDesignSystemShared.Sizing, { height: getSizingHeight(), width: isFullWidth ? '100%' : 'fit-content', children: jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", direction: "row", align: "center", justify: getJustifyContent(), gap: "xs", fill: true, padding: getPadding(), children: [isLoading && (jsxRuntime.jsx(lumirDesignSystemShared.Icon, { name: "LineIconsMenuMenuIcon", size: getIconSize(), "aria-hidden": true })), leftIcon && !isLoading && (jsxRuntime.jsx(lumirDesignSystemShared.Icon, { name: leftIcon, size: getIconSize(), "aria-hidden": true })), (actualButtonType !== 'icon-only' && children) && (jsxRuntime.jsx(lumirDesignSystemShared.Text, { as: "span", variant: getTextVariant(), weight: "medium", children: children })), rightIcon && !isLoading && (jsxRuntime.jsx(lumirDesignSystemShared.Icon, { name: rightIcon, size: getIconSize(), "aria-hidden": true }))] }) }) })); }); Button.displayName = 'Button'; /** * TextDisplay 컴포넌트는 다양한 텍스트 요소들을 조합하여 표시하는 컴파운드 컴포넌트입니다. * 라벨, 메인 텍스트, 설명, 캡션을 선택적으로 표시할 수 있으며, 아이콘도 함께 표시할 수 있습니다. * * @example * ```tsx * // 기본 사용법 * <TextDisplay * primaryText="Hello World" * /> * * // 모든 요소 포함 * <TextDisplay * size="lg" * style="center" * iconPosition="above" * iconName="star-filled" * iconType="flat" * showLabel={true} * labelText="Status" * primaryText="Success" * showDescription={true} * descriptionText="Your operation completed successfully" * showCaption={true} * captionText="Last updated: 2024-01-01" * /> * * // 좌측 정렬 + 앞쪽 아이콘 * <TextDisplay * style="left" * iconPosition="before" * iconName="info" * primaryText="Information" * showDescription={true} * descriptionText="Additional details here" * /> * ``` */ const TextDisplay = React.forwardRef(({ size = 'md', style = 'left', iconPosition = 'none', iconName = 'LineIconsStarFilledIcon', iconType = 'line', showLabel = false, labelText = 'label', primaryText, showDescription = false, descriptionText = 'description text', showCaption = false, captionText = 'caption', className, ...rest }, ref) => { // 크기별 텍스트 variant 매핑 (피그마 실제 크기 기준 - 수정됨) const getTextVariants = (size) => { switch (size) { case 'xlg': return { label: 'body-2', // 14px (피그마는 15px이지만 가장 근접한 토큰) primary: 'title-2', // 24px (lg의 20px에서 자연스러운 다음 단계) description: 'heading-3', // 16px (피그마는 18px이지만 가장 근접한 토큰) caption: 'body-2', // 14px (피그마는 15px이지만 가장 근접한 토큰) }; case 'lg': return { label: 'label-1', // 14px (정확히 일치) primary: 'heading-1', // 20px (md와 lg 사이의 자연스러운 중간 단계) description: 'body-1', // 16px (정확히 일치) caption: 'label-1', // 14px (정확히 일치) }; case 'md': return { label: 'label-2', // 12px (피그마는 13px이지만 가장 근접한 토큰) primary: 'heading-3', // 16px (피그마는 18px이지만 가장 근접한 토큰) description: 'label-1', // 14px (정확히 일치) caption: 'label-2', // 12px (피그마는 13px이지만 가장 근접한 토큰) }; case 'sm': return { label: 'caption-1', // 12px (정확히 일치) primary: 'body-1', // 16px (정확히 일치) description: 'label-1', // 14px (정확히 일치) caption: 'caption-1', // 12px (정확히 일치) }; case 'xsm': return { label: 'caption-1', // 12px (정확히 일치) primary: 'label-1', // 14px (정확히 일치) description: 'caption-1', // 12px (정확히 일치) caption: 'caption-3', // 11px (정확히 일치) }; } }; // 크기별 font weight 매핑 (피그마 기준) const getTextWeights = (size) => { switch (size) { case 'xlg': return { label: 'regular', // 400 primary: 'bold', // 700 description: 'regular', // 400 caption: 'regular', // 400 }; case 'lg': return { label: 'regular', // 400 primary: 'bold', // 700 description: 'regular', // 400 caption: 'regular', // 400 }; case 'md': return { label: 'regular', // 400 primary: 'bold', // 700 description: 'regular', // 400 caption: 'regular', // 400 }; case 'sm': return { label: 'regular', // 400 primary: 'bold', // 700 description: 'regular', // 400 caption: 'regular', // 400 }; case 'xsm': return { label: 'regular', // 400 primary: 'bold', // 700 description: 'regular', // 400 caption: 'regular', // 400 }; } }; // 크기별 아이콘 크기 매핑 const getIconSize = (size) => { switch (size) { case 'xlg': return 'lg'; case 'lg': return 'md'; case 'md': return 'sm'; case 'sm': return 'xs'; case 'xsm': return 'xs'; } }; // Surface foreground 색상 결정 const getForegroundColors = () => { return { label: 'secondary-system01-2-rest', primary: 'secondary-system01-1-rest', description: 'secondary-system01-2-rest', caption: 'secondary-system01-3-rest', }; }; const textVariants = getTextVariants(size); const textWeights = getTextWeights(size); const iconSize = getIconSize(size); const foregroundColors = getForegroundColors(); // 아이콘 렌더링 const renderIcon = () => { if (iconPosition === 'none' || !iconName) return null; return (jsxRuntime.jsx(lumirDesignSystemShared.Icon, { name: iconName, size: iconSize })); }; // 텍스트 콘텐츠 렌더링 const renderTextContent = () => { const alignItems = style === 'center' ? 'center' : 'flex-start'; const textAlign = style === 'center' ? 'center' : 'left'; return (jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", direction: "column", gap: "xxxs", align: alignItems, children: [showLabel && labelText && (jsxRuntime.jsx(lumirDesignSystemShared.Surface, { foreground: foregroundColors.label, children: jsxRuntime.jsx(lumirDesignSystemShared.Text, { variant: textVariants.label, weight: textWeights.label, textAlign: textAlign, children: labelText }) })), primaryText && (jsxRuntime.jsx(lumirDesignSystemShared.Surface, { foreground: foregroundColors.primary, children: jsxRuntime.jsx(lumirDesignSystemShared.Text, { variant: textVariants.primary, weight: textWeights.primary, textAlign: textAlign, children: primaryText }) })), showDescription && descriptionText && (jsxRuntime.jsx(lumirDesignSystemShared.Surface, { foreground: foregroundColors.description, children: jsxRuntime.jsx(lumirDesignSystemShared.Text, { variant: textVariants.description, weight: textWeights.description, textAlign: textAlign, children: descriptionText }) })), showCaption && captionText && (jsxRuntime.jsx(lumirDesignSystemShared.Surface, { foreground: foregroundColors.caption, children: jsxRuntime.jsx(lumirDesignSystemShared.Text, { variant: textVariants.caption, weight: textWeights.caption, textAlign: textAlign, children: captionText }) }))] })); }; // 레이아웃 구성 if (iconPosition === 'above') { // 아이콘이 위에 있는 경우 (세로 레이아웃) return (jsxRuntime.jsx("div", { ref: ref, className: className, ...rest, children: jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", direction: "column", gap: "sm", align: "center", children: [renderIcon(), renderTextContent()] }) })); } else if (iconPosition === 'before' || iconPosition === 'after') { // 아이콘이 앞뒤에 있는 경우 (가로 레이아웃) return (jsxRuntime.jsx("div", { ref: ref, className: className, ...rest, children: jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", direction: "row", gap: "sm", align: "center", children: [iconPosition === 'before' && renderIcon(), renderTextContent(), iconPosition === 'after' && renderIcon()] }) })); } else { // 아이콘이 없는 경우 return (jsxRuntime.jsx("div", { ref: ref, className: className, ...rest, children: renderTextContent() })); } }); TextDisplay.displayName = 'TextDisplay'; const Card = ({ // 🚨🚨🚨 실시간 테스트: 이 주석이 콘솔에 보이면 로컬 파일 참조 중! 🚨🚨🚨 variant = "filled", title, badge, showBadge = true, description, additionalInfo, statusText, actionButton, showActionButton = true, imageUrl, showImage = true, imageHeight = "242", imageObjectFit = "cover", imageObjectPosition = "center", imageAspectRatio, size, textStyle, showLabel, labelText, showDescription, showCaption, captionText, onClick, className, width = '100%', minWidth, maxWidth, // maxWidth 제한 제거 - Grid에서 자유롭게 확장 가능 badgeText, badgeColor, badgeIconName, badgeSize, badgeStyle, badgeLayout, badgeType, // Button props buttonVariant = "filled", buttonType = "icon-only", buttonColorScheme = "primary", buttonSize = "md", buttonPosition = "top-right", buttonText, buttonIcon, buttonRightIcon, buttonIsFullWidth = false, buttonIsSelected = false, }) => { // 🚨 실시간 테스트 로그 console.log('🚨🚨🚨 Card 컴포넌트 로컬 파일에서 실행 중! 🚨🚨🚨'); const handleClick = () => { if (onClick) { onClick(); } }; const handleActionClick = (e) => { e.stopPropagation(); if (actionButton === null || actionButton === void 0 ? void 0 : actionButton.onClick) { actionButton.onClick(e); } }; // Variant에 따른 스타일 설정 (System-01 스타일) const getCardStyles = () => { switch (variant) { case 'filled': return { background: "secondary-system01-1-rest", borderColor: "secondary-system01-2-rest", borderWidth: "thin", borderStyle: "solid", }; case 'outlined': return { background: undefined, borderColor: "secondary-system01-2-rest", borderWidth: "thin", borderStyle: "solid", }; case 'transparent': return { background: undefined, borderColor: undefined, borderWidth: undefined, borderStyle: undefined, }; default: return { background: "secondary-system01-1-rest", borderColor: "secondary-system01-2-rest", borderWidth: "thin", borderStyle: "solid", }; } }; const cardStyles = getCardStyles(); return (jsxRuntime.jsx(lumirDesignSystemShared.Sizing, { width: width, minWidth: minWidth, maxWidth: maxWidth, className: className, children: jsxRuntime.jsx(lumirDesignSystemShared.Surface, { borderRadius: variant === 'transparent' ? undefined : "sm", background: cardStyles.background, borderColor: cardStyles.borderColor, borderWidth: cardStyles.borderWidth, borderStyle: cardStyles.borderStyle, onClick: handleClick, style: { cursor: onClick ? 'pointer' : 'default' }, children: jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", direction: "column", gap: "md", padding: variant === 'transparent' ? undefined : "md", children: [showImage && (jsxRuntime.jsx(lumirDesignSystemShared.Sizing, { height: imageHeight, aspectRatio: imageAspectRatio, children: jsxRuntime.jsx(lumirDesignSystemShared.Surface, { borderRadius: "sm" // System-01: 작은 radius , children: imageUrl ? (jsxRuntime.jsx(lumirDesignSystemShared.Sizing, { width: "100%", height: "100%", objectFit: imageObjectFit, objectPosition: imageObjectPosition, children: jsxRuntime.jsx("img", { src: imageUrl, alt: title, style: { width: '100%', height: '100%', display: 'block' } }) })) : (jsxRuntime.jsx(lumirDesignSystemShared.Frame, { display: "flex", align: "center", justify: "center", fill: true, children: jsxRuntime.jsx(TextDisplay, { size: "lg", style: "center", primaryText: "Product" }) })) }) })), buttonPosition === 'bottom-full' ? ( /* bottom-full: 텍스트 아래에 full-width 버튼 */ jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", direction: "column", gap: "sm", children: [" ", jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", direction: "column", gap: "xs", children: [showBadge && (badge || badgeText) && (jsxRuntime.jsx(Badge, { text: badgeText || (badge === null || badge === void 0 ? void 0 : badge.text) || 'Badge', color: badgeColor || (badge === null || badge === void 0 ? void 0 : badge.color) || 'primary', iconName: badgeIconName || (badge === null || badge === void 0 ? void 0 : badge.iconName), size: badgeSize || (badge === null || badge === void 0 ? void 0 : badge.size) || 'sm', style: badgeStyle || (badge === null || badge === void 0 ? void 0 : badge.style) || 'filled', layout: badgeLayout || (badge === null || badge === void 0 ? void 0 : badge.layout) || 'only text', type: badgeType || (badge === null || badge === void 0 ? void 0 : badge.type) || 'round-square' })), jsxRuntime.jsx(TextDisplay, { size: size || 'md', style: textStyle || 'left', primaryText: title, showLabel: showLabel, labelText: labelText, showDescription: showDescription && !!description, descriptionText: description, showCaption: showCaption, captionText: captionText })] }), showActionButton && (jsxRuntime.jsx(Button, { variant: buttonVariant, buttonType: buttonType, colorScheme: buttonColorScheme, size: buttonSize, leftIcon: buttonIcon || (actionButton === null || actionButton === void 0 ? void 0 : actionButton.iconName) || 'LineIconsPlusPlusIcon', rightIcon: buttonRightIcon, isFullWidth: true, isSelected: buttonIsSelected, disabled: actionButton === null || actionButton === void 0 ? void 0 : actionButton.disabled, isLoading: actionButton === null || actionButton === void 0 ? void 0 : actionButton.isLoading, onClick: handleActionClick, ...actionButton === null || actionButton === void 0 ? void 0 : actionButton.buttonProps, children: buttonText }))] })) : buttonPosition === 'bottom-right' ? ( /* bottom-right: 전체를 세로로 배치하고 마지막에 우측 정렬 버튼 */ jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", direction: "column", gap: "sm", children: [jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", direction: "column", gap: "xs", children: [showBadge && (badge || badgeText) && (jsxRuntime.jsx(Badge, { text: badgeText || (badge === null || badge === void 0 ? void 0 : badge.text) || 'Badge', color: badgeColor || (badge === null || badge === void 0 ? void 0 : badge.color) || 'primary', iconName: badgeIconName || (badge === null || badge === void 0 ? void 0 : badge.iconName), size: badgeSize || (badge === null || badge === void 0 ? void 0 : badge.size) || 'sm', style: badgeStyle || (badge === null || badge === void 0 ? void 0 : badge.style) || 'filled', layout: badgeLayout || (badge === null || badge === void 0 ? void 0 : badge.layout) || 'only text', type: badgeType || (badge === null || badge === void 0 ? void 0 : badge.type) || 'round-square' })), jsxRuntime.jsx(TextDisplay, { size: size || 'md', style: textStyle || 'left', primaryText: title, showLabel: showLabel, labelText: labelText, showDescription: showDescription && !!description, descriptionText: description, showCaption: showCaption, captionText: captionText })] }), showActionButton && (jsxRuntime.jsx(lumirDesignSystemShared.Frame, { display: "flex", justify: "flex-end", children: jsxRuntime.jsx(Button, { variant: buttonVariant, buttonType: buttonType, colorScheme: buttonColorScheme, size: buttonSize, leftIcon: buttonIcon || (actionButton === null || actionButton === void 0 ? void 0 : actionButton.iconName) || 'LineIconsPlusPlusIcon', rightIcon: buttonRightIcon, isFullWidth: buttonIsFullWidth, isSelected: buttonIsSelected, disabled: actionButton === null || actionButton === void 0 ? void 0 : actionButton.disabled, isLoading: actionButton === null || actionButton === void 0 ? void 0 : actionButton.isLoading, onClick: handleActionClick, ...actionButton === null || actionButton === void 0 ? void 0 : actionButton.buttonProps, children: buttonText }) }))] })) : ( /* top-right (기본): 기존 가로 배치 */ jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", direction: "row", justify: "space-between", align: "flex-start", gap: "md", children: [" ", jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", direction: "column", gap: "xs", children: [showBadge && (badge || badgeText) && (jsxRuntime.jsx(Badge, { text: badgeText || (badge === null || badge === void 0 ? void 0 : badge.text) || 'Badge', color: badgeColor || (badge === null || badge === void 0 ? void 0 : badge.color) || 'primary', iconName: badgeIconName || (badge === null || badge === void 0 ? void 0 : badge.iconName), size: badgeSize || (badge === null || badge === void 0 ? void 0 : badge.size) || 'sm', style: badgeStyle || (badge === null || badge === void 0 ? void 0 : badge.style) || 'filled', layout: badgeLayout || (badge === null || badge === void 0 ? void 0 : badge.layout) || 'only text', type: badgeType || (badge === null || badge === void 0 ? void 0 : badge.type) || 'round-square' })), jsxRuntime.jsx(TextDisplay, { size: size || 'md', style: textStyle || 'left', primaryText: title, showLabel: showLabel, labelText: labelText, showDescription: showDescription && !!description, descriptionText: description, showCaption: showCaption, captionText: captionText })] }), showActionButton && (jsxRuntime.jsx(Button, { variant: buttonVariant, buttonType: buttonType, colorScheme: buttonColorScheme, size: buttonSize, leftIcon: buttonIcon || (actionButton === null || actionButton === void 0 ? void 0 : actionButton.iconName) || 'LineIconsPlusPlusIcon', rightIcon: buttonRightIcon, isFullWidth: buttonIsFullWidth, isSelected: buttonIsSelected, disabled: actionButton === null || actionButton === void 0 ? void 0 : actionButton.disabled, isLoading: actionButton === null || actionButton === void 0 ? void 0 : actionButton.isLoading, onClick: handleActionClick, ...actionButton === null || actionButton === void 0 ? void 0 : actionButton.buttonProps, children: buttonText }))] }))] }) }) })); }; const Label = React.forwardRef(({ size = 'md', type = 'regular', disabled = false, asterisk = false, align = 'top', interactionState, className, children, ...props }, ref) => { // 현재 토큰 시스템에 맞춰 Text variant 매핑 // 피그마 원본: lg=15px, md=14px, sm=13px // 토큰 시스템: lg=16px, md=14px, sm=12px (가장 가까운 값) const getTextVariant = () => { if (size === 'lg') { // 16px - heading-3 계열 사용 (bold=600, medium=500, regular=400) return 'heading-3'; } else if (size === 'md') { // 14px - body-2 계열 사용 (bold=700, medium=500, regular=400) return 'body-2'; } else { // 12px - label-2 계열 사용 (bold=700, medium=500, regular=400) return 'label-2'; } }; // 피그마 기준 fontWeight 매핑 const getFontWeight = () => { switch (type) { case 'bold': return 'bold'; case 'medium': return 'medium'; case 'regular': return 'regular'; default: return 'regular'; } }; // 색상 결정 (피그마 기준) const getTextColor = () => { return disabled ? 'secondary-system01-1-disabled' : 'secondary-system01-1-rest'; }; return (jsxRuntime.jsx("label", { ref: ref, className: className, ...props, children: jsxRuntime.jsx(lumirDesignSystemShared.Surface, { foreground: getTextColor(), children: jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", align: align === 'center' ? 'center' : 'flex-start', direction: "row", gap: "xxxs", children: [jsxRuntime.jsx(lumirDesignSystemShared.Text, { as: "span", variant: getTextVariant(), weight: getFontWeight(), children: children }), asterisk && (jsxRuntime.jsx(lumirDesignSystemShared.Asterisk, { disabled: disabled }))] }) }) })); }); Label.displayName = 'Label'; const Checkbox = React.forwardRef(({ checked = false, indeterminate = false, disabled = false, label, showLabel = true, name, value, onChange, onClick, onFocus, onBlur, className, style, ...props }, ref) => { const [isHovered, setIsHovered] = React.useState(false); const [isPressed, setIsPressed] = React.useState(false); const [isFocused, setIsFocused] = React.useState(false); const handleChange = (event) => { if (!disabled && onChange) { onChange(event.target.checked); } }; const handleClick = (event) => { if (!disabled && onChange) { onChange(!checked); } if (onClick) { onClick(event); } }; const handleFocus = (event) => { setIsFocused(true); if (onFocus) { onFocus(event); } }; const handleBlur = (event) => { setIsFocused(false); if (onBlur) { onBlur(event); } }; const handleMouseEnter = () => { if (!disabled) { setIsHovered(true); } }; const handleMouseLeave = () => { setIsHovered(false); setIsPressed(false); }; const handleMouseDown = () => { if (!disabled) { setIsPressed(true); } }; const handleMouseUp = () => { setIsPressed(false); }; // 체크박스 배경색 결정 const getBackgroundColor = () => { if (checked || indeterminate) { if (disabled) return 'primary-system01-1-disabled'; if (isPressed) return 'primary-system01-1-pressed'; if (isHovered) return 'primary-system01-1-hovered'; return 'primary-system01-1-rest'; // 체크된 상태는 primary 색상 사용 } if (disabled) return 'secondary-system01-1-disabled'; if (isPressed) return 'secondary-system01-1-pressed'; if (isHovered) return 'secondary-system01-1-hovered'; return 'secondary-system01-1-rest'; // 체크되지 않은 상태는 secondary 색상 사용 }; // 체크박스 테두리 색상 결정 const getBorderColor = () => { if (checked || indeterminate) { if (disabled) return 'primary-system01-1-disabled'; return 'primary-system01-1-rest'; } if (disabled) return 'secondary-system01-2-disabled'; // disabled일 때만 연한 색 return 'secondary-system01-1-rest'; // 기본 상태는 더 진한 색 }; // 전경색 결정 const getForegroundColor = () => { if (checked || indeterminate) { return 'primary-system01-oncolor'; // 체크된 상태에서 아이콘 색상 } return 'secondary-system01-1-rest'; }; const renderCheckIcon = () => { if (indeterminate) { return (jsxRuntime.jsx(lumirDesignSystemShared.Surface, { background: "secondary-system01-inverse-rest", borderRadius: "sm", children: jsxRuntime.jsx(lumirDesignSystemShared.Sizing, { style: { width: '8px', height: '2px' } }) })); } if (checked) { return (jsxRuntime.jsx(lumirDesignSystemShared.Icon, { name: "LineIconsCheckmarkCheckmarkIcon", size: "xs" })); } return null; }; return (jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", direction: "row", align: "center", gap: "sm", className: className, style: { cursor: disabled ? 'not-allowed' : 'pointer', ...style }, children: [jsxRuntime.jsx("input", { ref: ref, type: "checkbox", checked: checked, disabled: disabled, name: name, value: value, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, style: { position: 'absolute', opacity: 0, width: 0, height: 0, margin: 0, padding: 0, border: 'none' }, ...props }), jsxRuntime.jsx(lumirDesignSystemShared.Surface, { background: getBackgroundColor(), foreground: getForegroundColor(), borderRadius: "sm", borderWidth: "thin", borderStyle: "solid", borderColor: getBorderColor(), onClick: handleClick, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, style: { cursor: disabled ? 'not-allowed' : 'pointer', }, children: jsxRuntime.jsx(lumirDesignSystemShared.Sizing, { width: "sm", height: "xxs", children: jsxRuntime.jsx(lumirDesignSystemShared.Frame, { display: "flex", align: "center", justify: "center", fill: true, children: renderCheckIcon() }) }) }), showLabel && label && (jsxRuntime.jsx(Label, { htmlFor: name, disabled: disabled, onClick: !disabled ? (() => handleClick({})) : undefined, children: label }))] })); }); Checkbox.displayName = 'Checkbox'; /** * Chip 컴포넌트는 선택된 항목이나 태그를 표시하는 컴포넌트입니다. * ComboBox에서 선택된 항목을 표시하거나 태그 입력에서 사용할 수 있습니다. */ const Chip = React.forwardRef(({ children, variant = 'filled', size = 'md', state, disabled = false, removable = false, selectable = false, selected = false, onClick, onRemove, ...rest }, ref) => { const [isHovered, setIsHovered] = React.useState(false); const [isPressed, setIsPressed] = React.useState(false); // 실제 상태 결정 const getActualState = () => { if (disabled) return 'disabled'; if (state && state !== 'default') return state; if (isPressed) return 'pressed'; if (isHovered) return 'hovered'; return 'default'; }; // 크기별 설정 - 시멘틱 토큰 활용 const sizeConfig = { sm: { padding: 'xxs', // 4px variant: 'caption-1', // 12px/16px iconSize: 'xs', }, md: { padding: 'xs', // 6px variant: 'body-2', // 14px/20px iconSize: 'sm', } }; const config = sizeConfig[size]; const actualState = getActualState(); // 배경색 결정 const getBackgroundColor = () => { if (variant === 'outlined') { return undefined; // outlined는 투명 배경 } // filled variant if (selected) { if (disabled) return 'primary-system01-1-disabled'; if (actualState === 'pressed') return 'primary-system01-1-pressed'; if (actualState === 'hovered') return 'primary-system01-1-hovered'; return 'primary-system01-1-rest'; } if (disabled) return 'secondary-system01-1-disabled'; if (actualState === 'pressed') return 'secondary-system01-2-pressed'; if (actualState === 'hovered') return 'secondary-system01-2-hovered'; return 'secondary-system01-2-rest'; }; // 테두리 색상 결정 const getBorderColor = () => { if (variant === 'filled') return undefined; // filled는 테두리 없음 // outlined variant if (disabled) return 'secondary-system01-2-disabled'; if (selected) { if (actualState === 'pressed') return 'primary-system01-1-pressed'; if (actualState === 'hovered') return 'primary-system01-1-hovered'; return 'primary-system01-1-rest'; } if (actualState === 'pressed') return 'secondary-system01-1-pressed'; if (actualState === 'hovered') return 'secondary-system01-1-hovered'; return 'secondary-system01-1-rest'; }; // 전경색 결정 const getForegroundColor = () => { if (disabled) return 'secondary-system01-3-disabled'; if (variant === 'outlined') { if (selected) { if (actualState === 'pressed') return 'primary-system01-1-pressed'; if (actualState === 'hovered') return 'primary-system01-1-hovered'; return 'primary-system01-1-rest'; } if (actualState === 'pressed') return 'secondary-system01-1-pressed'; if (actualState === 'hovered') return 'secondary-system01-1-hovered'; return 'secondary-system01-2-rest'; } // filled variant if (selected) return 'primary-system01-oncolor'; // primary 배경에서 잘 보이도록 onColor 사용 return 'secondary-system01-1-rest'; // secondary 배경에서도 onColor 사용 }; // 이벤트 핸들러 const handleMouseEnter = () => { if (!disabled && (!state || state === 'default')) { setIsHovered(true); } }; const handleMouseLeave = () => { if (!disabled && (!state || state === 'default')) { setIsHovered(false); setIsPressed(false); } }; const handleMouseDown = () => { if (!disabled && (!state || state === 'default')) { setIsPressed(true); } }; const handleMouseUp = () => { if (!disabled && (!state || state === 'default')) { setIsPressed(false); } }; const handleClick = () => { if (disabled) return; if (selectable && onClick) { onClick(); } }; const handleRemoveClick = (event) => { event.stopPropagation(); if (disabled) return; if (onRemove) { onRemove(); } }; return (jsxRuntime.jsx(lumirDesignSystemShared.Surface, { ref: ref, background: getBackgroundColor(), foreground: getForegroundColor(), borderRadius: "pill", borderWidth: variant === 'outlined' ? 'thin' : undefined, borderStyle: variant === 'outlined' ? 'solid' : undefined, borderColor: getBorderColor(), style: { cursor: disabled ? 'not-allowed' : selectable ? 'pointer' : 'default', userSelect: 'none', display: 'inline-flex', width: 'fit-content', }, onClick: selectable ? handleClick : undefined, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onMouseDown: handleMouseDown, onMouseUp: handleMouseUp, ...rest, children: jsxRuntime.jsxs(lumirDesignSystemShared.Frame, { display: "flex", direction: "row", gap: "xxs", align: "center", padding: config.padding, fill: true, children: [jsxRuntime.jsx(lumirDesignSystemShared.Text, { variant: config.variant, color: undefined, style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1, }, children: children }), removable && (jsxRuntime.jsx(lumirDesignSystemShared.Surface, { borderRadius: "sm", onClick: handleRemoveClick, style: { cursor: disabled ? 'not-allowed' : 'pointer', }, children: jsxRuntime.jsx(lumirDesignSystemShared.Frame, { display: "flex", align: "center", justify: "center", padding: "xxxs", style: { flexShrink: 0, }, children: jsxRuntime.jsx(lumirDesignSystemShared.Icon, { name: "LineIconsCloseCloseIcon", size: config.iconSize }) }) }))] }) })); }); Chip.displayName = 'Chip'; /** * Field 컴포넌트는 사용자 입력을 받기 위한 텍스트 필드입니다. * outlined, transparent, filled 스타일을 지원하며, 라벨 위치를 다양하게 설정할 수 있습니다. */ const Field = React.forwardRef(({ variant = 'outlined', labelPosition = 'above', size = 'md', fieldWidth, minWidth, truncateOnFit, label, required = false, disabled = false, error = false, errorMessage, helperText, placeholder, value, type = 'text', leftIcon, rightIcon, onChange, onFocus, onBlur, onClick, className, style, ...rest }, ref) => { // 상태 관리 추가 const [isHovered, setIsHovered] = React.useState(false); const [isFocused, setIsFocused] = React.useState(false); // 이벤트 핸들러들 const handleMouseEnter = () => { if (!disabled) { setIsHovered(true); } }; const handleMouseLeave = () => { setIsHovered(false); }; const handleFocus = (event) => { setIsFocused(true); if (onFocus) { onFocus(event);