Image Frame
사용자가 업로드한 이미지를 표시하기 위한 컴포넌트입니다.
import { ImageFrame } from "@seed-design/react";
import { ContentPlaceholder } from "seed-design/ui/content-placeholder";
export default function ImageFramePreview() {
return (
<ImageFrame
ratio={4 / 3}
borderRadius="r2"
stroke
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="Landscape photograph by Tobias Tullius"
width="300px"
fallback={<ContentPlaceholder type="commerce" />}
/>
);
}Usage
import { ImageFrame } from "@seed-design/react";
import { ContentPlaceholder } from "seed-design/ui/content-placeholder";<ImageFrame
ratio={4 / 3}
borderRadius="r2"
stroke
src="..."
alt="..."
fallback={<ContentPlaceholder type="image" />}
/>Props
Prop
Type
rounded?boolean | undefinedsrcstringaltstringfallback?React.ReactNodeloading?"eager" | "lazy" | undefineddecoding?"auto" | "async" | "sync" | undefinedcrossOrigin?"" | "anonymous" | "use-credentials" | undefinedreferrerPolicy?React.HTMLAttributeReferrerPolicy | undefinedsizes?string | undefinedsrcSet?string | undefinedonLoad?React.ReactEventHandler<HTMLImageElement> | undefinedonError?React.ReactEventHandler<HTMLImageElement> | undefinedchildren?React.ReactNodecolor?(string & {}) | ScopedColorFg | ScopedColorPalette | undefinedasChild?boolean | undefinedpaddingX?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedheight?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedmaxHeight?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedminHeight?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedjustifyContent?"flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "flexStart" | "flexEnd" | "spaceBetween" | "spaceAround" | undefinedalignItems?"flex-start" | "flex-end" | "center" | "flexStart" | "flexEnd" | "stretch" | undefinedas?React.ElementType<any, keyof React.JSX.IntrinsicElements> | undefinedbackground?(string & {}) | ScopedColorBg | ScopedColorPalette | ScopedColorBanner | undefinedbackgroundGradient?"fadeLayerFloating" | "fadeLayerDefault" | "glowMagic" | "glowMagicPressed" | "highlightMagic" | "highlightMagicPressed" | "shimmerMagic" | "shimmerNeutral" | undefinedborderColor?(string & {}) | ScopedColorPalette | ScopedColorStroke | undefinedborderWidth?0 | (string & {}) | 1 | undefinedborderTopWidth?0 | (string & {}) | 1 | undefinedborderRightWidth?0 | (string & {}) | 1 | undefinedborderBottomWidth?0 | (string & {}) | 1 | undefinedborderLeftWidth?0 | (string & {}) | 1 | undefinedborderRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefinedborderTopLeftRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefinedborderTopRightRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefinedborderBottomRightRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefinedborderBottomLeftRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefinedboxShadow?(string & {}) | "s1" | "s2" | "s3" | undefinedwidth?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedminWidth?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedmaxWidth?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedtop?0 | (string & {}) | undefinedleft?0 | (string & {}) | undefinedright?0 | (string & {}) | undefinedbottom?0 | (string & {}) | undefinedpadding?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedpaddingY?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedpaddingTop?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefinedpaddingRight?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedpaddingBottom?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefinedpaddingLeft?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefineddisplay?"none" | "block" | "flex" | "inline-flex" | "inline" | "inline-block" | "inlineFlex" | "inlineBlock" | undefinedzIndex?number | (string & {}) | undefinedflexDirection?"row" | "column" | "row-reverse" | "column-reverse" | "rowReverse" | "columnReverse" | undefinedalignContent?"flex-start" | "flex-end" | "center" | "flexStart" | "flexEnd" | "stretch" | undefinedalignSelf?"flex-start" | "flex-end" | "center" | "flexStart" | "flexEnd" | "stretch" | undefinedgap?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefinedgridColumn?string | undefinedgridRow?string | undefinedunstable_transform?string | undefined_active?{ bg?: ScopedColorBg | ScopedColorPalette | (string & {}); background?: ScopedColorBg | ScopedColorPalette | (string & {}); } | undefinedExamples
Ratio
다양한 비율을 지정할 수 있습니다. 1은 정사각형, 4/3은 일반적인 사진 비율, 16/9는 와이드스크린 비율입니다.
import { ImageFrame, Flex, VStack, Text } from "@seed-design/react";
import { ContentPlaceholder } from "seed-design/ui/content-placeholder";
export default function ImageFrameRatio() {
return (
<Flex gap="x2" wrap="wrap" align="flex-end">
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="1:1"
style={{ width: 120 }}
fallback={<ContentPlaceholder type="buySell" />}
/>
<Text color="palette.gray700" textStyle="t1Regular">
1:1
</Text>
</VStack>
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={4 / 3}
borderRadius="r2"
stroke
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="4:3"
style={{ width: 160 }}
fallback={<ContentPlaceholder type="food" />}
/>
<Text color="palette.gray700" textStyle="t1Regular">
4:3
</Text>
</VStack>
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={16 / 9}
borderRadius="r2"
stroke
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="16:9"
style={{ width: 200 }}
fallback={<ContentPlaceholder type="realty" />}
/>
<Text color="palette.gray700" textStyle="t1Regular">
16:9
</Text>
</VStack>
</Flex>
);
}Border Radius
borderRadius prop으로 모서리 라운드 스타일을 적용할 수 있습니다.
import { ImageFrame, Flex, VStack, Text } from "@seed-design/react";
import { ContentPlaceholder } from "seed-design/ui/content-placeholder";
export default function ImageFrameBorderRadius() {
return (
<VStack gap="x6" alignItems="flex-start">
<Flex gap="x4" wrap="wrap" align="flex-end">
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={4 / 3}
borderRadius="r1"
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=120&dpr=2&q=80"
alt="size 20 borderRadius=r1"
style={{ width: 20 }}
fallback={<ContentPlaceholder type="buySell" />}
/>
<Text color="palette.gray700" textStyle="t1Regular">
20 / r1 (4px)
</Text>
</VStack>
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={4 / 3}
borderRadius="r1"
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=120&dpr=2&q=80"
alt="size 24 borderRadius r1"
style={{ width: 24 }}
fallback={<ContentPlaceholder type="car" />}
/>
<Text color="palette.gray700" textStyle="t1Regular">
24 / r1 (4px)
</Text>
</VStack>
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={4 / 3}
borderRadius="r1_5"
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=120&dpr=2&q=80"
alt="size 36 borderRadius r1_5"
style={{ width: 36 }}
fallback={<ContentPlaceholder type="food" />}
/>
<Text color="palette.gray700" textStyle="t1Regular">
36 / r1_5 (6px)
</Text>
</VStack>
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={4 / 3}
borderRadius="r1_5"
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=120&dpr=2&q=80"
alt="size 42 borderRadius r1_5"
style={{ width: 42 }}
fallback={<ContentPlaceholder type="jobs" />}
/>
<Text color="palette.gray700" textStyle="t1Regular">
42 / r1_5 (6px)
</Text>
</VStack>
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={4 / 3}
borderRadius="r1_5"
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=120&dpr=2&q=80"
alt="size 48 borderRadius r1_5"
style={{ width: 48 }}
fallback={<ContentPlaceholder type="realty" />}
/>
<Text color="palette.gray700" textStyle="t1Regular">
48 / r1_5 (6px)
</Text>
</VStack>
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={4 / 3}
borderRadius="r2"
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=160&dpr=2&q=80"
alt="size 64 borderRadius r2"
style={{ width: 64 }}
fallback={<ContentPlaceholder type="commerce" />}
/>
<Text color="palette.gray700" textStyle="t1Regular">
64+ / r2 (8px)
</Text>
</VStack>
</Flex>
</VStack>
);
}Stroke
stroke prop으로 테두리 스타일을 적용할 수 있습니다.
import { ImageFrame, Flex, VStack, Text } from "@seed-design/react";
import { ContentPlaceholder } from "seed-design/ui/content-placeholder";
export default function ImageFrameStroke() {
return (
<Flex gap="x4" wrap="wrap" align="flex-end">
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={4 / 3}
stroke={false}
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="stroke=false"
style={{ width: 150 }}
fallback={<ContentPlaceholder type="post" />}
/>
<Text color="palette.gray700" textStyle="t1Regular">
stroke=false
</Text>
</VStack>
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={4 / 3}
stroke={true}
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="stroke=true"
style={{ width: 150 }}
fallback={<ContentPlaceholder type="group" />}
/>
<Text color="palette.gray700" textStyle="t1Regular">
stroke=true
</Text>
</VStack>
</Flex>
);
}Fallback Image
fallback prop으로 이미지가 로드되지 않았을 때 보여질 요소를 지정할 수 있습니다. 주로 ContentPlaceholder를 사용합니다.
import { ImageFrame, Flex } from "@seed-design/react";
import { ContentPlaceholder } from "seed-design/ui/content-placeholder";
export default function ImageFrameFallbackExample() {
return (
<Flex gap="x3" wrap="wrap" align="flex-end">
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://invalid-url"
alt="Fallback with buySell type"
style={{ width: 120 }}
fallback={<ContentPlaceholder type="buySell" />}
/>
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://invalid-url"
alt="Fallback with food type"
style={{ width: 120 }}
fallback={<ContentPlaceholder type="food" />}
/>
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://invalid-url"
alt="Fallback with jobs type"
style={{ width: 120 }}
fallback={<ContentPlaceholder type="jobs" />}
/>
</Flex>
);
}Overlay
ImageFrameFloater를 사용하여 이미지 위에 오버레이 요소를 배치할 수 있습니다. ImageFrameBadge, ImageFrameIcon, ImageFrameIndicator, ImageFrameReactionButton 등 다양한 오버레이 컴포넌트를 제공합니다.
import { IconCarrotFill } from "@karrotmarket/react-monochrome-icon";
import {
ImageFrame,
ImageFrameFloater,
ImageFrameBadge,
ImageFrameIcon,
ImageFrameIndicator,
ImageFrameReactionButton,
Flex,
VStack,
Text,
} from "@seed-design/react";
import { ContentPlaceholder } from "seed-design/ui/content-placeholder";
import { useState } from "react";
export default function ImageFrameOverlayExample() {
const [liked, setLiked] = useState(false);
return (
<Flex gap="x3" wrap="wrap" align="flex-end">
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="Landscape with badge overlay"
style={{ width: 120 }}
fallback={<ContentPlaceholder type="buySell" />}
>
<ImageFrameFloater placement="bottom-end">
<ImageFrameBadge tone="brand" variant="solid">
NEW
</ImageFrameBadge>
</ImageFrameFloater>
</ImageFrame>
<Text color="palette.gray700" textStyle="t1Regular">
ImageFrameBadge
</Text>
</VStack>
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="Landscape with icon overlay"
style={{ width: 120 }}
fallback={<ContentPlaceholder type="commerce" />}
>
<ImageFrameFloater placement="bottom-end">
<ImageFrameIcon svg={<IconCarrotFill />} />
</ImageFrameFloater>
</ImageFrame>
<Text color="palette.gray700" textStyle="t1Regular">
ImageFrameIcon
</Text>
</VStack>
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="Landscape with indicator overlay"
style={{ width: 120 }}
fallback={<ContentPlaceholder type="food" />}
>
<ImageFrameFloater placement="bottom-end">
<ImageFrameIndicator>+9</ImageFrameIndicator>
</ImageFrameFloater>
</ImageFrame>
<Text color="palette.gray700" textStyle="t1Regular">
ImageFrameIndicator
</Text>
</VStack>
<VStack gap="x2" alignItems="center">
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="Landscape with reaction button overlay"
style={{ width: 120 }}
fallback={<ContentPlaceholder type="car" />}
>
<ImageFrameFloater placement="bottom-end">
<ImageFrameReactionButton
pressed={liked}
onPressedChange={setLiked}
aria-label="좋아요"
/>
</ImageFrameFloater>
</ImageFrame>
<Text color="palette.gray700" textStyle="t1Regular">
ImageFrameReactionButton
</Text>
</VStack>
</Flex>
);
}Multiple Overlays
여러 위치에 오버레이를 동시에 배치할 수 있습니다.
import {
ImageFrame,
ImageFrameFloater,
ImageFrameBadge,
ImageFrameReactionButton,
} from "@seed-design/react";
import { ContentPlaceholder } from "seed-design/ui/content-placeholder";
import { useState } from "react";
export default function ImageFrameOverlayMultipleExample() {
const [liked, setLiked] = useState(false);
return (
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="Landscape with multiple overlays"
style={{ width: 200 }}
fallback={<ContentPlaceholder type="coupon" />}
>
<ImageFrameFloater placement="top-start">
<ImageFrameBadge tone="brand" variant="solid">
NEW
</ImageFrameBadge>
</ImageFrameFloater>
<ImageFrameFloater placement="bottom-end">
<ImageFrameReactionButton pressed={liked} onPressedChange={setLiked} aria-label="좋아요" />
</ImageFrameFloater>
</ImageFrame>
);
}Custom Overlay
ImageFrameFloater는 모든 React 요소를 children으로 받을 수 있습니다. 미리 정의된 오버레이 컴포넌트 외에도 커스텀 UI를 배치할 수 있습니다.
import { ImageFrame, ImageFrameFloater } from "@seed-design/react";
import { ContentPlaceholder } from "seed-design/ui/content-placeholder";
export default function ImageFrameOverlayCustomExample() {
return (
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="Landscape with custom overlay"
style={{ width: 200 }}
fallback={<ContentPlaceholder type="business" />}
>
<ImageFrameFloater placement="bottom-end">
<div
style={{
padding: "4px 8px",
backgroundColor: "rgba(0, 0, 0, 0.6)",
borderRadius: 4,
color: "white",
fontSize: 12,
}}
>
Custom Element
</div>
</ImageFrameFloater>
</ImageFrame>
);
}Offset
offsetX, offsetY prop으로 오버레이의 여백을 조절할 수 있습니다. 기본값은 6px입니다.
import { ImageFrame, ImageFrameFloater, ImageFrameIndicator } from "@seed-design/react";
import { ContentPlaceholder } from "seed-design/ui/content-placeholder";
export default function ImageFrameOverlayInsetExample() {
return (
<div style={{ display: "flex", gap: 12 }}>
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="Landscape with default offset"
style={{ width: 150 }}
fallback={<ContentPlaceholder type="jobs" />}
>
<ImageFrameFloater placement="bottom-end">
<ImageFrameIndicator>default</ImageFrameIndicator>
</ImageFrameFloater>
</ImageFrame>
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="Landscape with 0 offset"
style={{ width: 150 }}
fallback={<ContentPlaceholder type="image" />}
>
<ImageFrameFloater placement="bottom-end" offsetX={0} offsetY={0}>
<ImageFrameIndicator>offset=0</ImageFrameIndicator>
</ImageFrameFloater>
</ImageFrame>
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
alt="Landscape with 12 offset"
style={{ width: 150 }}
fallback={<ContentPlaceholder type="default" />}
>
<ImageFrameFloater placement="bottom-end" offsetX="12px" offsetY="12px">
<ImageFrameIndicator>offset=12</ImageFrameIndicator>
</ImageFrameFloater>
</ImageFrame>
</div>
);
}Last updated on