File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
08-14
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>
);
}