File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
"use client";
import type { ReactNode, Ref } from "react";
import { cx } from "../../utils";
export type FoxLinkButtonType = "primary" | "secondary" | "tertiary";
export type FoxLinkButtonSize = "lg" | "md" | "sm";
export interface FoxLinkButtonProps {
type?: FoxLinkButtonType;
size: FoxLinkButtonSize;
/** 주면 `<a>`, 없으면 `<button>`으로 렌더한다. 방문 색은 앵커에서만 켜진다. */
href?: string;
target?: string;
/** 생략하고 `target="_blank"`를 주면 `noopener noreferrer`가 자동으로 붙는다. */
rel?: string;
/** 배치 조정용. 모양이 달라야 하면 여기 말고 `type`을 추가한다. */
className?: string;
/** 라벨 뒤에만 놓인다(시안에 앞 슬롯이 없다). 색은 라벨을 따라간다. */
icon?: ReactNode;
label: string;
disabled?: boolean;
/** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
hidden?: boolean;
onAction?: () => void;
/** 포인터가 올라오면 `true`, 벗어나면 `false`. */
onHoverChange?: (hovered: boolean) => void;
ref?: Ref<HTMLAnchorElement | HTMLButtonElement>;
}
const TYPE_CLASS: Record<FoxLinkButtonType, string> = {
primary: "fox-link-button--primary",
secondary: "fox-link-button--secondary",
tertiary: "fox-link-button--tertiary",
};
const SIZE_CLASS: Record<FoxLinkButtonSize, string> = {
lg: "fox-link-button--lg",
md: "fox-link-button--md",
sm: "fox-link-button--sm",
};
/**
* @fox 링크 버튼. 상태를 갖지 않는다.
*
* `href`를 주면 진짜 앵커라 새 탭 열기·주소 복사 같은 브라우저 기본 동작이 살아 있고
* 방문 색(`:visited`)도 켜진다. 방문 여부는 브라우저만 알아 JS로는 읽을 수 없다.
*
* ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
* (또는 개별 파티셜)로 한 번 불러와야 한다.
*/
export function FoxLinkButton({
type = "primary",
size,
href,
target,
rel,
className,
icon,
label,
disabled = false,
hidden = false,
onAction,
onHoverChange,
ref,
}: FoxLinkButtonProps) {
if (hidden) {
return null;
}
const classes = cx("fox-link-button", TYPE_CLASS[type], SIZE_CLASS[size], className);
const onMouseEnter = onHoverChange ? () => onHoverChange(true) : undefined;
const onMouseLeave = onHoverChange ? () => onHoverChange(false) : undefined;
const body = (
<>
<span className="fox-link-button__label">{label}</span>
{icon && (
<span className="fox-link-button__icon" aria-hidden="true">
{icon}
</span>
)}
</>
);
if (href !== undefined) {
return (
<a
// 렌더 요소가 갈리므로 호출부가 넘긴 ref를 이 분기의 타입으로 좁힌다.
ref={ref as Ref<HTMLAnchorElement>}
// 비활성 앵커에는 href를 두지 않는다 — 포커스와 클릭이 함께 사라진다.
href={disabled ? undefined : href}
target={target}
rel={rel ?? (target === "_blank" ? "noopener noreferrer" : undefined)}
className={classes}
aria-disabled={disabled || undefined}
onClick={disabled ? undefined : onAction}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{body}
</a>
);
}
return (
<button
ref={ref as Ref<HTMLButtonElement>}
type="button"
className={classes}
disabled={disabled}
onClick={disabled ? undefined : onAction}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
>
{body}
</button>
);
}