HoverCard | CoUI

HoverCard

트리거 요소에 호버 시 추가 정보를 표시하는 카드 컴포넌트

HoverCard#

트리거 요소 위에 마우스를 올리면 추가 정보를 담은 카드를 표시하는 컴포넌트입니다. 지연 시간을 조절하여 의도치 않은 표시를 방지할 수 있습니다.

Live Preview#

Web
Flutter
Loading Flutter...
class HoverCardDefaultExample extends StatelessComponent {
  const HoverCardDefaultExample({super.key});

  @override
  Component build(BuildContext context) {
    return CoHoverCard(
      trigger: span(
        [const Text('@coui').bodyMedium.primary.underline],
        classes: 'cursor-pointer',
      ),
      content: const Text('크로스 플랫폼 UI 디자인 시스템.').bodySmall.onSurface,
      placement: CorePopoverPlacement.bottom,
    );
  }
}
class HoverCardDefaultExample extends StatefulWidget {
  const HoverCardDefaultExample({super.key});

  @override
  State<HoverCardDefaultExample> createState() =>
      _HoverCardDefaultExampleState();
}

class _HoverCardDefaultExampleState extends State<HoverCardDefaultExample> {
  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    return CoHoverCard(
      trigger: Text(
        '@coui',
        style: theme.typography.bodyMedium.copyWith(
          color: theme.colorScheme.primary,
          decoration: TextDecoration.underline,
          decorationColor: theme.colorScheme.primary,
        ),
      ),
      content: Text(
        '크로스 플랫폼 UI 디자인 시스템.',
        style: theme.typography.bodySmall.copyWith(
          color: theme.colorScheme.onSurface,
        ),
      ),
      placement: CorePopoverPlacement.bottom,
    );
  }
}

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 사용자 멘션(@username), 링크 등에 호버 시 미리보기 카드를 표시할 때
  • 아이콘이나 축약된 텍스트에 호버 시 상세 정보를 보여줄 때
  • 클릭 없이 추가 맥락 정보를 제공하고 싶을 때

대신 다른 컴포넌트를 사용하세요:

  • Tooltip: 짧은 텍스트 힌트만 표시할 때 (복잡한 카드 내용이 아닌 경우)
  • Popover: 클릭으로 표시되고 사용자 인터랙션이 필요한 오버레이에
  • 터치 기기 사용자에게는 HoverCard가 작동하지 않으므로 대안 UI 고려

기본 사용법 (Basic Usage)#

// 기본 호버 카드
CoHoverCard(
  trigger: Text('@nextjs'),
  content: Text('The React Framework for the Web.'),
)

// 표시 위치 및 지연 시간 설정
CoHoverCard(
  trigger: Icon(Icons.info_outline),
  content: Text('추가 정보를 여기에 표시합니다.'),
  placement: CorePopoverPlacement.top,
  openDelay: Duration(milliseconds: 500),
  closeDelay: Duration(milliseconds: 200),
)
CoHoverCard(
  trigger: Component.text('@nextjs'),
  content: Component.text('The React Framework for the Web.'),
)

CoHoverCard(
  trigger: Component.text('정보'),
  content: Component.text('추가 정보'),
  placement: CorePopoverPlacement.top,
  openDelay: Duration(milliseconds: 300),
  closeDelay: Duration(milliseconds: 150),
)

Props / Parameters#

속성타입기본값설명
trigger Widget / Component 필수 호버를 감지할 트리거
content Widget / Component 필수 호버 시 표시할 카드 내용
placement CorePopoverPlacement bottom 카드가 표시될 방향
openDelay Duration? 500ms (CoreDuration.tooltipDelay) 카드 표시까지의 지연 시간
closeDelay Duration? 500ms (CoreDuration.tooltipDelay) 카드 닫힘까지의 지연 시간
onClose VoidCallback? null 카드가 닫힐 때 호출

변형 (Variants)#

방향 (Placement)#

카드가 트리거 기준으로 표시될 방향을 지정합니다.

// 상단 표시
CoHoverCard(
  trigger: Text('상단'),
  content: Text('상단에 표시'),
  placement: CorePopoverPlacement.top,
)

// 우측 표시
CoHoverCard(
  trigger: Text('우측'),
  content: Text('우측에 표시'),
  placement: CorePopoverPlacement.right,
)

// 좌측 표시
CoHoverCard(
  trigger: Text('좌측'),
  content: Text('좌측에 표시'),
  placement: CorePopoverPlacement.left,
)

// 하단 표시 (기본)
CoHoverCard(
  trigger: Text('하단'),
  content: Text('하단에 표시'),
  placement: CorePopoverPlacement.bottom,
)

동작 스펙 (Behavior)#

인터랙션#

  • 호버 진입: openDelay 후 카드 표시
  • 호버 이탈: closeDelay 후 카드 닫힘
  • 카드로 마우스 이동: 카드가 열린 상태 유지 (사용자가 카드와 인터랙션 가능)
  • 터치: Flutter는 길게 누르기로 열림, Web은 기본적으로 비활성

상태 전환#

  • hiddenvisible: openDelay 이후 페이드 인
  • visiblehidden: closeDelay 이후 페이드 아웃

애니메이션#

양 플랫폼 모두 동일한 토큰을 사용하는 Fade + Scale 트랜지션입니다.

  • Open: 150 ms linear (AnimationConstants.short)
  • Close: 67 ms 가시 모션 (100 ms wall-clock × Interval(0, 2/3))
  • Scale: 0.9 ↔ 1.0 (CorePopoverTokens.scaleClosed / scaleOpen)
  • Open delay / Close delay: CoreHoverCardTokens.openDelayMs / closeDelayMs (각 500 ms 기본). 트리거 hover-enter 후 openDelay 만큼 지연된 뒤 카드 표시 / hover-leave 후 closeDelay 만큼 유예 후 닫힘 (그 동안 패널 안으로 진입하면 닫힘 취소)

사용 가이드라인 (Usage Guidelines)#

✅ Do#

openDelay를 충분히 설정해 의도치 않은 표시 방지

CoHoverCard(
  trigger: Text('@username'),
  content: UserProfileCard(username: 'username'),
  openDelay: const Duration(milliseconds: 500),
)

너무 짧은 openDelay는 마우스가 지나갈 때마다 카드가 표시되어 방해가 된다.


❌ Don't#

터치 기기에서만 사용되는 UI에 HoverCard 적용

// ❌ 모바일 앱의 주요 정보를 HoverCard에만 표시
CoHoverCard(
  trigger: Text('자세히'),
  content: ImportantInfo(), // 터치 사용자는 볼 수 없음
)

HoverCard는 마우스 호버가 가능한 환경에서만 동작하므로 중요 정보는 다른 방법으로도 접근 가능해야 한다.

✅ Do#

카드 내용에 사용자 프로필, 링크 미리보기 등 풍부한 정보 제공

CoHoverCard(
  trigger: Text('@hong_gildong', style: linkStyle),
  content: Column(
    mainAxisSize: MainAxisSize.min,
    children: [
      CoAvatar(imageUrl: user.avatarUrl),
      Text(user.name, style: titleStyle),
      Text(user.bio),
      Row(children: [
        Text('팔로워: ${user.followers}'),
        const Gap.md(),
        Text('팔로잉: ${user.following}'),
      ]),
    ],
  ),
)

HoverCard는 Tooltip보다 많은 정보를 담을 수 있어 풍부한 미리보기에 적합하다.


❌ Don't#

필수 액션(버튼, 폼)을 HoverCard에만 배치하지 않기

// ❌ 호버해야만 볼 수 있는 중요한 액션
CoHoverCard(
  trigger: Icon(Icons.settings),
  content: Column(
    children: [
      CoButton(onPressed: handleDeletePressed, child: Text('계정 삭제')),
    ],
  ),
)

호버에서만 접근 가능한 액션은 키보드 사용자와 터치 사용자가 접근할 수 없다.

접근성 (Accessibility)#

키보드 인터랙션#

동작
Tab트리거 요소로 포커스 이동 시 카드 표시
Escape열린 카드 닫기

스크린 리더#

  • Flutter: Semantics(tooltip: ...) 또는 별도 읽기 영역으로 카드 내용 접근 가능
  • Web: role="dialog" + aria-describedby로 트리거와 카드 내용 연결

터치 타겟#

  • 트리거 요소 최소 크기: 44x44dp
  • 카드가 열려있는 경우 카드 영역도 터치 가능

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
클래스명CoHoverCardCoHoverCard
호버 감지 MouseRegion 위젯 JS mouseenter / mouseleave 이벤트
위치 계산 PopoverController 기반 root Overlay 마운트 CoOverlayHost.popover 레이어 portal + position: fixed + viewport 좌표
클리핑영향 없음 (root Overlay)영향 없음 (overlay host portal)
터치 지원길게 누르기로 활성화기본 비활성
Open 애니메이션 150 ms linear, scale 0.9→1.0 150 ms linear, scale 0.9→1.0 — 동일
Close 애니메이션 67 ms 가시 (Interval(0, 2/3)) 67 ms 가시 (transition-duration 단축) — 동일
Open / Close delay CoreHoverCardTokens.openDelayMs / closeDelayMs (각 500 ms) CoreHoverCardTokens.openDelayMs / closeDelayMs (각 500 ms) — 동일
  • Popover: 클릭으로 열리는 더 복잡한 오버레이
  • Tooltip: 짧은 텍스트 힌트를 호버 시 표시

조합 예제#

// 소셜 피드에서 사용자 멘션 호버 카드
RichText(
  text: TextSpan(
    children: [
      const TextSpan(text: '안녕하세요, '),
      WidgetSpan(
        child: CoHoverCard(
          trigger: Text('@홍길동', style: mentionStyle),
          content: UserHoverCard(username: '홍길동'),
          openDelay: const Duration(milliseconds: 400),
        ),
      ),
      const TextSpan(text: ' 반가워요!'),
    ],
  ),
)