**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" />
168 lines
4.7 KiB
TypeScript
168 lines
4.7 KiB
TypeScript
import React from "react";
|
|
|
|
import classnames from "classnames";
|
|
|
|
import { IFileDetails } from "utilities/file/fileUtils";
|
|
|
|
import Button from "components/buttons/Button";
|
|
import { ISupportedGraphicNames } from "components/FileUploader/FileUploader";
|
|
import Graphic from "components/Graphic";
|
|
import Icon from "components/Icon";
|
|
import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper";
|
|
|
|
export type IFileDetailsSupportedGraphicNames =
|
|
| ISupportedGraphicNames
|
|
| "app-store"; // For VPP apps (non-editable)
|
|
|
|
interface IFileDetailsProps {
|
|
graphicNames:
|
|
| IFileDetailsSupportedGraphicNames
|
|
| IFileDetailsSupportedGraphicNames[];
|
|
fileDetails: IFileDetails;
|
|
canEdit: boolean;
|
|
/** If present, will default to a custom editor section instead of edit icon */
|
|
customEditor?: () => React.ReactNode;
|
|
/** If present, replaces the default Graphic on the left of the file
|
|
* details (e.g. to render a preview thumbnail of an uploaded image). */
|
|
customPreview?: React.ReactNode;
|
|
/** If present, will show a trash icon */
|
|
onDeleteFile?: () => void;
|
|
onFileSelect?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
accept?: string;
|
|
progress?: number;
|
|
/** Set to false for one instance we allow users to edit a file as it shows them the YAML */
|
|
gitopsCompatible?: boolean;
|
|
gitOpsModeEnabled?: boolean;
|
|
}
|
|
|
|
const baseClass = "file-details";
|
|
|
|
const FileDetails = ({
|
|
graphicNames,
|
|
fileDetails,
|
|
canEdit,
|
|
customEditor,
|
|
customPreview,
|
|
onDeleteFile,
|
|
onFileSelect,
|
|
accept,
|
|
progress,
|
|
gitopsCompatible = true,
|
|
gitOpsModeEnabled = false,
|
|
}: IFileDetailsProps) => {
|
|
const inputRef = React.useRef<HTMLInputElement | null>(null);
|
|
|
|
const handleClickEdit = (disabled?: boolean) => {
|
|
if (disabled) return;
|
|
inputRef.current?.click();
|
|
};
|
|
|
|
const infoClasses = classnames(`${baseClass}__info`, {
|
|
[`${baseClass}__info--disabled-by-gitops-mode`]:
|
|
gitOpsModeEnabled && gitopsCompatible,
|
|
});
|
|
|
|
const renderEditButton = (disabled?: boolean) => {
|
|
if (customEditor) {
|
|
return (
|
|
<div
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
}}
|
|
>
|
|
{customEditor()}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className={`${baseClass}__edit`}>
|
|
<Button
|
|
disabled={disabled}
|
|
className={`${baseClass}__edit-button`}
|
|
variant="subdued"
|
|
onClick={() => handleClickEdit(disabled)}
|
|
title="Replace file"
|
|
>
|
|
<Icon name="pencil" />
|
|
</Button>
|
|
<input
|
|
ref={inputRef}
|
|
type="file"
|
|
accept={accept}
|
|
onChange={onFileSelect}
|
|
className="file-input-visually-hidden"
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className={baseClass}>
|
|
{/* disabling at this level preserves funcitonality of GitOpsModeTooltipWrapper around the edit icon */}
|
|
<div className={infoClasses}>
|
|
{customPreview ?? (
|
|
<Graphic
|
|
name={
|
|
typeof graphicNames === "string" ? graphicNames : graphicNames[0]
|
|
}
|
|
/>
|
|
)}
|
|
<div className={`${baseClass}__content`}>
|
|
<div className={`${baseClass}__name`}>{fileDetails.name}</div>
|
|
{fileDetails.description && (
|
|
<div className={`${baseClass}__description`}>
|
|
{fileDetails.description}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
{!progress &&
|
|
canEdit &&
|
|
onFileSelect &&
|
|
(gitopsCompatible ? (
|
|
<GitOpsModeTooltipWrapper
|
|
position="top"
|
|
tipOffset={8}
|
|
renderChildren={(disableChildren) =>
|
|
renderEditButton(disableChildren)
|
|
}
|
|
/>
|
|
) : (
|
|
renderEditButton()
|
|
))}
|
|
{!progress && onDeleteFile && (
|
|
<div className={`${baseClass}__delete`}>
|
|
<Button
|
|
className={`${baseClass}__delete-button`}
|
|
variant="subdued"
|
|
onClick={onDeleteFile}
|
|
>
|
|
<label htmlFor="delete-file">
|
|
<Icon name="trash" color="ui-fleet-black-75" />
|
|
</label>
|
|
</Button>
|
|
</div>
|
|
)}
|
|
{!!progress && (
|
|
<div className={`${baseClass}__progress-wrapper`}>
|
|
<div className={`${baseClass}__progress-bar`}>
|
|
<div
|
|
className={`${baseClass}__progress-bar--uploaded`}
|
|
style={{
|
|
width: `${progress * 100}%`,
|
|
}}
|
|
title="upload progress bar"
|
|
/>
|
|
</div>
|
|
<div className={`${baseClass}__progress-text`}>
|
|
{Math.round(progress * 100)}%
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default FileDetails;
|