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>
);
}
|