Files
Steven Palmesano 62ed3583e6 Clear button styles (#49292)
**Related issue:** Resolves #49276

**New features**
- Added new "Secondary" (bordered, off-white fill) and "Subdued"
(borderless, low-emphasis) button variants to match the Figma spec,
alongside the existing Primary style.
- Allowed rows to be selected in Controls > OS updates.

**Cleanup**
- Once nothing referenced the old styles anymore, fully removed the old
`text-icon`, `brand-inverse-icon`, `inverse-alert`, `inverse`, and
`icon` button variants (type, styles, and Storybook entries) from the
shared `Button` component.
- Removed the `iconStroke` prop, which had become a no-op once the old
variants it supported were gone.
- Renamed `ActionsDropdown`'s variants
(`button`/`brand-button`/`small-button`) to
`subdued`/`primary`/`secondary` to match the same naming used everywhere
else.
- Replaced a one-off dropdown implementation on the Software title page
with the shared `ActionsDropdown` component, instead of maintaining
duplicate styling logic.
- Changed the button name on Host details > Reports > Report details
from "View data for all hosts" to "View report for all hosts" (to match
the previous page's Actions drop-down options).


# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.


## Testing

- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually

<img width="1475" height="241" alt="Screenshot 2026-07-21 at 06 35 49"
src="https://github.com/user-attachments/assets/7cfbd444-7837-40e8-854e-bc5989d57d85"
/>
<img width="661" height="306" alt="Screenshot 2026-07-21 at 06 37 18"
src="https://github.com/user-attachments/assets/5d0c4873-8179-4089-b115-7e8cd3a53b4d"
/>
<img width="1427" height="423" alt="Screenshot 2026-07-21 at 06 37 30"
src="https://github.com/user-attachments/assets/a4850a60-f44a-4902-b45e-0094f23a52f8"
/>
<img width="1427" height="640" alt="Screenshot 2026-07-21 at 06 37 46"
src="https://github.com/user-attachments/assets/738a4a7f-cd7d-4162-b659-6f649c32204d"
/>
<img width="1445" height="479" alt="Screenshot 2026-07-22 at 07 03 22"
src="https://github.com/user-attachments/assets/4f672dc0-5c6d-4eb8-8465-ed5233fcd1b2"
/>
<img width="811" height="871" alt="Screenshot 2026-07-21 at 06 41 20"
src="https://github.com/user-attachments/assets/5421c96e-2dab-492a-af26-be0e5a7791ca"
/>
2026-07-23 07:11:59 -05:00

173 lines
4.3 KiB
TypeScript

import React, { useCallback, useEffect, useRef } from "react";
import classnames from "classnames";
import { ICON_MAP } from "components/icons";
import Icon from "components/Icon/Icon";
import TooltipWrapper from "components/TooltipWrapper";
import Button from "components/buttons/Button";
const baseClass = "input-icon-field";
type IconName = keyof typeof ICON_MAP;
export interface IInputFieldWithIconProps {
autofocus?: boolean;
error?: string | null;
helpText?: string[] | string;
iconSvg?: IconName;
label?: string;
name?: string;
onChange?: (value: string) => void;
onClick?: (evt: React.MouseEvent<HTMLInputElement>) => void;
clearButton?: boolean;
placeholder?: string;
tabIndex?: number;
type?: string;
className?: string;
disabled?: boolean;
inputOptions?: React.InputHTMLAttributes<HTMLInputElement>;
tooltip?: string;
/**
* Whether 1Password should skip this field.
* Defaults to `true` because most Fleet inputs are not credential fields.
*/
ignore1Password?: boolean;
value?: string;
}
const InputFieldWithIcon = ({
autofocus = false,
error,
helpText,
iconSvg,
label,
name,
onChange,
onClick,
clearButton,
placeholder,
tabIndex,
type,
className,
disabled,
inputOptions,
tooltip,
ignore1Password = true,
value,
}: IInputFieldWithIconProps): JSX.Element => {
const inputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
if (autofocus && inputRef.current) {
inputRef.current.focus();
}
}, [autofocus]);
const onInputChange = useCallback(
(evt: React.ChangeEvent<HTMLInputElement>) => {
evt.preventDefault();
return onChange?.(evt.target.value);
},
[onChange]
);
const renderHeading = () => {
const labelClasses = classnames(`${baseClass}__label`, {
[`${baseClass}__errors`]: !!error,
[`${baseClass}__label--disabled`]: disabled,
});
return (
<label
htmlFor={name}
className={labelClasses}
data-has-tooltip={!!tooltip}
>
{tooltip && !error ? (
<TooltipWrapper position="bottom-start" tipContent={tooltip}>
{label}
</TooltipWrapper>
) : (
<>{error || label}</>
)}
</label>
);
};
const renderHelpText = () => {
if (helpText) {
return (
<span className={`${baseClass}__help-text form-field__help-text`}>
{helpText}
</span>
);
}
return false;
};
const wrapperClasses = classnames(baseClass, "form-field");
const inputClasses = classnames(
`${baseClass}__input`,
className,
{ "input-with-icon": !!iconSvg },
{ [`${baseClass}__input--error`]: !!error },
{ [`${baseClass}__input--password`]: !!(type === "password" && value) }
);
const inputWrapperClasses = classnames(`${baseClass}__input-wrapper`, {
[`${baseClass}__input-wrapper--disabled`]: disabled,
});
const iconClasses = classnames(
`${baseClass}__icon`,
{ [`${baseClass}__icon--error`]: !!error },
{ [`${baseClass}__icon--active`]: !!value }
);
const handleClear = () => {
onChange?.("");
};
return (
<div className={wrapperClasses}>
{label && renderHeading()}
<div className={inputWrapperClasses}>
<input
id={name}
name={name}
onChange={onInputChange}
onClick={onClick}
className={inputClasses}
placeholder={placeholder}
ref={inputRef}
tabIndex={tabIndex}
type={type}
value={value}
disabled={disabled}
{...inputOptions}
// 1Password only checks for the presence (not the value) of `data-1p-ignore`,
// so we omit the attribute entirely when the field is not meant to be ignored.
// See https://developer.1password.com/docs/web/compatible-website-design/
data-1p-ignore={ignore1Password || undefined}
/>
{iconSvg && <Icon name={iconSvg} className={iconClasses} />}
{clearButton && !!value && (
<Button
onClick={() => handleClear()}
variant="subdued"
className={`${baseClass}__clear-button`}
>
<Icon name="close-filled" color="core-fleet-black" />
</Button>
)}
</div>
{renderHelpText()}
</div>
);
};
export default InputFieldWithIcon;