Image
Data Display
Next.js 이미지 최적화를 감싼 이미지 컴포넌트.
Usage
import
API Reference
반응형과 로딩
예제
Usage
제품 이미지와 학습 자료를 반응형 크기·지연 로딩·대체 텍스트와 함께 표시할 때 사용한다.
import
import
import { Image } from '@mildang/design-system/Image';예제를 복사해 쓸 때 필요한 준비
• @mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.
• 외부 패키지를 따로 설치한다. DS 의 전이 의존성에 기대지 않는다.
API Reference
공개 Props가 없습니다.
반응형과 로딩
반응형 이미지는 sizes를 지정하고, 부모 컨테이너를 채울 때는 position relative 컨테이너와 fill을 함께 사용합니다. 로딩 중에는 placeholder를 사용해 레이아웃 시프트를 줄일 수 있습니다.
반응형과 컨테이너 채우기
sizes와 fill로 반응형 이미지를 표시합니다.
import { default as Image } from '@mildang/design-system/Image';
import { css } from '@mildang/styled-system/css';
const containerStyle = css({
border: '1px solid token(colors.neutral.border.low)',
borderRadius: '8px',
padding: '16px',
marginBottom: '20px',
display: 'flex',
flexDirection: 'column',
gap: '12px',
});
const ImageFillContainerExample = () => {
return (
<div className={containerStyle}>
<h3>컨테이너 채우기</h3>
<div style={{ position: 'relative', width: '100%', height: '300px', border: '2px dashed #ccc' }}>
<Image
src="https://picsum.photos/id/1015/800/600"
alt="컨테이너를 채우는 이미지"
fill
style={{ objectFit: 'cover' }}
/>
</div>
</div>
);
};
export default ImageFillContainerExample;
로딩 상태
placeholder와 대체 상태를 구성합니다.
import { default as Image } from '@mildang/design-system/Image';
import { css } from '@mildang/styled-system/css';
// 기본 블러 데이터 URL (더 명확하고 큰 블러 이미지)
const defaultBlurDataURL =
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZGVmcz4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAlIiBzdHlsZT0ic3RvcC1jb2xvcjojZjBmMGYwO3N0b3Atb3BhY2l0eToxIiAvPgogICAgICA8c3RvcCBvZmZzZXQ9IjUwJSIgc3R5bGU9InN0b3AtY29sb3I6I2U1ZTVlNTtzdG9wLW9wYWNpdHk6MSIgLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIxMDAlIiBzdHlsZT0ic3RvcC1jb2xvcjojZDBkMGQwO3N0b3Atb3BhY2l0eToxIiAvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICA8L2RlZnM+CiAgPHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmFkaWVudCkiIC8+CiAgPGNpcmNsZSBjeD0iMjAwIiBjeT0iMTUwIiByPSI1MCIgZmlsbD0iI2NjY2NjYyIgb3BhY2l0eT0iMC41IiAvPgogIDx0ZXh0IHg9IjIwMCIgeT0iMTU1IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTYiIGZpbGw9IiM5OTk5OTkiPkxvYWRpbmcuLi48L3RleHQ+Cjwvc3ZnPg==';
const containerStyle = css({
border: '1px solid token(colors.neutral.border.low)',
borderRadius: '8px',
padding: '16px',
marginBottom: '20px',
display: 'flex',
flexDirection: 'column',
gap: '12px',
});
const imageGridStyle = css({
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
gap: '16px',
});
const ImageLoadingStatesExample = () => {
return (
<div className={containerStyle}>
<h3>로딩 상태 테스트</h3>
<div className={imageGridStyle}>
<div>
<p>Placeholder: empty</p>
<Image
src="https://picsum.photos/id/20/300/200"
alt="Empty placeholder"
width={300}
height={200}
placeholder="empty"
/>
</div>
<div>
<p>Placeholder: blur (기본 블러)</p>
<Image
src="https://picsum.photos/id/21/300/200"
alt="Blur placeholder"
width={300}
height={200}
placeholder="blur"
blurDataURL={defaultBlurDataURL}
/>
</div>
<div>
<p>느린 이미지 로딩 (5초 지연)</p>
<Image
src="https://picsum.photos/id/237/300/200"
alt="느린 로딩"
width={300}
height={200}
placeholder="blur"
blurDataURL={defaultBlurDataURL}
/>
</div>
</div>
<p style={{ marginTop: '16px', color: '#666', fontSize: '14px' }}>
💡 세 번째 이미지로 placeholder 효과를 확인할 수 있습니다.
</p>
</div>
);
};
export default ImageLoadingStatesExample;
예제
기본 사용
next/image 를 그대로 감싼 래퍼로, width·height 를 명시해야 레이아웃 시프트 없이 렌더된다.
코드
import { default as Image } from '@mildang/design-system/Image';
import { type ImageProps } from 'next/image';
// 기본 블러 데이터 URL (더 명확하고 큰 블러 이미지)
const defaultBlurDataURL =
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZGVmcz4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0iZ3JhZGllbnQiIHgxPSIwJSIgeTE9IjAlIiB4Mj0iMTAwJSIgeTI9IjEwMCUiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAlIiBzdHlsZT0ic3RvcC1jb2xvcjojZjBmMGYwO3N0b3Atb3BhY2l0eToxIiAvPgogICAgICA8c3RvcCBvZmZzZXQ9IjUwJSIgc3R5bGU9InN0b3AtY29sb3I6I2U1ZTVlNTtzdG9wLW9wYWNpdHk6MSIgLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIxMDAlIiBzdHlsZT0ic3RvcC1jb2xvcjojZDBkMGQwO3N0b3Atb3BhY2l0eToxIiAvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICA8L2RlZnM+CiAgPHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmFkaWVudCkiIC8+CiAgPGNpcmNsZSBjeD0iMjAwIiBjeT0iMTUwIiByPSI1MCIgZmlsbD0iI2NjY2NjYyIgb3BhY2l0eT0iMC41IiAvPgogIDx0ZXh0IHg9IjIwMCIgeT0iMTU1IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMTYiIGZpbGw9IiM5OTk5OTkiPkxvYWRpbmcuLi48L3RleHQ+Cjwvc3ZnPg==';
const STORY_DEFAULT_ARGS = { ...({}), ...({
src: 'https://picsum.photos/id/1025/400/300',
alt: '데모 이미지',
width: 400,
height: 300,
priority: false,
quality: 75,
fill: false,
placeholder: 'empty',
}) } as ImageProps;
const ImageDemoExampleRender = (args: ImageProps) => {
// fill이 true일 때는 width/height 제거 (Next.js Image 규칙)
const { fill, width, height, placeholder, blurDataURL, ...restArgs } = args;
// placeholder가 'blur'일 때 blurDataURL 처리
const blurProps =
placeholder === 'blur'
? {
placeholder,
blurDataURL: blurDataURL || defaultBlurDataURL, // 사용자 입력값 우선, 없으면 기본값
}
: placeholder === 'empty'
? { placeholder }
: {};
// TypeScript 타입 안전성을 위한 조건부 props 구성
if (fill) {
const fillImageProps = { fill, ...blurProps, ...restArgs };
return (
<div style={{ position: 'relative', width: '400px', height: '300px' }}>
<Image {...fillImageProps} />
</div>
);
} else {
const sizedImageProps = {
width: width!,
height: height!,
...blurProps,
...restArgs,
};
return <Image {...sizedImageProps} />;
}
};
export default function ImageDemoExample(props: Partial<ImageProps>) {
const mergedProps = { ...STORY_DEFAULT_ARGS, ...props } as ImageProps;
return ImageDemoExampleRender(mergedProps);
}
BasicUsage
import { default as Image } from '@mildang/design-system/Image';
import { css } from '@mildang/styled-system/css';
const containerStyle = css({
border: '1px solid token(colors.neutral.border.low)',
borderRadius: '8px',
padding: '16px',
marginBottom: '20px',
display: 'flex',
flexDirection: 'column',
gap: '12px',
});
const imageGridStyle = css({
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
gap: '16px',
});
const sampleImages = [
{ src: 'https://picsum.photos/id/1024/400/300', alt: '가로 4:3 이미지' },
{ src: 'https://picsum.photos/id/1020/400/400', alt: '정사각형 이미지' },
{ src: 'https://picsum.photos/id/1018/600/300', alt: '와이드 이미지' },
{ src: 'https://picsum.photos/id/1005/300/600', alt: '세로 이미지' },
];
const ImageBasicUsageExample = () => {
return (
<div className={containerStyle}>
<h3>기본 사용법</h3>
<div className={imageGridStyle}>
{sampleImages.map((image, index) => (
<Image
key={index}
src={image.src}
alt={image.alt}
width={200}
height={150}
style={{ objectFit: 'cover' }}
/>
))}
</div>
</div>
);
};
export default ImageBasicUsageExample;
ResponsiveImage
import { default as Image } from '@mildang/design-system/Image';
import { css } from '@mildang/styled-system/css';
const containerStyle = css({
border: '1px solid token(colors.neutral.border.low)',
borderRadius: '8px',
padding: '16px',
marginBottom: '20px',
display: 'flex',
flexDirection: 'column',
gap: '12px',
});
const ImageResponsiveImageExample = () => {
return (
<div className={containerStyle}>
<h3>반응형 이미지</h3>
<Image
src="https://picsum.photos/id/1018/1200/600"
alt="반응형 이미지"
width={1200}
height={600}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
style={{ width: '100%', height: 'auto' }}
/>
</div>
);
};
export default ImageResponsiveImageExample;
PriorityLoading
import { default as Image } from '@mildang/design-system/Image';
import { css } from '@mildang/styled-system/css';
const containerStyle = css({
border: '1px solid token(colors.neutral.border.low)',
borderRadius: '8px',
padding: '16px',
marginBottom: '20px',
display: 'flex',
flexDirection: 'column',
gap: '12px',
});
const ImagePriorityLoadingExample = () => {
return (
<div className={containerStyle}>
<h3>우선순위 로딩 (Above the fold)</h3>
<Image
src="https://picsum.photos/id/1003/600/400"
alt="우선순위 로딩 이미지"
width={600}
height={400}
priority={true}
style={{ width: '100%', height: 'auto' }}
/>
<p>이 이미지는 priority={'{true}'} 설정으로 즉시 로드됩니다.</p>
</div>
);
};
export default ImagePriorityLoadingExample;
QualityComparison
import { default as Image } from '@mildang/design-system/Image';
import { css } from '@mildang/styled-system/css';
const containerStyle = css({
border: '1px solid token(colors.neutral.border.low)',
borderRadius: '8px',
padding: '16px',
marginBottom: '20px',
display: 'flex',
flexDirection: 'column',
gap: '12px',
});
const imageGridStyle = css({
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
gap: '16px',
});
const ImageQualityComparisonExample = () => {
return (
<div className={containerStyle}>
<h3>이미지 품질 비교</h3>
<div className={imageGridStyle}>
<div>
<p>Quality: 30</p>
<Image
src="https://picsum.photos/id/100/300/200"
alt="낮은 품질"
width={300}
height={200}
quality={30}
/>
</div>
<div>
<p>Quality: 75 (기본값)</p>
<Image
src="https://picsum.photos/id/100/300/200"
alt="기본 품질"
width={300}
height={200}
quality={75}
/>
</div>
<div>
<p>Quality: 100</p>
<Image
src="https://picsum.photos/id/100/300/200"
alt="최고 품질"
width={300}
height={200}
quality={100}
/>
</div>
</div>
</div>
);
};
export default ImageQualityComparisonExample;
StyleVariations
import { default as Image } from '@mildang/design-system/Image';
import { css } from '@mildang/styled-system/css';
const containerStyle = css({
border: '1px solid token(colors.neutral.border.low)',
borderRadius: '8px',
padding: '16px',
marginBottom: '20px',
display: 'flex',
flexDirection: 'column',
gap: '12px',
});
const imageGridStyle = css({
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
gap: '16px',
});
const ImageStyleVariationsExample = () => {
return (
<div className={containerStyle}>
<h3>스타일 변형</h3>
<div className={imageGridStyle}>
<div>
<p>기본</p>
<Image src="https://picsum.photos/id/1011/200/200" alt="기본 스타일" width={200} height={200} />
</div>
<div>
<p>둥근 모서리</p>
<Image
src="https://picsum.photos/id/1000/200/200"
alt="둥근 모서리"
width={200}
height={200}
style={{ borderRadius: '12px' }}
/>
</div>
<div>
<p>원형</p>
<Image
src="https://picsum.photos/id/1001/200/200"
alt="원형 이미지"
width={200}
height={200}
style={{ borderRadius: '50%' }}
/>
</div>
<div>
<p>그림자</p>
<Image
src="https://picsum.photos/id/1002/200/200"
alt="그림자 효과"
width={200}
height={200}
style={{
borderRadius: '8px',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
}}
/>
</div>
</div>
</div>
);
};
export default ImageStyleVariationsExample;