lumir-design-system-01
Version:
Lumir Design System 01 - Professional & Clean
955 lines (945 loc) • 100 kB
JavaScript
'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);