Popover

Feedback & Status

트리거 자리에서 짧은 정보나 설정을 띄우는 작은 패널.

Usage

클릭으로 열어 그 자리에서 짧은 정보·간단한 설정을 끝내고 닫는 작은 패널. 호버로 띄우는 부가 정보는 HoverCard, 한 줄 설명은 Tooltip

import

import

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

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

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

API Reference

Popover Props

Prop

Type

Default

children

ReactNode

지정 안 함

container

Element | DocumentFragment

지정 안 함

content

ReactNode

지정 안 함

ContentProps

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

지정 안 함

defaultValue

boolean

false

hideArrow

boolean

false

modal

boolean

true

sx

SystemStyleObject

지정 안 함

TriggerProps

Omit<PopoverTriggerProps & RefAttributes<HTMLButtonElement>, "ref">

지정 안 함

type

"standard" | "custom"

standard

너비 방식 — standard vs custom

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

type="custom" 은 폭을 지정하지 않아 content 가 필요한 만큼 넓어진다. 콘텐츠가 길거나 폭을 직접 잡아야 하면 Popover 에 maxWidth="500px" 처럼 panda style prop 을 얹어 상한만 준다. 그래도 복잡해지면 Popover 대신 Dialog 로 올린다.

예제

기본 사용

버튼을 눌러 여는 실제 사용 형태다. 호버로 열어야 하면 HoverCard 를 쓴다.

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

export type PopoverDefaultExampleProps = Pick<ComponentProps<typeof Popover>, 'type' | 'hideArrow'>;

export default function PopoverExample({ type = 'standard', hideArrow = false }: PopoverDefaultExampleProps) {
  return (
    <Popover
      type={type}
      hideArrow={hideArrow}
      content={<div>팝오버 내용을 자유롭게 채울 수 있습니다.</div>}
      ContentProps={{ 'aria-label': 'Default 팝오버' }}
    >
      <Button variant="tertiary">Open Popover</Button>
    </Popover>
  );
}

Type

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

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

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

MinMaxWidth

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

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

export default function PopoverMinMaxWidthExample() {
  return (
    <div className={css({ display: 'flex', flexDirection: 'column', gap: '16px', alignItems: 'flex-start' })}>
      <Popover type="custom" minWidth="320px" content={<div>짧은 콘텐츠 (minWidth 320만)</div>} ContentProps={{ side: 'right', 'aria-label': 'custom minWidth 320 짧은 콘텐츠' }}>
        <Button variant="tertiary">custom + minWidth 320 (짧은)</Button>
      </Popover>
      <Popover type="custom" minWidth="320px" content={<div>{LONG_TEXT}</div>} ContentProps={{ side: 'right', 'aria-label': 'custom minWidth 320 긴 콘텐츠' }}>
        <Button variant="tertiary">custom + minWidth 320 (긴)</Button>
      </Popover>
      <Popover type="custom" minWidth="320px" maxWidth="400px" content={<div>{LONG_TEXT}</div>} ContentProps={{ side: 'right', 'aria-label': 'custom minWidth 320 maxWidth 400' }}>
        <Button variant="tertiary">custom + minWidth 320 + maxWidth 400</Button>
      </Popover>
    </div>
  );
}

Position

import { Button } from '@mildang/design-system/Button';
import { Popover } from '@mildang/design-system/Popover';
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 PopoverPositionExample() {
  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 }}>
          <Popover content={<div className={css({ width: '250px' })}>{label} 위치 팝오버</div>} ContentProps={{ side, align, 'aria-label': `${label} 팝오버` }}>
            <Button variant="tertiary">{label}</Button>
          </Popover>
        </div>
      ))}
    </div>
  );
}