Lynx
Image Frame
사용자가 업로드한 이미지를 표시하기 위한 컴포넌트입니다.
사용 가능 버전@seed-design/[email protected], @seed-design/[email protected]
Lynx 예제를 불러오는 중입니다.
import "./styles";
import { ImageFrame } from "@seed-design/lynx-react";
export default function ImageFramePreview() {
return (
<ImageFrame
ratio={4 / 3}
borderRadius="r2"
stroke
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="프로필 이미지"
width="300px"
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
);
}Installation
npm install @seed-design/lynx-react @seed-design/lynx-csspnpm add @seed-design/lynx-react @seed-design/lynx-cssyarn add @seed-design/lynx-react @seed-design/lynx-cssbun add @seed-design/lynx-react @seed-design/lynx-cssUsage
import { ImageFrame } from "@seed-design/lynx-react";<ImageFrame
ratio={4 / 3}
width="200px"
borderRadius="r2"
stroke
src="..."
alt="..."
fallback={<view><text>이미지를 불러올 수 없습니다.</text></view>}
/>width·height·margin 등 공통 레이아웃 prop을 받습니다. margin과 bleed는 함께 사용하지 않으며, bleed는 Lynx Box와 같이 px 길이·0·"safeArea"를 사용합니다.
Props
Prop
Type
srcstringaltstringratio?number | undefinedstroke?boolean | undefinedfallback?React.ReactNodeonLoadingStatusChange?((status: ImageLoadingStatus) => void) | undefinedLynx에서의 동작
Lynx Engine 3.9 이상을 지원합니다.
- 로딩 중에도 이미지를 마운트합니다.
src가 바뀌면 이전 요청의 이벤트를 새 상태에 반영하지 않습니다. alt는accessibility-label로 전달합니다. 장식용 이미지는 빈 문자열을 사용합니다.onLoad와onError대신bindload와binderror를 사용합니다.onLoadingStatusChange로 로딩 상태를 관찰할 수 있습니다.ratio의 기본값은4 / 3,borderRadius의 기본값은"r2"입니다.ImageFrameIcon의svg에는 Lynx 아이콘 컴포넌트를 전달합니다.ImageFrameReactionButton은 토글 버튼입니다.pressed,defaultPressed,onPressedChange,disabled를 제공하며 접근성 이름은accessibility-label로 지정합니다.- 좋아요 버튼은
hit-slop으로 터치 영역을 기본 8px씩 확장합니다. 부모가 콘텐츠를 자르면 확장 영역도 제한될 수 있습니다. as,asChild, DOM 속성, 이미지의loading,decoding,crossOrigin,referrerPolicy,sizes,srcSet은 지원하지 않습니다.
Examples
Ratio
ratio로 이미지 영역의 비율을 지정합니다.
Lynx 예제를 불러오는 중입니다.
import "./styles";
import { ImageFrame, HStack, VStack, Text } from "@seed-design/lynx-react";
export default function ImageFrameRatio() {
return (
<HStack gap="x2" wrap="wrap" align="flex-end">
<VStack gap="x2" align="center">
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="1:1"
style={{ width: "120px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
<Text color="palette.gray700" textStyle="t1Regular">
1:1
</Text>
</VStack>
<VStack gap="x2" align="center">
<ImageFrame
ratio={4 / 3}
borderRadius="r2"
stroke
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="4:3"
style={{ width: "160px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
<Text color="palette.gray700" textStyle="t1Regular">
4:3
</Text>
</VStack>
<VStack gap="x2" align="center">
<ImageFrame
ratio={16 / 9}
borderRadius="r2"
stroke
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="16:9"
style={{ width: "200px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
<Text color="palette.gray700" textStyle="t1Regular">
16:9
</Text>
</VStack>
</HStack>
);
}Border Radius
borderRadius에는 토큰, 0, CSS 길이를 전달합니다. 개별 모서리는 borderTopLeftRadius 같은 prop으로 지정합니다.
<ImageFrame
src={imageUrl}
alt="상품 이미지"
width="160px"
ratio={1}
borderRadius="r1"
borderTopLeftRadius="r4"
borderBottomRightRadius="24px"
stroke
/>Lynx 예제를 불러오는 중입니다.
import "./styles";
import { ImageFrame, HStack, VStack, Text } from "@seed-design/lynx-react";
export default function ImageFrameBorderRadius() {
return (
<VStack gap="x6" align="flex-start">
<HStack gap="x4" wrap="wrap" align="flex-end">
<VStack gap="x2" align="center">
<ImageFrame
ratio={4 / 3}
borderRadius="r1"
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="size 20 borderRadius=r1"
style={{ width: "20px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
<Text color="palette.gray700" textStyle="t1Regular">
20 / r1 (4px)
</Text>
</VStack>
<VStack gap="x2" align="center">
<ImageFrame
ratio={4 / 3}
borderRadius="r1"
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="size 24 borderRadius r1"
style={{ width: "24px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
<Text color="palette.gray700" textStyle="t1Regular">
24 / r1 (4px)
</Text>
</VStack>
<VStack gap="x2" align="center">
<ImageFrame
ratio={4 / 3}
borderRadius="r1_5"
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="size 36 borderRadius r1_5"
style={{ width: "36px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
<Text color="palette.gray700" textStyle="t1Regular">
36 / r1_5 (6px)
</Text>
</VStack>
<VStack gap="x2" align="center">
<ImageFrame
ratio={4 / 3}
borderRadius="r1_5"
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="size 42 borderRadius r1_5"
style={{ width: "42px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
<Text color="palette.gray700" textStyle="t1Regular">
42 / r1_5 (6px)
</Text>
</VStack>
<VStack gap="x2" align="center">
<ImageFrame
ratio={4 / 3}
borderRadius="r1_5"
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="size 48 borderRadius r1_5"
style={{ width: "48px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
<Text color="palette.gray700" textStyle="t1Regular">
48 / r1_5 (6px)
</Text>
</VStack>
<VStack gap="x2" align="center">
<ImageFrame
ratio={4 / 3}
borderRadius="r2"
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="size 64 borderRadius r2"
style={{ width: "64px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
<Text color="palette.gray700" textStyle="t1Regular">
64+ / r2 (8px)
</Text>
</VStack>
</HStack>
</VStack>
);
}Stroke
stroke prop으로 테두리 스타일을 적용할 수 있습니다.
Lynx 예제를 불러오는 중입니다.
import "./styles";
import { ImageFrame, HStack, VStack, Text } from "@seed-design/lynx-react";
export default function ImageFrameStroke() {
return (
<HStack gap="x4" wrap="wrap" align="flex-end">
<VStack gap="x2" align="center">
<ImageFrame
ratio={4 / 3}
stroke={false}
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="stroke=false"
style={{ width: "150px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
<Text color="palette.gray700" textStyle="t1Regular">
stroke=false
</Text>
</VStack>
<VStack gap="x2" align="center">
<ImageFrame
ratio={4 / 3}
stroke={true}
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="stroke=true"
style={{ width: "150px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
<Text color="palette.gray700" textStyle="t1Regular">
stroke=true
</Text>
</VStack>
</HStack>
);
}Fallback Image
fallback은 로딩 중이거나 오류가 발생했을 때 표시하고, 로딩에 성공하면 언마운트합니다. 다시 표시할 때 유지해야 하는 상태는 부모에서 관리하세요.
Lynx 예제를 불러오는 중입니다.
import "./styles";
import { ImageFrame, HStack } from "@seed-design/lynx-react";
export default function ImageFrameFallbackExample() {
return (
<HStack gap="x3" wrap="wrap" align="flex-end">
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://invalid-url"
alt="이미지를 불러올 수 없습니다"
style={{ width: "120px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://invalid-url"
alt="이미지를 불러올 수 없습니다"
style={{ width: "120px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://invalid-url"
alt="이미지를 불러올 수 없습니다"
style={{ width: "120px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
/>
</HStack>
);
}Overlay
ImageFrameFloater를 자식으로 넣고 placement로 위치를 지정합니다. 세로와 가로 위치를 조합한 9개 값을 사용할 수 있습니다.
Lynx 예제를 불러오는 중입니다.
import "./styles";
import IconCarrotFill from "@karrotmarket/lynx-monochrome-icon/IconCarrotFill";
import {
ImageFrame,
ImageFrameFloater,
ImageFrameBadge,
ImageFrameIcon,
ImageFrameIndicator,
ImageFrameReactionButton,
HStack,
VStack,
Text,
} from "@seed-design/lynx-react";
import { useState } from "@lynx-js/react";
export default function ImageFrameOverlayExample() {
const [liked, setLiked] = useState(false);
return (
<HStack gap="x3" wrap="wrap" align="flex-end">
<VStack gap="x2" align="center">
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="Profile with badge overlay"
style={{ width: "120px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
>
<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" align="center">
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="Profile with icon overlay"
style={{ width: "120px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
>
<ImageFrameFloater placement="bottom-end">
<ImageFrameIcon svg={<IconCarrotFill />} />
</ImageFrameFloater>
</ImageFrame>
<Text color="palette.gray700" textStyle="t1Regular">
ImageFrameIcon
</Text>
</VStack>
<VStack gap="x2" align="center">
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="Profile with indicator overlay"
style={{ width: "120px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
>
<ImageFrameFloater placement="bottom-end">
<ImageFrameIndicator>+9</ImageFrameIndicator>
</ImageFrameFloater>
</ImageFrame>
<Text color="palette.gray700" textStyle="t1Regular">
ImageFrameIndicator
</Text>
</VStack>
<VStack gap="x2" align="center">
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="Profile with reaction button overlay"
style={{ width: "120px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
>
<ImageFrameFloater placement="bottom-end">
<ImageFrameReactionButton
pressed={liked}
onPressedChange={setLiked}
accessibility-label="좋아요"
/>
</ImageFrameFloater>
</ImageFrame>
<Text color="palette.gray700" textStyle="t1Regular">
ImageFrameReactionButton
</Text>
</VStack>
</HStack>
);
}Multiple Overlays
여러 위치에 오버레이를 동시에 배치할 수 있습니다.
Lynx 예제를 불러오는 중입니다.
import "./styles";
import {
ImageFrame,
ImageFrameFloater,
ImageFrameBadge,
ImageFrameReactionButton,
} from "@seed-design/lynx-react";
import { useState } from "@lynx-js/react";
export default function ImageFrameOverlayMultipleExample() {
const [liked, setLiked] = useState(false);
return (
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="Profile with multiple overlays"
style={{ width: "200px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
>
<ImageFrameFloater placement="top-start">
<ImageFrameBadge tone="brand" variant="solid">
NEW
</ImageFrameBadge>
</ImageFrameFloater>
<ImageFrameFloater placement="bottom-end">
<ImageFrameReactionButton
pressed={liked}
onPressedChange={setLiked}
accessibility-label="좋아요"
/>
</ImageFrameFloater>
</ImageFrame>
);
}Custom Overlay
ImageFrameFloater는 커스텀 UI도 children으로 받습니다.
Lynx 예제를 불러오는 중입니다.
import "./styles";
import { ImageFrame, ImageFrameFloater } from "@seed-design/lynx-react";
export default function ImageFrameOverlayCustomExample() {
return (
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="Profile with custom overlay"
style={{ width: "200px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
>
<ImageFrameFloater placement="bottom-end">
<view
style={{
padding: "4px 8px",
backgroundColor: "rgba(0, 0, 0, 0.6)",
borderRadius: "4px",
color: "white",
fontSize: "12px",
}}
>
<text className="image-frame-custom-label">Custom Element</text>
</view>
</ImageFrameFloater>
</ImageFrame>
);
}Offset
offsetX와 offsetY로 가로와 세로 여백을 지정합니다. 0, SEED 간격 토큰, CSS 길이를 사용할 수 있습니다. 가장자리의 기본 여백은 6px이며, 중앙 축의 기본 오프셋은 0입니다.
import { ImageFrame, ImageFrameFloater, ImageFrameIndicator } from "@seed-design/lynx-react";
<ImageFrame src={imageUrl} alt="상품 이미지" width="150px" ratio={1}>
<ImageFrameFloater placement="bottom-end" offsetX="12px" offsetY="12px">
<ImageFrameIndicator>+9</ImageFrameIndicator>
</ImageFrameFloater>
</ImageFrame>Lynx 예제를 불러오는 중입니다.
import "./styles";
import { ImageFrame, ImageFrameFloater, ImageFrameIndicator } from "@seed-design/lynx-react";
export default function ImageFrameOverlayInsetExample() {
return (
<view style={{ display: "flex", flexDirection: "row", flexWrap: "wrap", gap: "12px" }}>
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="Profile with default offset"
style={{ width: "150px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
>
<ImageFrameFloater placement="bottom-end">
<ImageFrameIndicator>default</ImageFrameIndicator>
</ImageFrameFloater>
</ImageFrame>
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="Profile with 0 offset"
style={{ width: "150px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
>
<ImageFrameFloater placement="bottom-end" offsetX={0} offsetY={0}>
<ImageFrameIndicator>offset=0</ImageFrameIndicator>
</ImageFrameFloater>
</ImageFrame>
<ImageFrame
ratio={1}
borderRadius="r2"
stroke
src="https://avatars.githubusercontent.com/u/54893898?v=4"
alt="Profile with 12 offset"
style={{ width: "150px" }}
fallback={
<view className="image-frame-fallback">
<text className="image-frame-fallback-label">이미지</text>
</view>
}
>
<ImageFrameFloater placement="bottom-end" offsetX="12px" offsetY="12px">
<ImageFrameIndicator>offset=12</ImageFrameIndicator>
</ImageFrameFloater>
</ImageFrame>
</view>
);
}Overlay Props
Prop
Type
placementNonNullable<"top-start" | "top-center" | "top-end" | "middle-start" | "middle-center" | "middle-end" | "bottom-start" | "bottom-center" | "bottom-end" | undefined>offsetX?0 | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | (string & {}) | "full" | undefinedoffsetY?0 | Dimension | "spacingX.betweenChips" | "spacingX.globalGutter" | "spacingY.componentDefault" | "spacingY.navToTitle" | "spacingY.screenBottom" | "spacingY.betweenText" | (string & {}) | "full" | undefinedzIndex?Property.ZIndex | undefinedProp
Type
Prop
Type
svgReact.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>>Prop
Type
childrenReact.ReactNodeProp
Type
pressed?boolean | undefineddefaultPressed?boolean | undefinedonPressedChange?((pressed: boolean) => void) | undefinedLast updated on