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
08-14
08-18
08-18
08-14
08-18
08-18
08-18
File name
Commit message
Commit date
"use client";
import type { ReactNode, Ref } from "react";
import { cx } from "../../utils";
import { FoxTextButton } from "../fox-text-button";
export interface FoxSnackbarProps {
/**
* 글자 앞에 붙는 아이콘. 넘기지 않으면 자리를 만들지 않는다.
*
* 계열이 없는 조각이라 무엇을 그릴지는 호출부가 정한다(`FoxAlert`처럼 계열이 아이콘을
* 고르지 않는다). `fox-ico` 클래스로 넘긴다 — 시안은 `fill` 굵기다:
* `<i className="fox-ico fox-ico--fill fox-ico-CheckCircle" aria-hidden="true" />`.
*
* 색은 스타일이 정한다(`icon-theme-primary`) — 글리프가 `fill: currentColor`라 넘기는
* 쪽에서 색을 줄 필요가 없다.
*/
icon?: ReactNode;
/** 굵은 첫 줄. 넘기지 않으면 영역을 렌더링하지 않는다 — 본문만 있는 스낵바가 된다. */
title?: ReactNode;
/** 제목 아래 본문. 넘기지 않으면 영역을 렌더링하지 않는다. */
message?: ReactNode;
/**
* 오른쪽 글자 버튼의 문구. 없으면 버튼을 그리지 않는다.
*
* 버튼은 `FoxTextButton`의 `primary`·`md`로 고정이다(시안 지정) — 계열·크기를 열지 않는
* 것은 스낵바마다 버튼 모양이 달라지면 같은 조각으로 읽히지 않기 때문이다.
*/
actionLabel?: string;
/** 글자 버튼을 눌렀을 때. `actionLabel`이 없으면 의미 없다. */
onAction?: () => void;
/**
* 닫기(X)를 눌렀을 때. **필수다** — 시안이 닫기를 항상 그리므로, 눌러도 아무 일이 없는
* 버튼이 남지 않게 호출부가 반드시 처리를 준다.
*/
onClose: () => void;
/** 닫기 버튼을 읽어 줄 이름. X 아이콘은 장식이라 이 글자가 유일한 이름이다. */
closeLabel?: string;
/**
* 보조기술이 읽는 방식을 정한다(`FoxToast`·`FoxAlert`와 같은 규칙).
*
* - `false`(기본): `role="status"` — 하던 말을 끊지 않는다.
* - `true`: `role="alert"` — 읽던 것을 끊고 바로 읽는다. 지금 손을 멈춰야 하는 알림에만.
*/
urgent?: boolean;
/** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
hidden?: boolean;
id?: string;
/** 배치 조정용. 띄우는 자리(fixed·bottom 등)와 시안의 폭(640px)을 여기로 덮는다. */
className?: string;
ref?: Ref<HTMLDivElement>;
}
/**
* @fox 스낵바. 흰 판 위에 굵은 한 줄과 글자 버튼이 서고, 오른쪽 끝에 닫기가 붙는다.
* 크기 수식어가 없다 — 시안에 하나뿐이다.
*
* `FoxToast`와 형제지만 **누를 것이 있다.** 토스트는 읽고 지나가는 조각이라 스스로 사라지고,
* 스낵바는 사용자가 되돌리거나(실행 취소) 이어서 할 일이 있어 손이 닿을 때까지 남는다.
*
* **상태를 갖지 않는다.** 언제 뜨고 언제 사라질지, 화면 어디에 놓일지는 전부 호출부가 정한다
* — `onClose`는 "닫아 달라"는 알림이지 이 조각이 스스로 사라지는 것이 아니다.
*
* ⚠️ 보조기술은 **이미 화면에 있던 live region의 내용이 바뀔 때** 읽는다. 이 요소를 통째로
* 새로 붙이면 조합에 따라 읽히지 않을 수 있다(`FoxToast`와 같은 사정).
*
* ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
* (또는 개별 파티셜)로 한 번 불러와야 한다.
*/
export function FoxSnackbar({
icon,
title,
message,
actionLabel,
onAction,
onClose,
closeLabel = "닫기",
urgent = false,
hidden = false,
id,
className,
ref,
}: FoxSnackbarProps) {
if (hidden) {
return null;
}
return (
<div
ref={ref}
id={id}
role={urgent ? "alert" : "status"}
className={cx("fox-snackbar", className)}
>
{icon ? (
<span className="fox-snackbar__icon" aria-hidden="true">
{icon}
</span>
) : null}
<div className="fox-snackbar__content">
{title === undefined ? null : (
<strong className="fox-snackbar__title">{title}</strong>
)}
{message === undefined ? null : (
<span className="fox-snackbar__message">{message}</span>
)}
</div>
{actionLabel ? (
<FoxTextButton
type="primary"
size="md"
label={actionLabel}
onAction={onAction}
className="fox-snackbar__action"
/>
) : null}
<button
type="button"
className="fox-snackbar__close"
aria-label={closeLabel}
onClick={onClose}
>
<i className="fox-ico fox-ico-X" aria-hidden="true" />
</button>
</div>
);
}