<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #44325 (follow-up to #47861 and #48009) ## Summary Follow-up to #47861 and #48009 (both merged) for #44325. Those left several GitOps-mode "Manage in YAML" tooltips still misaligned. This makes the wrapper hug its content so the tooltip centers on the disabled control instead of drifting to the full form width. # Checklist for submitter ## Testing - [x] QA'd all new/changed functionality manually <img width="1280" height="860" alt="1_bootstrap_advanced" src="https://github.com/user-attachments/assets/403cabb4-3d24-4ece-91f5-4887efd0da05" /> <img width="1280" height="860" alt="2_install_software" src="https://github.com/user-attachments/assets/2f9b2130-0fd6-4744-9603-1a31aebd773e" /> <img width="1280" height="860" alt="3_fleet_action_rename" src="https://github.com/user-attachments/assets/180f3e8d-1dee-404c-b13d-14124bc3509b" /> <img width="1280" height="860" alt="4_fleet_action_enroll" src="https://github.com/user-attachments/assets/9378b6fb-5b1e-45e0-80e5-61d4996b49e0" /> <img width="1280" height="860" alt="5_fleet_checkbox" src="https://github.com/user-attachments/assets/dcec8992-5407-452a-8b4c-5d4e8dc27d10" /> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved tooltip positioning and width behavior in setup forms so tooltips align more consistently with the visible control. * Input and dropdown fields now keep their full-width behavior, while other controls are centered more naturally. * Updated the macOS and Windows software requirement options to show tooltips beside the control for better readability. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
111 lines
4.7 KiB
TypeScript
111 lines
4.7 KiB
TypeScript
import classnames from "classnames";
|
|
import { uniqueId } from "lodash";
|
|
import { ITooltipWrapper } from "components/TooltipWrapper/TooltipWrapper";
|
|
import useGitOpsMode from "hooks/useGitOpsMode";
|
|
import { IGitOpsExceptions } from "interfaces/config";
|
|
import React, { useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
import { Tooltip as ReactTooltip5 } from "react-tooltip-5";
|
|
import { getGitOpsModeTipContent } from "utilities/helpers";
|
|
|
|
interface IGitOpsModeTooltipWrapper {
|
|
renderChildren: (disableChildren?: boolean) => React.ReactNode;
|
|
/** Position left is used for forms partially disabled by gitops mode (e.g. settings/organization/advanced) */
|
|
position?: ITooltipWrapper["position"];
|
|
tipOffset?: ITooltipWrapper["tipOffset"];
|
|
fixedPositionStrategy?: ITooltipWrapper["fixedPositionStrategy"];
|
|
// When specified, the wrapper checks the exception for this entity type.
|
|
// If the entity is excepted, children remain enabled even in GitOps mode.
|
|
entityType?: keyof IGitOpsExceptions;
|
|
/** Set to true when wrapping an input/dropdown or a group of them that must stretch to the
|
|
* full form width. By default the wrapper hugs its content so the tooltip centers on the
|
|
* control; inputs opt back into full width. */
|
|
isInputField?: boolean;
|
|
}
|
|
|
|
const baseClass = "gitops-mode-tooltip-wrapper";
|
|
|
|
// The label/control row of a wrapped FormField. FormField renders the label as a
|
|
// direct-child <label> for inputs/dropdowns, and a checkbox's control row is also a
|
|
// direct-child <label>. The help text is a <span class="form-field__help-text"> and is
|
|
// intentionally never matched, so the tooltip anchors at the label rather than the
|
|
// geometric center of label + input + help text.
|
|
const FIRST_ROW_PARTS = [".form-field__label", ".form-field > label"];
|
|
|
|
const GitOpsModeTooltipWrapper = ({
|
|
position = "top",
|
|
tipOffset,
|
|
renderChildren,
|
|
fixedPositionStrategy,
|
|
entityType,
|
|
isInputField = false,
|
|
}: IGitOpsModeTooltipWrapper) => {
|
|
const { gitOpsModeEnabled, repoURL } = useGitOpsMode(entityType);
|
|
|
|
const wrapperRef = useRef<HTMLSpanElement>(null);
|
|
const [hasSingleFieldRow, setHasSingleFieldRow] = useState(false);
|
|
// Prefix makes this a valid CSS id selector (lodash uniqueId returns a bare number).
|
|
const wrapperId = useMemo(() => uniqueId(`${baseClass}-`), []);
|
|
|
|
useLayoutEffect(() => {
|
|
// Only re-anchor when the wrapped content is a single FormField (its root element is the
|
|
// `.form-field`, as rendered by Checkbox/InputField/Dropdown) with exactly one label
|
|
// row. Groups (e.g. a fieldset of radios + a checkbox wrapped in a div) and non-field
|
|
// content (buttons, icon rows) keep whole-wrapper anchoring so hovering anywhere still
|
|
// shows the tooltip.
|
|
const root = wrapperRef.current?.firstElementChild;
|
|
const rows = wrapperRef.current?.querySelectorAll(
|
|
FIRST_ROW_PARTS.join(", ")
|
|
);
|
|
setHasSingleFieldRow(!!root?.matches(".form-field") && rows?.length === 1);
|
|
}, []);
|
|
|
|
if (!gitOpsModeEnabled) {
|
|
return <>{renderChildren()}</>;
|
|
}
|
|
|
|
const tipContent = (
|
|
<div className={`${baseClass}__tooltip-content`}>
|
|
{repoURL && getGitOpsModeTipContent(repoURL)}
|
|
</div>
|
|
);
|
|
|
|
// Hug the wrapped content (buttons, checkboxes, groups) so the tooltip centers on the visible
|
|
// control instead of the full-width form row. Inputs/dropdowns opt back into full width.
|
|
const wrapperClass = classnames(baseClass, {
|
|
[`${baseClass}--inputfield`]: isInputField,
|
|
[`${baseClass}--hug`]: !isInputField,
|
|
});
|
|
|
|
// Anchor to the field's first row so the arrow points at the label regardless of input,
|
|
// help-text, or tooltip-content height. Falls back to the whole wrapper otherwise,
|
|
// preserving the previous centered, hover-anywhere behavior.
|
|
const anchorSelect = hasSingleFieldRow
|
|
? FIRST_ROW_PARTS.map((part) => `#${wrapperId} ${part}`).join(", ")
|
|
: `#${wrapperId}`;
|
|
|
|
return (
|
|
<span ref={wrapperRef} id={wrapperId} className={wrapperClass}>
|
|
{renderChildren(true)}
|
|
<ReactTooltip5
|
|
className={`${baseClass}__tip-text`}
|
|
anchorSelect={anchorSelect}
|
|
place={position}
|
|
// This wrapper always renders an arrow. A 5px offset leaves no gap between the
|
|
// arrow and a button (e.g. "Add certificate authority"), so non-field content
|
|
// gets extra clearance. Field tooltips anchor to the label row and read fine at 5.
|
|
offset={tipOffset ?? (hasSingleFieldRow ? 5 : 8)}
|
|
opacity={1}
|
|
disableStyleInjection
|
|
clickable
|
|
delayShow={250}
|
|
delayHide={250}
|
|
positionStrategy={fixedPositionStrategy ? "fixed" : "absolute"}
|
|
>
|
|
{tipContent}
|
|
</ReactTooltip5>
|
|
</span>
|
|
);
|
|
};
|
|
|
|
export default GitOpsModeTooltipWrapper;
|