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 | undefined
srcstring
altstring
fallback?React.ReactNode
loading?"eager" | "lazy" | undefined
decoding?"auto" | "async" | "sync" | undefined
crossOrigin?"" | "anonymous" | "use-credentials" | undefined
referrerPolicy?React.HTMLAttributeReferrerPolicy | undefined
sizes?string | undefined
srcSet?string | undefined
onLoad?React.ReactEventHandler<HTMLImageElement> | undefined
onError?React.ReactEventHandler<HTMLImageElement> | undefined
children?React.ReactNode
color?(string & {}) | ScopedColorFg | ScopedColorPalette | undefined
asChild?boolean | undefined
paddingX?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
height?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
maxHeight?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
minHeight?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
justifyContent?"flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "flexStart" | "flexEnd" | "spaceBetween" | "spaceAround" | undefined
alignItems?"flex-start" | "flex-end" | "center" | "flexStart" | "flexEnd" | "stretch" | undefined
as?React.ElementType<any, keyof React.JSX.IntrinsicElements> | undefined
background?(string & {}) | ScopedColorBg | ScopedColorPalette | ScopedColorBanner | undefined
backgroundGradient?"fadeLayerFloating" | "fadeLayerDefault" | "glowMagic" | "glowMagicPressed" | "highlightMagic" | "highlightMagicPressed" | "shimmerMagic" | "shimmerNeutral" | undefined
borderColor?(string & {}) | ScopedColorPalette | ScopedColorStroke | undefined
borderWidth?0 | (string & {}) | 1 | undefined
borderTopWidth?0 | (string & {}) | 1 | undefined
borderRightWidth?0 | (string & {}) | 1 | undefined
borderBottomWidth?0 | (string & {}) | 1 | undefined
borderLeftWidth?0 | (string & {}) | 1 | undefined
borderRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
borderTopLeftRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
borderTopRightRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
borderBottomRightRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
borderBottomLeftRadius?0 | (string & {}) | "r0_5" | "r1" | "r1_5" | "r2" | "r2_5" | "r3" | "r3_5" | "r4" | "r5" | "r6" | "full" | undefined
boxShadow?(string & {}) | "s1" | "s2" | "s3" | undefined
width?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
minWidth?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
maxWidth?(string & {}) | "full" | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
top?0 | (string & {}) | undefined
left?0 | (string & {}) | undefined
right?0 | (string & {}) | undefined
bottom?0 | (string & {}) | undefined
padding?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
paddingY?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
paddingTop?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefined
paddingRight?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
paddingBottom?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | "safeArea" | undefined
paddingLeft?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
display?"none" | "block" | "flex" | "inline-flex" | "inline" | "inline-block" | "inlineFlex" | "inlineBlock" | undefined
zIndex?number | (string & {}) | undefined
flexDirection?"row" | "column" | "row-reverse" | "column-reverse" | "rowReverse" | "columnReverse" | undefined
alignContent?"flex-start" | "flex-end" | "center" | "flexStart" | "flexEnd" | "stretch" | undefined
alignSelf?"flex-start" | "flex-end" | "center" | "flexStart" | "flexEnd" | "stretch" | undefined
gap?0 | (string & {}) | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | undefined
gridColumn?string | undefined
gridRow?string | undefined
unstable_transform?string | undefined
_active?{ bg?: ScopedColorBg | ScopedColorPalette | (string & {}); background?: ScopedColorBg | ScopedColorPalette | (string & {}); } | undefined

Examples

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

On this page