File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
'use client';
import { useEffect, useRef } from 'react';
import { FoxButton } from '@fox/core/components/fox-button';
import { FoxModal } from '@fox/core/components/fox-modal';
import { FoxToast } from '@fox/core/components/fox-toast';
import { Spinner } from '@/components/ui/spinner';
import { useFeedback } from '@/app/_hooks/use-feedback';
import styles from './feedback-host.module.scss';
/**
* FeedbackProvider의 상태를 @fox 비주얼(Toast·Modal·Spinner)에 연결만 하는 역할 —
* 위치·스택·표시 타이머 등 정책은 FeedbackProvider가 가지고, 이 컴포넌트는 마운트만 담당한다.
*
* 계열(success/danger/…)은 색으로 드러나지 않는다 — @fox 시안의 토스트가 하나뿐이라
* (`_fox-toast.scss`) 모양이 갈리지 않는다. 대신 `danger`만 `urgent`로 올려 보조기술이
* 읽던 것을 끊고 바로 읽게 한다.
*/
export function FeedbackHost() {
const { toasts, alert, spinnerCount, hideAlert } = useFeedback();
const toastLayerRef = useRef<HTMLDivElement>(null);
const spinnerLayerRef = useRef<HTMLDivElement>(null);
const hasToasts = toasts.length > 0;
const isBusy = spinnerCount > 0;
// 모달 위에서도 보이도록 top layer에 얹는다(feedback-host.module.scss의 이슈 주석 참고).
useEffect(() => {
const layer = toastLayerRef.current;
if (!layer || typeof layer.showPopover !== 'function') {
return;
}
const isOpen = layer.matches(':popover-open');
if (hasToasts && !isOpen) {
layer.showPopover();
} else if (!hasToasts && isOpen) {
layer.hidePopover();
}
}, [hasToasts]);
useEffect(() => {
const layer = spinnerLayerRef.current;
if (!layer || typeof layer.showPopover !== 'function') {
return;
}
if (!layer.matches(':popover-open')) {
layer.showPopover();
}
}, [isBusy]);
return (
<>
<div ref={toastLayerRef} popover="manual" className={styles.toastLayer}>
{toasts.map((toast) => (
<FoxToast
key={toast.id}
message={toast.message}
urgent={toast.variant === 'danger'}
/>
))}
</div>
{alert && (
<FoxModal
open
size="sm"
title={alert.title}
onClose={hideAlert}
actions={
alert.actions ?? (
<FoxButton
type="primary"
size="md"
label="확인"
onAction={hideAlert}
/>
)
}
>
{alert.message}
</FoxModal>
)}
{isBusy && (
<div ref={spinnerLayerRef} popover="manual" className={styles.spinnerLayer}>
<Spinner size="lg" tone="inverse" />
</div>
)}
</>
);
}