본문으로 건너뛰기
블록

사업자 정보

전자상거래법 표시 의무에 맞춘 푸터의 사업자 정보 블록이에요.

  • #arc
  • #block
  • #footer
  • #legal
  • #business
  • #commerce
  • #korea

설치

이 명령 한 줄이면 필요한 파일과 패키지가 함께 설치돼요.

터미널bash
npx shadcn@latest add https://dsui.vercel.app/r/business-info.json

business-info.module.csscss
.block { display: flex; flex-direction: column; gap: var(--space-3); }
.title { margin: 0; color: var(--text-secondary); font-size: var(--text-xs); font-weight: var(--label-weight); }
/* The lines run on like a sentence and wrap, rather than making a table of two long columns in a footer. */
.lines { display: flex; flex-wrap: wrap; margin: 0; padding: 0; gap: var(--space-2) var(--space-6); list-style: none; }
.line { display: inline-flex; gap: var(--space-2); font-size: var(--text-sm); line-height: 1.6; }
/* The label is the quieter half: the reader already knows what they came for and is looking for the value. */
.label { color: var(--text-secondary); }
.value { color: var(--foreground); }
.link { color: var(--accent); text-decoration: none; }
@media (hover: hover) and (pointer: fine) { .link:hover { text-decoration: underline; text-underline-offset: 3px; } }
/* Press feedback: the same answer the hover gives, so touch and mouse both respond. */
.link:active { color: var(--accent-strong); text-decoration: underline; text-underline-offset: 3px; }
business-info.tsxtsx
import type { HTMLAttributes } from "react";
import styles from "./business-info.module.css";

export interface BusinessInfoLine {
  label: string;
  value: string;
  /** Makes the value a link: `tel:` for a phone, `mailto:` for an address. */
  href?: string;
}

export interface BusinessInfoProps extends HTMLAttributes<HTMLElement> {
  lines: BusinessInfoLine[];
  /** The block's heading, also its accessible name. */
  title?: string;
}

const present = (value: string | null | undefined): value is string => Boolean(value && value.trim());

/**
 * Builds the lines from a set of facts, dropping any that is blank. A label with nothing after it is worse than no
 * line at all: it reads as missing information rather than information that does not apply.
 */
export function businessInfoLines(facts: {
  tradeName?: string | null;
  representative?: string | null;
  registrationNumber?: string | null;
  mailOrderNumber?: string | null;
  address?: string | null;
  phone?: string | null;
  phoneTel?: string | null;
  email?: string | null;
}, labels: Partial<Record<"tradeName" | "representative" | "registrationNumber" | "mailOrderNumber" | "address" | "phone" | "email", string>> = {}): BusinessInfoLine[] {
  const lines: BusinessInfoLine[] = [];
  const add = (label: string, value: string | null | undefined, href?: string) => {
    if (present(value)) lines.push(href ? { label, value: value.trim(), href } : { label, value: value.trim() });
  };
  add(labels.tradeName ?? "상호", facts.tradeName);
  add(labels.representative ?? "대표", facts.representative);
  add(labels.registrationNumber ?? "사업자등록번호", facts.registrationNumber);
  add(labels.mailOrderNumber ?? "통신판매업 신고번호", facts.mailOrderNumber);
  add(labels.address ?? "주소", facts.address);
  add(labels.phone ?? "전화", facts.phone, present(facts.phoneTel) ? `tel:${facts.phoneTel.trim()}` : undefined);
  add(labels.email ?? "이메일", facts.email, present(facts.email) ? `mailto:${facts.email.trim()}` : undefined);
  return lines;
}

/**
 * The seller's details in a footer, as Korean e-commerce law requires (전자상거래법 표시 의무). The label is the
 * quieter half: someone looking here already knows what they came for and is scanning for the value.
 */
export function BusinessInfo({ className, lines, title = "사업자 정보", ...props }: BusinessInfoProps) {
  if (lines.length === 0) return null;
  return (
    <section {...props} aria-label={title} className={[styles.block, className].filter(Boolean).join(" ")}>
      <p className={styles.title}>{title}</p>
      <ul className={styles.lines}>
        {lines.map(line => (
          <li key={line.label} className={styles.line}>
            <span className={styles.label}>{line.label}</span>
            {line.href ? <a href={line.href} className={styles.link}>{line.value}</a> : <span className={styles.value}>{line.value}</span>}
          </li>
        ))}
      </ul>
    </section>
  );
}

BusinessInfo.displayName = "BusinessInfo";

export default BusinessInfo;