/** * @title Tile * @description 이모지·로고·아이콘 같은 에셋을 모서리가 둥근 정사각형 안에 담아 하나의 대상을 나타내요. */import * as React from 'react';import { cn } from '@/lib/utils';// Atlassian @atlaskit/tile 스펙 (tile.tsx 해독 값)export type TileSize = 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'large' | 'xlarge';export type TileBackgroundColor = | 'color.background.accent.lime.subtlest' | 'color.background.accent.lime.subtler' | 'color.background.accent.lime.subtle' | 'color.background.accent.lime.bolder' | 'color.background.accent.red.subtlest' | 'color.background.accent.red.subtler' | 'color.background.accent.red.subtle' | 'color.background.accent.red.bolder' | 'color.background.accent.orange.subtlest' | 'color.background.accent.orange.subtler' | 'color.background.accent.orange.subtle' | 'color.background.accent.orange.bolder' | 'color.background.accent.yellow.subtlest' | 'color.background.accent.yellow.subtler' | 'color.background.accent.yellow.subtle' | 'color.background.accent.yellow.bolder' | 'color.background.accent.green.subtlest' | 'color.background.accent.green.subtler' | 'color.background.accent.green.subtle' | 'color.background.accent.green.bolder' | 'color.background.accent.teal.subtlest' | 'color.background.accent.teal.subtler' | 'color.background.accent.teal.subtle' | 'color.background.accent.teal.bolder' | 'color.background.accent.blue.subtlest' | 'color.background.accent.blue.subtler' | 'color.background.accent.blue.subtle' | 'color.background.accent.blue.bolder' | 'color.background.accent.purple.subtlest' | 'color.background.accent.purple.subtler' | 'color.background.accent.purple.subtle' | 'color.background.accent.purple.bolder' | 'color.background.accent.magenta.subtlest' | 'color.background.accent.magenta.subtler' | 'color.background.accent.magenta.subtle' | 'color.background.accent.magenta.bolder' | 'color.background.accent.gray.subtlest' | 'color.background.accent.gray.subtler' | 'color.background.accent.gray.subtle' | 'color.background.accent.gray.bolder' | 'color.background.inverse.subtle' | 'color.background.neutral' | 'color.background.neutral.bold' | 'color.background.brand.subtlest' | 'color.background.brand.bold' | 'color.background.brand.boldest' | 'color.background.danger' | 'color.background.danger.bold' | 'color.background.warning' | 'color.background.warning.bold' | 'color.background.success' | 'color.background.success.bold' | 'color.background.discovery' | 'color.background.discovery.bold' | 'color.background.information' | 'color.background.information.bold' | 'transparent' | 'white' | 'black';/** 크기별 픽셀 값 */export const TILE_SIZES: Record<TileSize, number> = { xxsmall: 16, xsmall: 20, small: 24, medium: 32, large: 40, xlarge: 48,};// width / height / font-size (이모지 같은 텍스트 에셋 크기)const sizeClass: Record<TileSize, string> = { xxsmall: 'size-4 text-[10px]', xsmall: 'size-5 text-[12px]', small: 'size-6 text-[14px]', medium: 'size-8 text-[16px]', large: 'size-10 text-[20px]', xlarge: 'size-12 text-[24px]',};// isInset 일 때 안쪽 에셋 너비 (높이는 auto)const insetSizeClass: Record<TileSize, string> = { xxsmall: '[&_:is(div,img,span,svg)]:w-[10px]', xsmall: '[&_:is(div,img,span,svg)]:w-[12px]', small: '[&_:is(div,img,span,svg)]:w-[14px]', medium: '[&_:is(div,img,span,svg)]:w-[16px]', large: '[&_:is(div,img,span,svg)]:w-[20px]', xlarge: '[&_:is(div,img,span,svg)]:w-[24px]',};export const tileBackgroundClass: Record<TileBackgroundColor, string> = { 'color.background.accent.lime.subtlest': 'bg-ds-background-accent-lime-subtlest', 'color.background.accent.lime.subtler': 'bg-ds-background-accent-lime-subtler', 'color.background.accent.lime.subtle': 'bg-ds-background-accent-lime-subtle', 'color.background.accent.lime.bolder': 'bg-ds-background-accent-lime-bolder', 'color.background.accent.red.subtlest': 'bg-ds-background-accent-red-subtlest', 'color.background.accent.red.subtler': 'bg-ds-background-accent-red-subtler', 'color.background.accent.red.subtle': 'bg-ds-background-accent-red-subtle', 'color.background.accent.red.bolder': 'bg-ds-background-accent-red-bolder', 'color.background.accent.orange.subtlest': 'bg-ds-background-accent-orange-subtlest', 'color.background.accent.orange.subtler': 'bg-ds-background-accent-orange-subtler', 'color.background.accent.orange.subtle': 'bg-ds-background-accent-orange-subtle', 'color.background.accent.orange.bolder': 'bg-ds-background-accent-orange-bolder', 'color.background.accent.yellow.subtlest': 'bg-ds-background-accent-yellow-subtlest', 'color.background.accent.yellow.subtler': 'bg-ds-background-accent-yellow-subtler', 'color.background.accent.yellow.subtle': 'bg-ds-background-accent-yellow-subtle', 'color.background.accent.yellow.bolder': 'bg-ds-background-accent-yellow-bolder', 'color.background.accent.green.subtlest': 'bg-ds-background-accent-green-subtlest', 'color.background.accent.green.subtler': 'bg-ds-background-accent-green-subtler', 'color.background.accent.green.subtle': 'bg-ds-background-accent-green-subtle', 'color.background.accent.green.bolder': 'bg-ds-background-accent-green-bolder', 'color.background.accent.teal.subtlest': 'bg-ds-background-accent-teal-subtlest', 'color.background.accent.teal.subtler': 'bg-ds-background-accent-teal-subtler', 'color.background.accent.teal.subtle': 'bg-ds-background-accent-teal-subtle', 'color.background.accent.teal.bolder': 'bg-ds-background-accent-teal-bolder', 'color.background.accent.blue.subtlest': 'bg-ds-background-accent-blue-subtlest', 'color.background.accent.blue.subtler': 'bg-ds-background-accent-blue-subtler', 'color.background.accent.blue.subtle': 'bg-ds-background-accent-blue-subtle', 'color.background.accent.blue.bolder': 'bg-ds-background-accent-blue-bolder', 'color.background.accent.purple.subtlest': 'bg-ds-background-accent-purple-subtlest', 'color.background.accent.purple.subtler': 'bg-ds-background-accent-purple-subtler', 'color.background.accent.purple.subtle': 'bg-ds-background-accent-purple-subtle', 'color.background.accent.purple.bolder': 'bg-ds-background-accent-purple-bolder', 'color.background.accent.magenta.subtlest': 'bg-ds-background-accent-magenta-subtlest', 'color.background.accent.magenta.subtler': 'bg-ds-background-accent-magenta-subtler', 'color.background.accent.magenta.subtle': 'bg-ds-background-accent-magenta-subtle', 'color.background.accent.magenta.bolder': 'bg-ds-background-accent-magenta-bolder', 'color.background.accent.gray.subtlest': 'bg-ds-background-accent-gray-subtlest', 'color.background.accent.gray.subtler': 'bg-ds-background-accent-gray-subtler', 'color.background.accent.gray.subtle': 'bg-ds-background-accent-gray-subtle', 'color.background.accent.gray.bolder': 'bg-ds-background-accent-gray-bolder', 'color.background.inverse.subtle': 'bg-ds-background-inverse-subtle', 'color.background.neutral': 'bg-ds-background-neutral', 'color.background.neutral.bold': 'bg-ds-background-neutral-bold', 'color.background.brand.subtlest': 'bg-ds-background-brand-subtlest', 'color.background.brand.bold': 'bg-ds-background-brand-bold', 'color.background.brand.boldest': 'bg-ds-background-brand-boldest', 'color.background.danger': 'bg-ds-background-danger', 'color.background.danger.bold': 'bg-ds-background-danger-bold', 'color.background.warning': 'bg-ds-background-warning', 'color.background.warning.bold': 'bg-ds-background-warning-bold', 'color.background.success': 'bg-ds-background-success', 'color.background.success.bold': 'bg-ds-background-success-bold', 'color.background.discovery': 'bg-ds-background-discovery', 'color.background.discovery.bold': 'bg-ds-background-discovery-bold', 'color.background.information': 'bg-ds-background-information', 'color.background.information.bold': 'bg-ds-background-information-bold', transparent: 'bg-transparent', // 색 모드와 무관하게 고정되는 정적 색 white: 'bg-white', black: 'bg-black',};export interface TileProps extends Omit<React.ComponentProps<'span'>, 'children'> { /** 접근성 이름. 주변 텍스트가 이미 설명하는 장식용 타일이면 빈 문자열로 둬요. */ label: string; /** 16px(xxsmall) ~ 48px(xlarge). 기본 medium(32px) */ size?: TileSize; /** 안쪽 여백을 두고 에셋을 작게 배치해요. 기본 true. 꽉 채우는 로고·이미지는 false */ isInset?: boolean; /** 1px 테두리. 기본 false */ hasBorder?: boolean; /** 배경 색 토큰. 기본 color.background.neutral */ backgroundColor?: TileBackgroundColor; /** 타일에 담을 에셋 (명사를 나타내는 아이콘·이모지·이미지) */ children?: React.ReactNode; testId?: string;}function Tile({ label, size = 'medium', isInset = true, hasBorder = false, backgroundColor, children, testId, className, ...props}: TileProps) { return ( <span data-slot="tile" data-size={size} data-testid={testId} aria-label={label !== '' ? label : undefined} role={label !== '' ? 'img' : undefined} className={cn( 'relative box-border inline-flex min-h-0 min-w-0 shrink-0 grow-0 items-center justify-center overflow-hidden rounded-ds-tile align-middle', sizeClass[size], isInset ? cn('[&_:is(div,img,span,svg)]:h-auto', insetSizeClass[size]) : '[&_:is(div,img,span,svg)]:size-full [&_img]:object-cover', tileBackgroundClass[backgroundColor || 'color.background.neutral'], hasBorder && 'border-(length:--ds-border-width) border-solid border-ds-border', className, )} {...props} > {children} </span> );}export interface TileSkeletonProps { size?: TileSize; /** 기본 color.skeleton 대신 쓸 색 */ color?: string; /** shimmeringEndColor 와 함께 쓰면 반짝임 끝 색 */ shimmeringEndColor?: string; isShimmering?: boolean; testId?: string; className?: string;}/** 타일 자리를 미리 잡아 레이아웃이 밀리지 않게 하는 로딩 상태예요. */function TileSkeleton({ size = 'medium', color, shimmeringEndColor, isShimmering = false, testId, className }: TileSkeletonProps) { return ( <span data-slot="tile-skeleton" data-testid={testId} aria-hidden style={{ backgroundColor: color, ...(isShimmering && shimmeringEndColor ? ({ '--tile-skeleton-end': shimmeringEndColor } as React.CSSProperties) : null), }} className={cn( 'inline-block shrink-0 rounded-ds-tile bg-ds-skeleton align-middle', sizeClass[size], isShimmering && 'animate-pulse', className, )} /> );}export { Tile, TileSkeleton };export default Tile;