All files / src/components/display SectionHeader.tsx

100% Statements 3/3
100% Branches 6/6
100% Functions 2/2
100% Lines 3/3

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                                                                                                            318x 318x           668x                                        
/**
 * SectionHeader -- shared uppercase section label with optional icon-button actions.
 *
 * Used atop every sidebar nav (Tasks, Environments, Schedules, Personas, Coordination)
 * to provide a consistent header strip. Actions (filter, group, sort, add, etc.) only
 * render when provided.
 *
 * @module
 */
 
import { type JSX, type ReactNode } from "react";
import { Tooltip } from "./Tooltip.js";
import styles from "./SectionHeader.module.scss";
 
/** A single action button rendered in the SectionHeader trailing slot. */
export interface SectionHeaderAction {
  /** Unique key for React rendering. */
  key: string;
  /** Icon element (typically a lucide-react component). */
  icon: ReactNode;
  /** Tooltip text shown on hover. */
  tooltip: string;
  /** Accessible label for the button. */
  ariaLabel: string;
  /** Click handler. */
  onClick: () => void;
  /** Whether this action is currently active (renders with green accent). */
  active?: boolean;
  /** aria-pressed value for toggle buttons. */
  ariaPressed?: boolean;
  /** aria-haspopup value for buttons that open menus. */
  ariaHasPopup?: boolean;
  /** aria-expanded value for buttons that control a popup. */
  ariaExpanded?: boolean;
  /** Optional data-testid for the button. */
  testId?: string;
}
 
/** Props for the {@link SectionHeader} component. */
export interface SectionHeaderProps {
  /** Section title text (rendered uppercase via section-label mixin). */
  title: string;
  /** Optional action buttons rendered in the trailing slot. Only renders the actions container when non-empty. */
  actions?: SectionHeaderAction[];
  /** Optional data-testid for the header root. */
  "data-testid"?: string;
}
 
/** Uppercase section label with optional trailing icon-button actions. */
export function SectionHeader({
  title,
  actions,
  "data-testid": testId,
}: SectionHeaderProps): JSX.Element {
  const hasActions = actions !== undefined && actions.length > 0;
  return (
    <div className={styles.header} data-testid={testId}>
      <span className={styles.title}>{title}</span>
      {hasActions && (
        <div className={styles.actions}>
          {actions.map((action) => (
            <Tooltip key={action.key} text={action.tooltip}>
              <button
                type="button"
                className={`${styles.actionButton} ${action.active ? styles.actionActive : ""}`}
                onClick={action.onClick}
                aria-label={action.ariaLabel}
                aria-pressed={action.ariaPressed}
                aria-haspopup={action.ariaHasPopup}
                aria-expanded={action.ariaExpanded}
                data-testid={action.testId}
              >
                {action.icon}
              </button>
            </Tooltip>
          ))}
        </div>
      )}
    </div>
  );
}