HoverCard

Feedback & Status

호버하거나 포커스하면 부가 정보를 띄우는 패널.

Usage

호버/포커스로 가볍게 띄우는 부가 정보 패널(배경 스크롤을 막지 않음). 눌러서 조작하는 패널은 Popover

import

import

import { HoverCard } from '@mildang/design-system/HoverCard';

예제를 복사해 쓸 때 필요한 준비

@mildang/styled-system 은 이 저장소에서 Panda 가 생성하는 산출물이다. 저장소 안에서는 turbo run ship 이후 쓸 수 있고, 패키지 소비자는 자기 Panda 산출물이나 다른 레이아웃 수단으로 바꿔야 한다.

API Reference

HoverCard Props

Prop

Type

Default

children

ReactNode

지정 안 함

container

Element | DocumentFragment

지정 안 함

content

ReactNode

지정 안 함

ContentProps

Omit<HoverCardContentProps & RefAttributes<HTMLDivElement>, "ref">

지정 안 함

hideArrow

boolean

false

sx

SystemStyleObject

지정 안 함

TriggerProps

Omit<HoverCardTriggerProps & RefAttributes<HTMLAnchorElement>, "ref">

지정 안 함

type

"standard" | "custom"

standard

너비 방식 — standard vs custom

type="standard"(기본)는 너비가 312px 로 고정된다. 다만 항상 가용 폭 상한이 함께 걸려 있어서, 뷰포트가 312px 보다 좁아지면 패널도 그 폭에 맞춰 함께 줄어든다.

type="custom" 은 폭을 지정하지 않아 content 가 필요한 만큼 넓어진다. 콘텐츠가 길거나 폭을 직접 잡아야 하면 HoverCard 에 maxWidth="500px" 처럼 panda style prop 을 얹어 상한만 준다.

예제

기본 사용

마우스를 올리면 뜨는 카드다. 클릭으로 열어야 하면 Popover 를 쓴다.

import type { ComponentProps } from 'react';
import { Button } from '@mildang/design-system/Button';
import { HoverCard } from '@mildang/design-system/HoverCard';

export type HoverCardExampleProps = Pick<ComponentProps<typeof HoverCard>, 'type' | 'hideArrow'>;

export default function HoverCardExample({ type = 'standard', hideArrow = false }: HoverCardExampleProps) {
  return (
    <HoverCard
      type={type}
      hideArrow={hideArrow}
      content={<div>호버 시 노출되는 부가 정보 카드입니다.</div>}
      ContentProps={{ 'aria-label': 'Default HoverCard' }}
    >
      <Button variant="tertiary">Hover me</Button>
    </HoverCard>
  );
}

너비 방식

standard 고정폭과 custom 콘텐츠 너비를 비교한다.

코드

import { Button } from '@mildang/design-system/Button';
import { HoverCard } from '@mildang/design-system/HoverCard';
import { css } from '@mildang/styled-system/css';

const LONG_TEXT =
  '이 텍스트는 maxWidth 한계에서 줄바꿈이 일어나는지 확인하기 위한 두세 줄 길이의 본문입니다.';

export default function HoverCardTypeExample() {
  return (
    <div className={css({ display: 'flex', flexDirection: 'column', gap: '16px', alignItems: 'flex-start' })}>
      <HoverCard
        type="standard"
        content={<div>짧은 콘텐츠 (standard 312px)</div>}
        ContentProps={{ side: 'right', 'aria-label': 'Standard 짧은 콘텐츠' }}
      >
        <Button variant="tertiary">standard (312px)</Button>
      </HoverCard>
      <HoverCard
        type="standard"
        content={<div>{LONG_TEXT}</div>}
        ContentProps={{ side: 'right', 'aria-label': 'Standard 긴 콘텐츠' }}
      >
        <Button variant="tertiary">standard — 긴 콘텐츠</Button>
      </HoverCard>
      <HoverCard
        type="custom"
        maxWidth="500px"
        content={<div>짧은 콘텐츠 (custom maxWidth 500px)</div>}
        ContentProps={{ side: 'right', 'aria-label': 'Custom 짧은 콘텐츠' }}
      >
        <Button variant="tertiary">custom (maxWidth 500)</Button>
      </HoverCard>
      <HoverCard
        type="custom"
        maxWidth="500px"
        content={<div>{LONG_TEXT}</div>}
        ContentProps={{ side: 'right', 'aria-label': 'Custom 긴 콘텐츠' }}
      >
        <Button variant="tertiary">custom (maxWidth 500) — 긴 콘텐츠</Button>
      </HoverCard>
    </div>
  );
}

위치

방향과 정렬을 조합한 12개 위치를 비교한다.

코드

import { Button } from '@mildang/design-system/Button';
import { HoverCard } from '@mildang/design-system/HoverCard';
import { css } from '@mildang/styled-system/css';

const POSITIONS = [
  { key: 'top-start', label: 'TOP-START', side: 'top', align: 'start', gridColumn: 1, gridRow: 1 },
  { key: 'top', label: 'TOP', side: 'top', align: 'center', gridColumn: 2, gridRow: 1 },
  { key: 'top-end', label: 'TOP-END', side: 'top', align: 'end', gridColumn: 3, gridRow: 1 },
  { key: 'left-start', label: 'LEFT-START', side: 'left', align: 'start', gridColumn: 1, gridRow: 3 },
  { key: 'left', label: 'LEFT', side: 'left', align: 'center', gridColumn: 1, gridRow: 5 },
  { key: 'left-end', label: 'LEFT-END', side: 'left', align: 'end', gridColumn: 1, gridRow: 7 },
  { key: 'right-start', label: 'RIGHT-START', side: 'right', align: 'start', gridColumn: 3, gridRow: 3 },
  { key: 'right', label: 'RIGHT', side: 'right', align: 'center', gridColumn: 3, gridRow: 5 },
  { key: 'right-end', label: 'RIGHT-END', side: 'right', align: 'end', gridColumn: 3, gridRow: 7 },
  { key: 'bottom-start', label: 'BOTTOM-START', side: 'bottom', align: 'start', gridColumn: 1, gridRow: 9 },
  { key: 'bottom', label: 'BOTTOM', side: 'bottom', align: 'center', gridColumn: 2, gridRow: 9 },
  { key: 'bottom-end', label: 'BOTTOM-END', side: 'bottom', align: 'end', gridColumn: 3, gridRow: 9 },
] as const;

export default function HoverCardPositionExample() {
  return (
    <div
      className={css({
        position: 'relative',
        width: '1200px',
        padding: '100px',
        display: 'grid',
        gridTemplateColumns: '1fr 1fr 1fr',
        gridTemplateRows: 'auto 16px auto 120px auto 120px auto 16px auto',
        columnGap: '16px',
      })}
    >
      {POSITIONS.map(({ key, label, side, align, gridColumn, gridRow }) => (
        <div key={key} style={{ gridColumn, gridRow }}>
          <HoverCard
            content={<div className={css({ width: '250px' })}>{label} 위치 HoverCard</div>}
            ContentProps={{
              side,
              align,
              'aria-label': `${label} HoverCard`,
            }}
          >
            <Button variant="tertiary">{label}</Button>
          </HoverCard>
        </div>
      ))}
    </div>
  );
}