All files / src/components/display FloatingActionBar.tsx

100% Statements 4/4
100% Branches 16/16
100% Functions 2/2
100% Lines 4/4

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 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122                                                                                                      69x   69x                                                                                       3x 3x                                              
/**
 * Floating action bar shown at the bottom of EventStream during multi-select mode.
 *
 * Displays selection count, select all/deselect all toggle, Copy button,
 * Forward button, and Cancel.
 * Pure presentational component -- no useGrackle().
 */
 
import { type JSX } from "react";
import { Copy, Forward, X } from "lucide-react";
import { motion } from "motion/react";
import { ICON_SM } from "../../utils/iconSize.js";
import { Tooltip } from "./Tooltip.js";
import styles from "./FloatingActionBar.module.scss";
 
/** Props for the FloatingActionBar component. */
export interface FloatingActionBarProps {
  /** Number of currently selected events. */
  selectedCount: number;
  /** Total number of selectable (content-bearing) events. */
  totalSelectable: number;
  /** Called when "Select all" is clicked. */
  onSelectAll: () => void;
  /** Called when "Deselect all" is clicked. */
  onDeselectAll: () => void;
  /** Called when "Copy" is clicked. */
  onCopy: () => void;
  /** Called when "Forward" is clicked. Omit to hide the Forward button. */
  onForward?: () => void;
  /** When true, the Forward button is rendered but disabled. */
  forwardDisabled?: boolean;
  /** Called when "Cancel" (X) is clicked. */
  onCancel: () => void;
}
 
/**
 * Floating action bar for multi-select mode in EventStream.
 *
 * Positioned absolutely at the bottom of the EventStream wrapper.
 * Uses motion.div for animated entrance/exit.
 */
export function FloatingActionBar({
  selectedCount,
  totalSelectable,
  onSelectAll,
  onDeselectAll,
  onCopy,
  onForward,
  forwardDisabled = false,
  onCancel,
}: FloatingActionBarProps): JSX.Element {
  const allSelected = selectedCount > 0 && selectedCount >= totalSelectable;
 
  return (
    <motion.div
      className={styles.bar}
      initial={{ opacity: 0, y: 12 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: 12 }}
      transition={{ duration: 0.15 }}
      data-testid="floating-action-bar"
    >
      <div className={styles.left}>
        <span className={styles.count} data-testid="floating-bar-count">
          {selectedCount} selected
        </span>
        <button
          type="button"
          className={styles.toggleButton}
          onClick={allSelected ? onDeselectAll : onSelectAll}
          data-testid="floating-bar-select-all"
        >
          {allSelected ? "Deselect all" : "Select all"}
        </button>
      </div>
      <div className={styles.right}>
        <button
          type="button"
          className={styles.copyButton}
          onClick={onCopy}
          disabled={selectedCount === 0}
          data-testid="floating-bar-copy"
        >
          <Copy size={ICON_SM} aria-hidden="true" />
          Copy
        </button>
        {onForward !== undefined && (
          <Tooltip
            text={
              forwardDisabled ? "No active sessions to forward to" : "Forward to another session"
            }
          >
            <button
              type="button"
              className={styles.forwardButton}
              aria-disabled={forwardDisabled || selectedCount === 0}
              onClick={() => {
                if (!forwardDisabled && selectedCount > 0) {
                  onForward();
                }
              }}
              data-testid="floating-bar-forward"
            >
              <Forward size={ICON_SM} aria-hidden="true" />
              Forward
            </button>
          </Tooltip>
        )}
        <button
          type="button"
          className={styles.cancelButton}
          onClick={onCancel}
          aria-label="Cancel selection"
          data-testid="floating-bar-cancel"
        >
          <X size={ICON_SM} aria-hidden="true" />
        </button>
      </div>
    </motion.div>
  );
}