mirror of
https://github.com/Frooodle/Stirling-PDF.git
synced 2026-03-19 02:22:11 +01:00
## Description of Changes - Removed unused `React` default imports across multiple frontend components. - Updated imports to only include required React hooks and types (e.g., `useState`, `useEffect`, `Suspense`, `createContext`). - Ensured consistency with React 17+ JSX transform, where default `React` import is no longer required. - This cleanup reduces bundle size slightly and aligns code with modern React best practices. --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
138 lines
4.7 KiB
TypeScript
138 lines
4.7 KiB
TypeScript
import { useToast } from './ToastContext';
|
|
import { ToastInstance, ToastLocation } from './types';
|
|
import { LocalIcon } from '../shared/LocalIcon';
|
|
import './ToastRenderer.css';
|
|
|
|
const locationToClass: Record<ToastLocation, string> = {
|
|
'top-left': 'toast-container--top-left',
|
|
'top-right': 'toast-container--top-right',
|
|
'bottom-left': 'toast-container--bottom-left',
|
|
'bottom-right': 'toast-container--bottom-right',
|
|
};
|
|
|
|
function getToastItemClass(t: ToastInstance): string {
|
|
return `toast-item toast-item--${t.alertType}`;
|
|
}
|
|
|
|
function getProgressBarClass(t: ToastInstance): string {
|
|
return `toast-progress-bar toast-progress-bar--${t.alertType}`;
|
|
}
|
|
|
|
function getActionButtonClass(t: ToastInstance): string {
|
|
return `toast-action-button toast-action-button--${t.alertType}`;
|
|
}
|
|
|
|
function getDefaultIconName(t: ToastInstance): string {
|
|
switch (t.alertType) {
|
|
case 'success':
|
|
return 'check-circle-rounded';
|
|
case 'error':
|
|
return 'close-rounded';
|
|
case 'warning':
|
|
return 'warning-rounded';
|
|
case 'neutral':
|
|
default:
|
|
return 'info-rounded';
|
|
}
|
|
}
|
|
|
|
export default function ToastRenderer() {
|
|
const { toasts, dismiss } = useToast();
|
|
|
|
const grouped = toasts.reduce<Record<ToastLocation, ToastInstance[]>>((acc, t) => {
|
|
const key = t.location;
|
|
if (!acc[key]) acc[key] = [] as ToastInstance[];
|
|
acc[key].push(t);
|
|
return acc;
|
|
}, { 'top-left': [], 'top-right': [], 'bottom-left': [], 'bottom-right': [] });
|
|
|
|
return (
|
|
<>
|
|
{(Object.keys(grouped) as ToastLocation[]).map((loc) => (
|
|
<div key={loc} className={`toast-container ${locationToClass[loc]}`}>
|
|
{grouped[loc].map(t => {
|
|
return (
|
|
<div
|
|
key={t.id}
|
|
role="status"
|
|
className={getToastItemClass(t)}
|
|
>
|
|
{/* Top row: Icon + Title + Controls */}
|
|
<div className="toast-header">
|
|
{/* Icon */}
|
|
<div className="toast-icon">
|
|
{t.icon ?? (
|
|
<LocalIcon icon={`material-symbols:${getDefaultIconName(t)}`} width={20} height={20} />
|
|
)}
|
|
</div>
|
|
|
|
{/* Title + count badge */}
|
|
<div className="toast-title-container">
|
|
<span>{t.title}</span>
|
|
{typeof t.count === 'number' && t.count > 1 && (
|
|
<span className="toast-count-badge">{t.count}</span>
|
|
)}
|
|
</div>
|
|
|
|
{/* Controls */}
|
|
<div className="toast-controls">
|
|
{t.expandable && (
|
|
<button
|
|
aria-label="Toggle details"
|
|
onClick={() => {
|
|
const evt = new CustomEvent('toast:toggle', { detail: { id: t.id } });
|
|
window.dispatchEvent(evt);
|
|
}}
|
|
className={`toast-button toast-expand-button ${t.isExpanded ? 'toast-expand-button--expanded' : ''}`}
|
|
>
|
|
<LocalIcon icon="material-symbols:expand-more-rounded" />
|
|
</button>
|
|
)}
|
|
<button
|
|
aria-label="Dismiss"
|
|
onClick={() => dismiss(t.id)}
|
|
className="toast-button"
|
|
>
|
|
<LocalIcon icon="material-symbols:close-rounded" width={20} height={20} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{/* Progress bar - always show when present */}
|
|
{typeof t.progress === 'number' && (
|
|
<div className="toast-progress-container">
|
|
<div
|
|
className={getProgressBarClass(t)}
|
|
style={{ width: `${t.progress}%` }}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Body content - only show when expanded */}
|
|
{(t.isExpanded || !t.expandable) && (
|
|
<div className="toast-body">
|
|
{t.body}
|
|
</div>
|
|
)}
|
|
|
|
{/* Button - always show when present, positioned below body */}
|
|
{t.buttonText && t.buttonCallback && (
|
|
<div className="toast-action-container">
|
|
<button
|
|
onClick={t.buttonCallback}
|
|
className={getActionButtonClass(t)}
|
|
>
|
|
{t.buttonText}
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
|