2026-tombutcher-ui/src/contexts/MessageContext.jsx
Tom Butcher 8ef109b8e7 Update environment variables, refactor context usage, and enhance UI components
- Changed API and Keycloak URLs in `.env` and `.env.development`.
- Updated `vite.config.js` to reflect new allowed hosts.
- Refactored components to use `useContent` context instead of individual contexts for blogs, companies, and projects.
- Improved error handling in `App` component with `AppError`.
- Added FPS monitoring to `ParticlesBackground` component for developer mode.
- Removed unused contexts and adjusted related imports.
- Enhanced styling for various components and added new styles for FPS monitor.
- Cleaned up SVG files by removing unnecessary attributes.
2025-11-15 19:32:09 +00:00

161 lines
3.5 KiB
JavaScript

import PropTypes from "prop-types";
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
const DEFAULT_AUTO_HIDE_MS = 5000;
const MessageContext = createContext({
isVisible: false,
message: null,
icon: null,
showMessage: () => {},
hideMessage: () => {},
});
export const MessageProvider = ({
children,
defaultAutoHideDelay = DEFAULT_AUTO_HIDE_MS,
}) => {
const [state, setState] = useState({
message: null,
icon: null,
isVisible: false,
});
const hideTimeoutRef = useRef(null);
const clearHideTimeout = useCallback(() => {
if (hideTimeoutRef.current) {
window.clearTimeout(hideTimeoutRef.current);
hideTimeoutRef.current = null;
}
}, []);
const hideMessage = useCallback(() => {
clearHideTimeout();
setState((prev) => ({
...prev,
isVisible: false,
}));
}, [clearHideTimeout]);
const showMessage = useCallback(
(messageText, icon = null, autoHide = true) => {
if (!messageText) {
console.warn("showMessage called without message text");
return;
}
clearHideTimeout();
setState({
message: messageText,
icon,
isVisible: true,
});
const resolvedDelay =
typeof autoHide === "number" && autoHide > 0
? autoHide
: defaultAutoHideDelay;
if (autoHide && typeof window !== "undefined") {
hideTimeoutRef.current = window.setTimeout(() => {
setState((prev) => ({
...prev,
isVisible: false,
}));
hideTimeoutRef.current = null;
}, resolvedDelay);
}
},
[clearHideTimeout, defaultAutoHideDelay]
);
useEffect(() => {
return () => {
clearHideTimeout();
};
}, [clearHideTimeout]);
const contextValue = useMemo(
() => ({
isVisible: state.isVisible,
message: state.message,
icon: state.icon,
showMessage,
hideMessage,
}),
[state.isVisible, state.message, state.icon, showMessage, hideMessage]
);
return (
<MessageContext.Provider value={contextValue}>
{children}
<MessageOverlay
message={state.message}
icon={state.icon}
isVisible={state.isVisible}
onDismiss={hideMessage}
/>
</MessageContext.Provider>
);
};
MessageProvider.propTypes = {
children: PropTypes.node.isRequired,
defaultAutoHideDelay: PropTypes.number,
};
export const useMessage = () => useContext(MessageContext);
const MessageOverlay = ({ message, icon, isVisible, onDismiss }) => {
const shouldRender = Boolean(message);
if (!shouldRender) {
return null;
}
const className = [
"tb-message-popup",
isVisible ? "tb-message-popup-visible" : "tb-message-popup-hidden",
]
.filter(Boolean)
.join(" ");
return (
<div
className={className}
role="status"
aria-live="polite"
aria-hidden={!isVisible}
>
<div className="tb-message-popup-content">
{icon ? <span className="tb-message-popup-icon">{icon}</span> : null}
<span className="tb-message-popup-text">{message}</span>
<button
type="button"
className="tb-message-popup-dismiss"
onClick={onDismiss}
aria-label="Dismiss message"
>
{"\u00d7"}
</button>
</div>
</div>
);
};
MessageOverlay.propTypes = {
message: PropTypes.string,
icon: PropTypes.node,
isVisible: PropTypes.bool.isRequired,
onDismiss: PropTypes.func.isRequired,
};