All files / src/components StatusBar.tsx

100% Statements 39/39
100% Branches 8/8
60% Functions 3/5
100% Lines 39/39

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 1021x 1x 1x                                           1x 1x 1x 1x 1x   1x                   1x 1x 74x 74x   1x                             1x 1x 16x 16x   16x 4x 1x 1x 4x 4x 3x 3x 4x 16x   16x 16x 16x     16x   1x                   1x 1x 16x   16x   5x 5x  
import { StatusBarContainer } from '@/components/StyledComponents';
import { createContext, ReactNode, useCallback, useContext, useRef, useState } from 'react';
import styles from './StatusBar.module.scss';
 
/**
 * Shape of the status-bar context: the current status text plus setters for
 * sticky and transient (auto-clearing) messages.
 * @example
 * ```tsx
 * const { setStatusText, flashStatusText } = useStatusBar();
 * setStatusText("https://example.com"); // sticky until cleared
 * flashStatusText("Copied!", 1500);     // clears after 1.5s
 * ```
 * @source
 */
interface StatusBarContextValue {
  /** Current status message, or `null` when hidden. */
  statusText: string | null;
  /** Sets a sticky status message (pass `null` to clear). */
  setStatusText: (text: string | null) => void;
  /** Shows a message that auto-clears after `durationMs` (default 1000ms). */
  flashStatusText: (text: string, durationMs?: number) => void;
}
 
const StatusBarContext = createContext<StatusBarContextValue>({
  statusText: null,
  setStatusText: () => {},
  flashStatusText: () => {},
});
 
/**
 * Hook returning the status-bar context, used to read or update the message
 * shown in the bottom status bar (e.g. hovered link URLs, transient flashes).
 * @returns The {@link StatusBarContextValue} from the nearest provider.
 * @example
 * ```tsx
 * const { setStatusText } = useStatusBar();
 * <a onMouseEnter={() => setStatusText(href)} onMouseLeave={() => setStatusText(null)} />
 * ```
 * @source
 */
export function useStatusBar() {
  return useContext(StatusBarContext);
}
 
/**
 * Context provider for the status bar. Holds the current message and exposes
 * `setStatusText` / `flashStatusText` (the latter auto-clears via a timer) to
 * descendants through {@link useStatusBar}.
 * @param props - The provider props.
 * - `children` - The subtree that can read and update the status bar.
 * @returns The provider wrapping `children`.
 * @example
 * ```tsx
 * <StatusBarProvider>
 *   <App />
 *   <StatusBar />
 * </StatusBarProvider>
 * ```
 * @source
 */
export function StatusBarProvider({ children }: { children: ReactNode }) {
  const [statusText, setStatusText] = useState<string | null>(null);
  const flashTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
 
  const flashStatusText = useCallback((text: string, durationMs = 1000) => {
    if (flashTimerRef.current) {
      clearTimeout(flashTimerRef.current);
    }
    setStatusText(text);
    flashTimerRef.current = setTimeout(() => {
      setStatusText(null);
      flashTimerRef.current = null;
    }, durationMs);
  }, []);
 
  return (
    <StatusBarContext.Provider value={{ statusText, setStatusText, flashStatusText }}>
      {children}
    </StatusBarContext.Provider>
  );
}
 
/**
 * Renders the bottom status bar showing the current message from
 * {@link useStatusBar}, or nothing when there's no message.
 * @returns The status-bar element, or `null` when `statusText` is empty.
 * @example
 * ```tsx
 * // Renders "https://example.com" while a link is hovered; null otherwise.
 * <StatusBar />
 * ```
 * @source
 */
export default function StatusBar() {
  const { statusText } = useStatusBar();
 
  if (!statusText) return null;
 
  return <StatusBarContainer className={styles['status-bar']}>{statusText}</StatusBarContainer>;
}