Fleet UI: Accessibility button actions, tabbing (#22916)
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
import React from "react";
|
||||
import { screen } from "@testing-library/react";
|
||||
import { renderWithSetup } from "test/test-utils";
|
||||
|
||||
import ActionsDropdown from "./ActionsDropdown";
|
||||
|
||||
const DROPDOWN_OPTIONS = [
|
||||
{ disabled: false, label: "Edit", value: "edit-query" },
|
||||
{ disabled: false, label: "Show query", value: "show-query" },
|
||||
{ disabled: true, label: "Delete", value: "delete-query" },
|
||||
];
|
||||
const PLACEHOLDER = "Actions";
|
||||
const ON_CHANGE = (value: string) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
describe("Actions dropdown", () => {
|
||||
it("renders dropdown placeholder and options", async () => {
|
||||
const { user } = renderWithSetup(
|
||||
<ActionsDropdown
|
||||
options={DROPDOWN_OPTIONS} // Test
|
||||
placeholder={PLACEHOLDER}
|
||||
onChange={ON_CHANGE}
|
||||
/>
|
||||
);
|
||||
|
||||
await user.click(screen.getByText("Actions"));
|
||||
|
||||
expect(screen.queryAllByText(/edit/i)[1]).toBeInTheDocument(); // Aria shows Edit twice since it's focused
|
||||
expect(screen.queryByText(/show query/i)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/delete/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders dropdown as disabled when disabled prop is true", () => {
|
||||
renderWithSetup(
|
||||
<ActionsDropdown
|
||||
options={DROPDOWN_OPTIONS}
|
||||
placeholder={PLACEHOLDER}
|
||||
onChange={ON_CHANGE}
|
||||
disabled // Test
|
||||
/>
|
||||
);
|
||||
expect(screen.getByRole("combobox")).toBeDisabled();
|
||||
});
|
||||
|
||||
it("calls onChange with correct value when an option is selected", async () => {
|
||||
const mockOnChange = jest.fn();
|
||||
const { user } = renderWithSetup(
|
||||
<ActionsDropdown
|
||||
options={DROPDOWN_OPTIONS}
|
||||
placeholder={PLACEHOLDER}
|
||||
onChange={mockOnChange}
|
||||
/>
|
||||
);
|
||||
|
||||
await user.click(screen.getByText("Actions"));
|
||||
await user.click(screen.getByText("Edit"));
|
||||
|
||||
expect(mockOnChange).toHaveBeenCalledWith("edit-query");
|
||||
});
|
||||
|
||||
it("renders disabled option as non-selectable", async () => {
|
||||
const { user } = renderWithSetup(
|
||||
<ActionsDropdown
|
||||
options={DROPDOWN_OPTIONS}
|
||||
placeholder={PLACEHOLDER}
|
||||
onChange={ON_CHANGE}
|
||||
/>
|
||||
);
|
||||
|
||||
await user.click(screen.getByText("Actions"));
|
||||
const deleteOption = screen.getByText("Delete");
|
||||
|
||||
expect(deleteOption).toHaveAttribute("aria-disabled", "true");
|
||||
});
|
||||
|
||||
it("closes the dropdown when clicking outside", async () => {
|
||||
const { user } = renderWithSetup(
|
||||
<ActionsDropdown
|
||||
options={DROPDOWN_OPTIONS}
|
||||
placeholder={PLACEHOLDER}
|
||||
onChange={ON_CHANGE}
|
||||
/>
|
||||
);
|
||||
|
||||
await user.click(screen.getByText("Actions"));
|
||||
expect(screen.getByText("Edit")).toBeVisible();
|
||||
|
||||
await user.click(document.body);
|
||||
expect(screen.queryByText(/edit/i)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,252 @@
|
||||
import React from "react";
|
||||
import Select, {
|
||||
StylesConfig,
|
||||
DropdownIndicatorProps,
|
||||
OptionProps,
|
||||
components,
|
||||
} from "react-select-5";
|
||||
|
||||
import { PADDING } from "styles/var/padding";
|
||||
import { COLORS } from "styles/var/colors";
|
||||
import classnames from "classnames";
|
||||
|
||||
import { IDropdownOption } from "interfaces/dropdownOption";
|
||||
|
||||
import Icon from "components/Icon";
|
||||
import DropdownOptionTooltipWrapper from "components/forms/fields/Dropdown/DropdownOptionTooltipWrapper";
|
||||
|
||||
const baseClass = "actions-dropdown";
|
||||
|
||||
interface IActionsDropdownProps {
|
||||
options: IDropdownOption[];
|
||||
placeholder: string;
|
||||
onChange: (value: string) => void;
|
||||
disabled?: boolean;
|
||||
isSearchable?: boolean;
|
||||
className?: string;
|
||||
menuAlign?: "right" | "left" | "default";
|
||||
}
|
||||
|
||||
const getOptionBackgroundColor = (state: any) => {
|
||||
return state.isSelected || state.isFocused
|
||||
? COLORS["ui-vibrant-blue-10"]
|
||||
: "transparent";
|
||||
};
|
||||
|
||||
const getLeftMenuAlign = (menuAlign: "right" | "left" | "default") => {
|
||||
switch (menuAlign) {
|
||||
case "right":
|
||||
return "auto";
|
||||
case "left":
|
||||
return "0";
|
||||
default:
|
||||
return "-12px";
|
||||
}
|
||||
};
|
||||
|
||||
const getRightMenuAlign = (menuAlign: "right" | "left" | "default") => {
|
||||
switch (menuAlign) {
|
||||
case "right":
|
||||
return "0";
|
||||
default:
|
||||
return "undefined";
|
||||
}
|
||||
};
|
||||
|
||||
const CustomDropdownIndicator = (
|
||||
props: DropdownIndicatorProps<any, false, any>
|
||||
) => {
|
||||
const { isFocused, selectProps } = props;
|
||||
// no access to hover state here from react-select so that is done in the scss
|
||||
// file of ActionsDropdown.
|
||||
const color =
|
||||
isFocused || selectProps.menuIsOpen
|
||||
? "core-fleet-blue"
|
||||
: "core-fleet-black";
|
||||
|
||||
return (
|
||||
<components.DropdownIndicator {...props} className={baseClass}>
|
||||
<Icon
|
||||
name="chevron-down"
|
||||
color={color}
|
||||
className={`${baseClass}__icon`}
|
||||
/>
|
||||
</components.DropdownIndicator>
|
||||
);
|
||||
};
|
||||
|
||||
const CustomOption: React.FC<OptionProps<IDropdownOption, false>> = (props) => {
|
||||
const { innerProps, innerRef, data, isDisabled } = props;
|
||||
|
||||
const optionContent = (
|
||||
<div
|
||||
className={`${baseClass}__option`}
|
||||
ref={innerRef}
|
||||
{...innerProps}
|
||||
tabIndex={isDisabled ? -1 : 0} // Tabbing skipped when disabled
|
||||
aria-disabled={isDisabled}
|
||||
>
|
||||
{data.label}
|
||||
{data.helpText && (
|
||||
<span className={`${baseClass}__help-text`}>{data.helpText}</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<components.Option {...props}>
|
||||
{data.tooltipContent ? (
|
||||
<DropdownOptionTooltipWrapper tipContent={data.tooltipContent}>
|
||||
{optionContent}
|
||||
</DropdownOptionTooltipWrapper>
|
||||
) : (
|
||||
optionContent
|
||||
)}
|
||||
</components.Option>
|
||||
);
|
||||
};
|
||||
|
||||
const ActionsDropdown = ({
|
||||
options,
|
||||
placeholder,
|
||||
onChange,
|
||||
disabled,
|
||||
isSearchable = false,
|
||||
className,
|
||||
menuAlign = "default",
|
||||
}: IActionsDropdownProps): JSX.Element => {
|
||||
const dropdownClassnames = classnames(baseClass, className);
|
||||
|
||||
const handleChange = (newValue: IDropdownOption | null) => {
|
||||
if (newValue) {
|
||||
onChange(newValue.value.toString());
|
||||
}
|
||||
};
|
||||
|
||||
const customStyles: StylesConfig<IDropdownOption, false> = {
|
||||
container: (provided) => ({
|
||||
...provided,
|
||||
width: "80px",
|
||||
}),
|
||||
control: (provided, state) => ({
|
||||
...provided,
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
width: "max-content",
|
||||
padding: "8px 0",
|
||||
backgroundColor: "initial",
|
||||
border: 0,
|
||||
boxShadow: "none",
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
boxShadow: "none",
|
||||
".actions-dropdown-select__placeholder": {
|
||||
color: COLORS["core-vibrant-blue-over"],
|
||||
},
|
||||
".actions-dropdown-select__indicator path": {
|
||||
stroke: COLORS["core-vibrant-blue-over"],
|
||||
},
|
||||
},
|
||||
"&:active .actions-dropdown-select__indicator path": {
|
||||
stroke: COLORS["core-vibrant-blue-down"],
|
||||
},
|
||||
// TODO: Figure out a way to apply separate &:focus-visible styling
|
||||
// Currently only relying on &:focus styling for tabbing through app
|
||||
...(state.menuIsOpen && {
|
||||
".actions-dropdown-select__indicator svg": {
|
||||
transform: "rotate(180deg)",
|
||||
transition: "transform 0.25s ease",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
placeholder: (provided, state) => ({
|
||||
...provided,
|
||||
color: state.isFocused
|
||||
? COLORS["core-fleet-blue"]
|
||||
: COLORS["core-fleet-black"],
|
||||
fontSize: "14px",
|
||||
lineHeight: "normal",
|
||||
paddingLeft: 0,
|
||||
marginTop: "1px",
|
||||
}),
|
||||
dropdownIndicator: (provided) => ({
|
||||
...provided,
|
||||
display: "flex",
|
||||
padding: "2px",
|
||||
svg: {
|
||||
transition: "transform 0.25s ease",
|
||||
},
|
||||
}),
|
||||
menu: (provided) => ({
|
||||
...provided,
|
||||
boxShadow: "0 2px 6px rgba(0, 0, 0, 0.1)",
|
||||
borderRadius: "4px",
|
||||
zIndex: 6,
|
||||
overflow: "hidden",
|
||||
border: 0,
|
||||
marginTop: 0,
|
||||
minWidth: "158px",
|
||||
maxHeight: "220px",
|
||||
position: "absolute",
|
||||
left: getLeftMenuAlign(menuAlign),
|
||||
right: getRightMenuAlign(menuAlign),
|
||||
animation: "fade-in 150ms ease-out",
|
||||
}),
|
||||
menuList: (provided) => ({
|
||||
...provided,
|
||||
padding: PADDING["pad-small"],
|
||||
}),
|
||||
valueContainer: (provided) => ({
|
||||
...provided,
|
||||
padding: 0,
|
||||
}),
|
||||
option: (provided, state) => ({
|
||||
...provided,
|
||||
padding: "10px 8px",
|
||||
fontSize: "14px",
|
||||
backgroundColor: getOptionBackgroundColor(state),
|
||||
"&:hover": {
|
||||
backgroundColor: state.isDisabled
|
||||
? "transparent"
|
||||
: COLORS["ui-vibrant-blue-10"],
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: state.isDisabled
|
||||
? "transparent"
|
||||
: COLORS["ui-vibrant-blue-10"],
|
||||
},
|
||||
...(state.isDisabled && {
|
||||
color: COLORS["ui-fleet-black-50"],
|
||||
fontStyle: "italic",
|
||||
// pointerEvents: "none", // Prevents any mouse interaction
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<Select<IDropdownOption, false>
|
||||
options={options}
|
||||
placeholder={placeholder}
|
||||
onChange={handleChange}
|
||||
isDisabled={disabled}
|
||||
isSearchable={isSearchable}
|
||||
styles={customStyles}
|
||||
components={{
|
||||
DropdownIndicator: CustomDropdownIndicator,
|
||||
IndicatorSeparator: () => null,
|
||||
Option: CustomOption,
|
||||
SingleValue: () => null, // Doesn't replace placeholder text with selected text
|
||||
// Note: react-select doesn't support skipping disabled options when keyboarding through
|
||||
}}
|
||||
controlShouldRenderValue={false} // Doesn't change placeholder text to selected text
|
||||
isOptionSelected={() => false} // Hides any styling on selected option
|
||||
className={dropdownClassnames}
|
||||
classNamePrefix={`${baseClass}-select`}
|
||||
isOptionDisabled={(option) => !!option.disabled}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActionsDropdown;
|
||||
@@ -0,0 +1,6 @@
|
||||
// All styling in customStyles part of react-select-5
|
||||
.actions-dropdown-select__control {
|
||||
&:focus-visible {
|
||||
background-color: $core-fleet-blue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./ActionsDropdown";
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useRef } from "react";
|
||||
import classnames from "classnames";
|
||||
|
||||
import Button from "components/buttons/Button";
|
||||
@@ -74,18 +74,32 @@ export const FileUploader = ({
|
||||
fileDetails,
|
||||
}: IFileUploaderProps) => {
|
||||
const [isFileSelected, setIsFileSelected] = useState(!!fileDetails);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const classes = classnames(baseClass, className, {
|
||||
[`${baseClass}__file-preview`]: isFileSelected,
|
||||
});
|
||||
const buttonVariant = buttonType === "button" ? "brand" : "text-icon";
|
||||
|
||||
const triggerFileInput = () => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
const onFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = e.target.files;
|
||||
onFileUpload(files);
|
||||
setIsFileSelected(true);
|
||||
|
||||
e.target.value = "";
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = "";
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
triggerFileInput();
|
||||
}
|
||||
};
|
||||
|
||||
const renderGraphics = () => {
|
||||
@@ -113,6 +127,9 @@ export const FileUploader = ({
|
||||
variant={buttonVariant}
|
||||
isLoading={isLoading}
|
||||
disabled={disabled}
|
||||
customOnKeyDown={handleKeyDown}
|
||||
tabIndex={0}
|
||||
onClick={triggerFileInput}
|
||||
>
|
||||
<label htmlFor="upload-file">
|
||||
{buttonType === "link" && <Icon name="upload" />}
|
||||
@@ -120,6 +137,7 @@ export const FileUploader = ({
|
||||
</label>
|
||||
</Button>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
accept={accept}
|
||||
id="upload-file"
|
||||
type="file"
|
||||
|
||||
@@ -113,7 +113,7 @@ const Modal = ({
|
||||
<span>{title}</span>
|
||||
{!disableClosingModal && (
|
||||
<div className={`${baseClass}__ex`}>
|
||||
<Button className="button button--unstyled" onClick={onExit}>
|
||||
<Button variant="unstyled" onClick={onExit}>
|
||||
<Icon name="close" color="core-fleet-black" size="medium" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import React from "react";
|
||||
import { screen } from "@testing-library/react";
|
||||
import { renderWithSetup } from "test/test-utils";
|
||||
|
||||
import DropdownCell from "./DropdownCell";
|
||||
|
||||
const DROPDOWN_OPTIONS = [
|
||||
{ disabled: false, label: "Edit", value: "edit-query" },
|
||||
{ disabled: false, label: "Show query", value: "show-query" },
|
||||
{ disabled: true, label: "Delete", value: "delete-query" },
|
||||
];
|
||||
const PLACEHOLDER = "Actions";
|
||||
const ON_CHANGE = (value: string) => {
|
||||
console.log(value);
|
||||
};
|
||||
|
||||
describe("Dropdown cell", () => {
|
||||
it("renders dropdown placeholder and options", async () => {
|
||||
const { user } = renderWithSetup(
|
||||
<DropdownCell
|
||||
options={DROPDOWN_OPTIONS}
|
||||
placeholder={PLACEHOLDER}
|
||||
onChange={ON_CHANGE}
|
||||
/>
|
||||
);
|
||||
|
||||
await user.click(screen.getByText("Actions"));
|
||||
|
||||
expect(screen.getByText(/edit/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/show query/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/delete/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
// ignore TS error for now until these are rewritten in ts.
|
||||
// @ts-ignore
|
||||
import Dropdown from "components/forms/fields/Dropdown";
|
||||
|
||||
import { IDropdownOption } from "interfaces/dropdownOption";
|
||||
|
||||
const baseClass = "dropdown-cell";
|
||||
|
||||
interface IDropdownCellProps {
|
||||
options: IDropdownOption[];
|
||||
placeholder: string;
|
||||
onChange: (value: string) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const DropdownCell = ({
|
||||
options,
|
||||
placeholder,
|
||||
onChange,
|
||||
disabled,
|
||||
}: IDropdownCellProps): JSX.Element => {
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<Dropdown
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
searchable={false}
|
||||
options={options}
|
||||
disabled={disabled ?? false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DropdownCell;
|
||||
@@ -1,87 +0,0 @@
|
||||
.dropdown-cell {
|
||||
width: 80px;
|
||||
|
||||
.form-field {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.Select {
|
||||
position: relative;
|
||||
border: 0;
|
||||
height: auto;
|
||||
|
||||
&.is-focused,
|
||||
&:hover {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&.is-focused:not(.is-open) {
|
||||
.Select-control {
|
||||
background-color: initial;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
.Select-control {
|
||||
.Select-placeholder {
|
||||
@include disabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Select-control {
|
||||
display: flex;
|
||||
background-color: initial;
|
||||
height: auto;
|
||||
justify-content: space-between;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&:hover .Select-placeholder {
|
||||
color: $core-vibrant-blue;
|
||||
}
|
||||
|
||||
.Select-placeholder {
|
||||
color: $core-fleet-black;
|
||||
font-size: 14px;
|
||||
line-height: normal;
|
||||
padding-left: 0;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.Select-input {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.Select-arrow-zone {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.Select-menu-outer {
|
||||
margin-top: $pad-xsmall;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||
border-radius: $border-radius;
|
||||
z-index: 6;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
width: 188px;
|
||||
left: unset;
|
||||
top: unset;
|
||||
max-height: 220px;
|
||||
padding: $pad-small;
|
||||
position: absolute;
|
||||
left: -12px;
|
||||
}
|
||||
|
||||
&.is-open {
|
||||
.Select-control .Select-placeholder {
|
||||
color: $core-vibrant-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./DropdownCell";
|
||||
@@ -85,6 +85,7 @@ const TeamsDropdown = ({
|
||||
onChange={onChange}
|
||||
onOpen={onOpen}
|
||||
onClose={onClose}
|
||||
tabIndex={0}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { Children } from "react";
|
||||
import classnames from "classnames";
|
||||
import Spinner from "components/Spinner";
|
||||
|
||||
@@ -35,6 +35,7 @@ export interface IButtonProps {
|
||||
tabIndex?: number;
|
||||
type?: "button" | "submit" | "reset";
|
||||
title?: string;
|
||||
/** Default: "brand" */
|
||||
variant?: ButtonVariant;
|
||||
onClick?:
|
||||
| ((value?: any) => void)
|
||||
@@ -44,6 +45,7 @@ export interface IButtonProps {
|
||||
| React.KeyboardEvent<HTMLButtonElement>
|
||||
) => void);
|
||||
isLoading?: boolean;
|
||||
customOnKeyDown?: (e: React.KeyboardEvent) => void;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
@@ -57,7 +59,7 @@ class Button extends React.Component<IButtonProps, IButtonState> {
|
||||
static defaultProps = {
|
||||
size: "",
|
||||
type: "button",
|
||||
variant: "default",
|
||||
variant: "brand",
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
@@ -115,6 +117,7 @@ class Button extends React.Component<IButtonProps, IButtonState> {
|
||||
title,
|
||||
variant,
|
||||
isLoading,
|
||||
customOnKeyDown,
|
||||
} = this.props;
|
||||
const fullClassName = classnames(
|
||||
baseClass,
|
||||
@@ -136,7 +139,7 @@ class Button extends React.Component<IButtonProps, IButtonState> {
|
||||
className={fullClassName}
|
||||
disabled={disabled}
|
||||
onClick={handleClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
onKeyDown={customOnKeyDown || handleKeyDown}
|
||||
tabIndex={tabIndex}
|
||||
type={type}
|
||||
title={title}
|
||||
|
||||
@@ -301,8 +301,13 @@ $base-class: "button";
|
||||
color: $core-vibrant-blue-down;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
&:focus-visible {
|
||||
color: $core-vibrant-blue-over;
|
||||
border: 1px;
|
||||
border-radius: 2px; // Visble when tabbing
|
||||
background: var(--Core-White, #fff);
|
||||
outline: none;
|
||||
box-shadow: 0px 0px 0px 2px #fff, 0px 0px 0px 4px #d9d9fe;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.dropdown-button {
|
||||
padding: 8px 24px 8px 0;
|
||||
padding: 8px 0;
|
||||
&__wrapper {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
.Select > .Select-menu-outer {
|
||||
// Used with old react-select dropdown and
|
||||
// New react-select-5 ActionsDropdown.tsx
|
||||
.Select > .Select-menu-outer,
|
||||
.actions-dropdown {
|
||||
.is-disabled * {
|
||||
color: $ui-fleet-black-50;
|
||||
}
|
||||
|
||||
+5
-5
@@ -12,7 +12,7 @@ import { IScheduledQuery } from "interfaces/scheduled_query";
|
||||
import { IDropdownOption } from "interfaces/dropdownOption";
|
||||
|
||||
import Checkbox from "components/forms/fields/Checkbox";
|
||||
import DropdownCell from "components/TableContainer/DataTable/DropdownCell";
|
||||
import ActionsDropdown from "components/ActionsDropdown";
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
|
||||
import PerformanceImpactCell from "components/TableContainer/DataTable/PerformanceImpactCell";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
@@ -54,7 +54,7 @@ interface IPerformanceImpactCellProps extends IRowProps {
|
||||
};
|
||||
}
|
||||
|
||||
interface IDropdownCellProps extends IRowProps {
|
||||
interface IActionsDropdownProps extends IRowProps {
|
||||
cell: {
|
||||
value: IDropdownOption[];
|
||||
};
|
||||
@@ -68,7 +68,7 @@ interface IDataColumn {
|
||||
Cell:
|
||||
| ((props: ICellProps) => JSX.Element)
|
||||
| ((props: IPerformanceImpactCellProps) => JSX.Element)
|
||||
| ((props: IDropdownCellProps) => JSX.Element);
|
||||
| ((props: IActionsDropdownProps) => JSX.Element);
|
||||
disableHidden?: boolean;
|
||||
disableSortBy?: boolean;
|
||||
}
|
||||
@@ -182,8 +182,8 @@ const generateTableHeaders = (
|
||||
Header: "",
|
||||
disableSortBy: true,
|
||||
accessor: "actions",
|
||||
Cell: (cellProps: IDropdownCellProps) => (
|
||||
<DropdownCell
|
||||
Cell: (cellProps: IActionsDropdownProps) => (
|
||||
<ActionsDropdown
|
||||
options={cellProps.cell.value}
|
||||
onChange={(value: string) =>
|
||||
actionSelectHandler(value, cellProps.row.original)
|
||||
|
||||
@@ -71,7 +71,7 @@ const UserMenu = ({
|
||||
|
||||
return (
|
||||
<div className={baseClass} data-testid="user-menu">
|
||||
<DropdownButton options={dropdownItems}>
|
||||
<DropdownButton options={dropdownItems} variant="unstyled">
|
||||
<AvatarTopNav
|
||||
className={`${baseClass}__avatar-image`}
|
||||
user={{ gravatar_url_dark: currentUser.gravatar_url_dark }}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
.user-menu {
|
||||
button {
|
||||
background-color: transparent;
|
||||
margin-right: 24px;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&.focus-visible {
|
||||
border: 1px solid $ui-vibrant-blue-10;
|
||||
@@ -18,6 +20,20 @@
|
||||
svg {
|
||||
transition: transform 0.25s ease;
|
||||
}
|
||||
|
||||
&:hover svg {
|
||||
animation: bounceDown 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes bounceDown {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(3px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-button__options {
|
||||
|
||||
+8
-8
@@ -16,10 +16,9 @@ import { buildQueryStringFromParams } from "utilities/url";
|
||||
import { internationalTimeFormat } from "utilities/helpers";
|
||||
import { uploadedFromNow } from "utilities/date_format";
|
||||
|
||||
// @ts-ignore
|
||||
import Dropdown from "components/forms/fields/Dropdown";
|
||||
import Card from "components/Card";
|
||||
import Graphic from "components/Graphic";
|
||||
import ActionsDropdown from "components/ActionsDropdown";
|
||||
import TooltipWrapper from "components/TooltipWrapper";
|
||||
import DataSet from "components/DataSet";
|
||||
import Icon from "components/Icon";
|
||||
@@ -183,7 +182,7 @@ interface IActionsDropdownProps {
|
||||
onEditSoftwareClick: () => void;
|
||||
}
|
||||
|
||||
const ActionsDropdown = ({
|
||||
const SoftwareActionsDropdown = ({
|
||||
isSoftwarePackage,
|
||||
onDownloadClick,
|
||||
onDeleteClick,
|
||||
@@ -207,16 +206,17 @@ const ActionsDropdown = ({
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__actions`}>
|
||||
<Dropdown
|
||||
<ActionsDropdown
|
||||
className={`${baseClass}__host-actions-dropdown`}
|
||||
onChange={onSelect}
|
||||
placeholder="Actions"
|
||||
searchable={false}
|
||||
isSearchable={false}
|
||||
options={
|
||||
isSoftwarePackage
|
||||
? SOFTWARE_PACKAGE_DROPDOWN_OPTIONS
|
||||
: APP_STORE_APP_DROPDOWN_OPTIONS
|
||||
? [...SOFTWARE_PACKAGE_DROPDOWN_OPTIONS]
|
||||
: [...APP_STORE_APP_DROPDOWN_OPTIONS]
|
||||
}
|
||||
menuAlign="right"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -353,7 +353,7 @@ const SoftwarePackageCard = ({
|
||||
</div>
|
||||
)}
|
||||
{showActions && (
|
||||
<ActionsDropdown
|
||||
<SoftwareActionsDropdown
|
||||
isSoftwarePackage={!!softwarePackage}
|
||||
onDownloadClick={onDownloadClick}
|
||||
onDeleteClick={onDeleteClick}
|
||||
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import DropdownCell from "components/TableContainer/DataTable/DropdownCell";
|
||||
import ActionsDropdown from "components/ActionsDropdown";
|
||||
|
||||
import {
|
||||
IJiraIntegration,
|
||||
@@ -31,7 +31,7 @@ interface ICellProps extends IRowProps {
|
||||
};
|
||||
}
|
||||
|
||||
interface IDropdownCellProps extends IRowProps {
|
||||
interface IActionsDropdownProps extends IRowProps {
|
||||
cell: {
|
||||
value: IDropdownOption[];
|
||||
};
|
||||
@@ -43,7 +43,7 @@ interface IDataColumn {
|
||||
accessor: string;
|
||||
Cell:
|
||||
| ((props: ICellProps) => JSX.Element)
|
||||
| ((props: IDropdownCellProps) => JSX.Element);
|
||||
| ((props: IActionsDropdownProps) => JSX.Element);
|
||||
disableHidden?: boolean;
|
||||
disableSortBy?: boolean;
|
||||
sortType?: string;
|
||||
@@ -98,8 +98,8 @@ const generateTableHeaders = (
|
||||
Header: "",
|
||||
disableSortBy: true,
|
||||
accessor: "actions",
|
||||
Cell: (cellProps: IDropdownCellProps) => (
|
||||
<DropdownCell
|
||||
Cell: (cellProps: IActionsDropdownProps) => (
|
||||
<ActionsDropdown
|
||||
options={cellProps.cell.value}
|
||||
onChange={(value: string) =>
|
||||
actionSelectHandler(value, cellProps.row.original)
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ import { IHeaderProps, IStringCellProps } from "interfaces/datatable_config";
|
||||
import { IDropdownOption } from "interfaces/dropdownOption";
|
||||
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
|
||||
import DropdownCell from "components/TableContainer/DataTable/DropdownCell";
|
||||
import ActionsDropdown from "components/ActionsDropdown";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import TooltipWrapper from "components/TooltipWrapper";
|
||||
|
||||
@@ -163,7 +163,7 @@ export const generateTableConfig = (
|
||||
// but we don't use it.
|
||||
accessor: "id",
|
||||
Cell: (cellProps) => (
|
||||
<DropdownCell
|
||||
<ActionsDropdown
|
||||
options={generateActions()}
|
||||
onChange={(value: string) =>
|
||||
actionSelectHandler(value, cellProps.row.original)
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ import { IHeaderProps, IStringCellProps } from "interfaces/datatable_config";
|
||||
import { IDropdownOption } from "interfaces/dropdownOption";
|
||||
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
|
||||
import DropdownCell from "components/TableContainer/DataTable/DropdownCell";
|
||||
import ActionsDropdown from "components/ActionsDropdown";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
|
||||
import RenewDateCell from "../../../components/RenewDateCell";
|
||||
@@ -104,7 +104,7 @@ export const generateTableConfig = (
|
||||
// but we don't use it.
|
||||
accessor: "id",
|
||||
Cell: (cellProps) => (
|
||||
<DropdownCell
|
||||
<ActionsDropdown
|
||||
options={generateActions()}
|
||||
onChange={(value: string) =>
|
||||
actionSelectHandler(value, cellProps.row.original)
|
||||
|
||||
+1
@@ -114,6 +114,7 @@ const IdpSection = () => {
|
||||
disabled={!completedForm}
|
||||
onClick={onSubmit}
|
||||
className="button-wrap"
|
||||
variant="brand"
|
||||
>
|
||||
<span data-tip data-for="save-button">
|
||||
Save
|
||||
|
||||
+5
-5
@@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell/TextCell";
|
||||
import DropdownCell from "components/TableContainer/DataTable/DropdownCell";
|
||||
import ActionsDropdown from "components/ActionsDropdown";
|
||||
import CustomLink from "components/CustomLink";
|
||||
import { IUser, UserRole } from "interfaces/user";
|
||||
import { ITeam } from "interfaces/team";
|
||||
@@ -29,7 +29,7 @@ interface ICellProps extends IRowProps {
|
||||
};
|
||||
}
|
||||
|
||||
interface IDropdownCellProps extends IRowProps {
|
||||
interface IActionsDropdownProps extends IRowProps {
|
||||
cell: {
|
||||
value: IDropdownOption[];
|
||||
};
|
||||
@@ -41,7 +41,7 @@ interface IDataColumn {
|
||||
accessor: string;
|
||||
Cell:
|
||||
| ((props: ICellProps) => JSX.Element)
|
||||
| ((props: IDropdownCellProps) => JSX.Element);
|
||||
| ((props: IActionsDropdownProps) => JSX.Element);
|
||||
disableHidden?: boolean;
|
||||
disableSortBy?: boolean;
|
||||
sortType?: string;
|
||||
@@ -174,8 +174,8 @@ const generateColumnConfigs = (
|
||||
Header: "",
|
||||
disableSortBy: true,
|
||||
accessor: "actions",
|
||||
Cell: (cellProps: IDropdownCellProps) => (
|
||||
<DropdownCell
|
||||
Cell: (cellProps: IActionsDropdownProps) => (
|
||||
<ActionsDropdown
|
||||
options={cellProps.cell.value}
|
||||
onChange={(value: string) =>
|
||||
actionSelectHandler(value, cellProps.row.original)
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from "react";
|
||||
|
||||
import LinkCell from "components/TableContainer/DataTable/LinkCell";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import DropdownCell from "components/TableContainer/DataTable/DropdownCell";
|
||||
import DropdownCell from "components/ActionsDropdown";
|
||||
import { ITeam } from "interfaces/team";
|
||||
import { IDropdownOption } from "interfaces/dropdownOption";
|
||||
import PATHS from "router/paths";
|
||||
|
||||
@@ -12,7 +12,7 @@ import { IDropdownOption } from "interfaces/dropdownOption";
|
||||
import { generateRole, generateTeam, greyCell } from "utilities/helpers";
|
||||
import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";
|
||||
import { COLORS } from "styles/var/colors";
|
||||
import DropdownCell from "../../../../../components/TableContainer/DataTable/DropdownCell";
|
||||
import ActionsDropdown from "../../../../../components/ActionsDropdown";
|
||||
|
||||
interface IHeaderProps {
|
||||
column: {
|
||||
@@ -33,7 +33,7 @@ interface ICellProps extends IRowProps {
|
||||
};
|
||||
}
|
||||
|
||||
interface IDropdownCellProps extends IRowProps {
|
||||
interface IActionsDropdownProps extends IRowProps {
|
||||
cell: {
|
||||
value: IDropdownOption[];
|
||||
};
|
||||
@@ -45,7 +45,7 @@ interface IDataColumn {
|
||||
accessor: string;
|
||||
Cell:
|
||||
| ((props: ICellProps) => JSX.Element)
|
||||
| ((props: IDropdownCellProps) => JSX.Element);
|
||||
| ((props: IActionsDropdownProps) => JSX.Element);
|
||||
disableHidden?: boolean;
|
||||
disableSortBy?: boolean;
|
||||
}
|
||||
@@ -200,8 +200,8 @@ const generateTableHeaders = (
|
||||
Header: "",
|
||||
disableSortBy: true,
|
||||
accessor: "actions",
|
||||
Cell: (cellProps: IDropdownCellProps) => (
|
||||
<DropdownCell
|
||||
Cell: (cellProps: IActionsDropdownProps) => (
|
||||
<ActionsDropdown
|
||||
options={cellProps.cell.value}
|
||||
onChange={(value: string) =>
|
||||
actionSelectHandler(value, cellProps.row.original)
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ const HostStatusWebhookPreviewModal = ({
|
||||
/>
|
||||
</div>
|
||||
<div className="modal-cta-wrap">
|
||||
<Button type="button" onClick={toggleModal}>
|
||||
<Button type="button" onClick={toggleModal} variant="brand">
|
||||
Done
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ const CustomLabelGroupHeading = (
|
||||
const handleInputClick = (
|
||||
event: React.MouseEvent<HTMLInputElement, MouseEvent>
|
||||
) => {
|
||||
onClickLabelSearchInput(event);
|
||||
onClickLabelSearchInput && onClickLabelSearchInput(event);
|
||||
inputRef.current?.focus();
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
@@ -17,11 +17,6 @@
|
||||
padding: 0px;
|
||||
border: none;
|
||||
margin-left: 0;
|
||||
|
||||
img {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.premium-icon-tip {
|
||||
|
||||
+10
-6
@@ -24,12 +24,12 @@ declare module "react-select-5/dist/declarations/src/Select" {
|
||||
IsMulti extends boolean,
|
||||
Group extends GroupBase<Option>
|
||||
> {
|
||||
labelQuery: string;
|
||||
canAddNewLabels: boolean;
|
||||
onAddLabel: () => void;
|
||||
onChangeLabelQuery: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onClickLabelSearchInput: React.MouseEventHandler<HTMLInputElement>;
|
||||
onBlurLabelSearchInput: React.FocusEventHandler<HTMLInputElement>;
|
||||
labelQuery?: string;
|
||||
canAddNewLabels?: boolean;
|
||||
onAddLabel?: () => void;
|
||||
onChangeLabelQuery?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onClickLabelSearchInput?: React.MouseEventHandler<HTMLInputElement>;
|
||||
onBlurLabelSearchInput?: React.FocusEventHandler<HTMLInputElement>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,10 @@ const LabelFilterSelect = ({
|
||||
if (e.key === "Escape") {
|
||||
setMenuIsOpen(false);
|
||||
selectRef.current?.blur();
|
||||
} else if (e.key === "Tab" && !e.shiftKey) {
|
||||
// Allow tabbing out of the component
|
||||
setMenuIsOpen(false);
|
||||
selectRef.current?.blur();
|
||||
} else {
|
||||
setMenuIsOpen(true);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import strUtils from "utilities/strings";
|
||||
|
||||
import Modal from "components/Modal";
|
||||
import Button from "components/buttons/Button";
|
||||
import CustomLink from "components/CustomLink";
|
||||
|
||||
const baseClass = "delete-host-modal";
|
||||
|
||||
@@ -59,12 +58,7 @@ const DeleteHostModal = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Delete host"
|
||||
onExit={onCancel}
|
||||
onEnter={onSubmit}
|
||||
className={baseClass}
|
||||
>
|
||||
<Modal title="Delete host" onExit={onCancel} className={baseClass}>
|
||||
<>
|
||||
<p>
|
||||
This will remove the record of <b>{hostText()}</b>.{largeVolumeText()}
|
||||
|
||||
+9
-9
@@ -116,7 +116,7 @@ describe("Host Actions Dropdown", () => {
|
||||
|
||||
expect(
|
||||
screen.getByText("Query").parentElement?.parentElement?.parentElement
|
||||
).toHaveClass("is-disabled");
|
||||
).toHaveClass("actions-dropdown-select__option--is-disabled");
|
||||
|
||||
await waitFor(() => {
|
||||
waitFor(() => {
|
||||
@@ -153,7 +153,7 @@ describe("Host Actions Dropdown", () => {
|
||||
await user.click(screen.getByText("Actions"));
|
||||
expect(
|
||||
screen.getByText("Query").parentElement?.parentElement?.parentElement
|
||||
).toHaveClass("is-disabled");
|
||||
).toHaveClass("actions-dropdown-select__option--is-disabled");
|
||||
});
|
||||
|
||||
it("renders the Query action as disabled when a host is updating", async () => {
|
||||
@@ -180,7 +180,7 @@ describe("Host Actions Dropdown", () => {
|
||||
await user.click(screen.getByText("Actions"));
|
||||
|
||||
expect(screen.getByText("Query").parentElement).toHaveClass(
|
||||
"is-disabled"
|
||||
"actions-dropdown-select__option--is-disabled"
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -388,7 +388,7 @@ describe("Host Actions Dropdown", () => {
|
||||
debug();
|
||||
|
||||
expect(screen.getByText("Turn off MDM").parentElement).toHaveClass(
|
||||
"is-disabled"
|
||||
"actions-dropdown-select__option--is-disabled"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -590,7 +590,7 @@ describe("Host Actions Dropdown", () => {
|
||||
|
||||
expect(
|
||||
screen.getByText("Lock").parentElement?.parentElement?.parentElement
|
||||
).toHaveClass("is-disabled");
|
||||
).toHaveClass("actions-dropdown-select__option--is-disabled");
|
||||
|
||||
await waitFor(() => {
|
||||
waitFor(() => {
|
||||
@@ -845,7 +845,7 @@ describe("Host Actions Dropdown", () => {
|
||||
|
||||
expect(
|
||||
screen.getByText("Unlock").parentElement?.parentElement?.parentElement
|
||||
).toHaveClass("is-disabled");
|
||||
).toHaveClass("actions-dropdown-select__option--is-disabled");
|
||||
|
||||
await waitFor(() => {
|
||||
waitFor(() => {
|
||||
@@ -981,7 +981,7 @@ describe("Host Actions Dropdown", () => {
|
||||
|
||||
expect(
|
||||
screen.getByText("Wipe").parentElement?.parentElement?.parentElement
|
||||
).toHaveClass("is-disabled");
|
||||
).toHaveClass("actions-dropdown-select__option--is-disabled");
|
||||
|
||||
await waitFor(() => {
|
||||
waitFor(() => {
|
||||
@@ -1055,7 +1055,7 @@ describe("Host Actions Dropdown", () => {
|
||||
screen
|
||||
.getByText("Run script")
|
||||
.parentElement?.parentElement?.parentElement?.classList.contains(
|
||||
"is-disabled"
|
||||
"actions-dropdown-select__option--is-disabled"
|
||||
)
|
||||
).toBeFalsy();
|
||||
|
||||
@@ -1098,7 +1098,7 @@ describe("Host Actions Dropdown", () => {
|
||||
expect(
|
||||
screen.getByText("Run script").parentElement?.parentElement
|
||||
?.parentElement
|
||||
).toHaveClass("is-disabled");
|
||||
).toHaveClass("actions-dropdown-select__option--is-disabled");
|
||||
|
||||
await waitFor(() => {
|
||||
waitFor(() => {
|
||||
|
||||
+3
-4
@@ -4,8 +4,7 @@ import { MdmEnrollmentStatus } from "interfaces/mdm";
|
||||
import permissions from "utilities/permissions";
|
||||
import { AppContext } from "context/app";
|
||||
|
||||
// @ts-ignore
|
||||
import Dropdown from "components/forms/fields/Dropdown";
|
||||
import ActionsDropdown from "components/ActionsDropdown";
|
||||
import { generateHostActionOptions } from "./helpers";
|
||||
import { HostMdmDeviceStatusUIState } from "../../helpers";
|
||||
|
||||
@@ -81,12 +80,12 @@ const HostActionsDropdown = ({
|
||||
|
||||
return (
|
||||
<div className={baseClass}>
|
||||
<Dropdown
|
||||
<ActionsDropdown
|
||||
className={`${baseClass}__host-actions-dropdown`}
|
||||
onChange={onSelect}
|
||||
placeholder="Actions"
|
||||
searchable={false}
|
||||
options={options}
|
||||
menuAlign="right"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
+4
-4
@@ -5,7 +5,7 @@ import { IHostScript, ILastExecution } from "interfaces/script";
|
||||
import { IUser } from "interfaces/user";
|
||||
|
||||
import Icon from "components/Icon";
|
||||
import DropdownCell from "components/TableContainer/DataTable/DropdownCell";
|
||||
import ActionsDropdown from "components/ActionsDropdown";
|
||||
import {
|
||||
isGlobalAdmin,
|
||||
isTeamMaintainer,
|
||||
@@ -24,7 +24,7 @@ interface IStatusCellProps {
|
||||
};
|
||||
}
|
||||
|
||||
interface IDropdownCellProps {
|
||||
interface IActionsDropdownProps {
|
||||
cell: {
|
||||
value: IDropdownOption[];
|
||||
};
|
||||
@@ -94,7 +94,7 @@ export const generateTableColumnConfigs = (
|
||||
Header: "",
|
||||
disableSortBy: true,
|
||||
accessor: "actions",
|
||||
Cell: (cellProps: IDropdownCellProps) => {
|
||||
Cell: (cellProps: IActionsDropdownProps) => {
|
||||
if (scriptsDisabled) {
|
||||
// create a basic span that doesn't use the dropdown component (which relies on react-select
|
||||
// and makes it difficult for us to style the disabled tooltip underline on the placeholder text.
|
||||
@@ -120,7 +120,7 @@ export const generateTableColumnConfigs = (
|
||||
cellProps.row.original
|
||||
);
|
||||
return (
|
||||
<DropdownCell
|
||||
<ActionsDropdown
|
||||
options={opts}
|
||||
onChange={(value: string) =>
|
||||
onSelectAction(value, cellProps.row.original)
|
||||
|
||||
@@ -23,7 +23,7 @@ import PATHS from "router/paths";
|
||||
import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell";
|
||||
import TextCell from "components/TableContainer/DataTable/TextCell";
|
||||
import SoftwareNameCell from "components/TableContainer/DataTable/SoftwareNameCell";
|
||||
import DropdownCell from "components/TableContainer/DataTable/DropdownCell";
|
||||
import ActionsDropdown from "components/ActionsDropdown";
|
||||
|
||||
import VulnerabilitiesCell from "pages/SoftwarePage/components/VulnerabilitiesCell";
|
||||
import VersionCell from "pages/SoftwarePage/components/VersionCell";
|
||||
@@ -237,7 +237,7 @@ export const generateSoftwareTableHeaders = ({
|
||||
} = original;
|
||||
|
||||
return (
|
||||
<DropdownCell
|
||||
<ActionsDropdown
|
||||
placeholder="Actions"
|
||||
options={generateActions({
|
||||
userHasSWWritePermission,
|
||||
|
||||
@@ -26,4 +26,9 @@ export const COLORS = {
|
||||
"status-success": "#3DB67B",
|
||||
"status-warning": "#F8CD6B",
|
||||
"status-error": "#ED6E85",
|
||||
|
||||
"core-vibrant-blue-over": "#5d5ae7",
|
||||
"core-vibrant-blue-down": "#4b4ab4",
|
||||
"ui-vibrant-blue-25": "#d9d9fe",
|
||||
"ui-vibrant-blue-10": "#f1f0ff",
|
||||
};
|
||||
|
||||
@@ -294,6 +294,13 @@ $max-width: 2560px;
|
||||
|
||||
.Select-input {
|
||||
height: auto;
|
||||
|
||||
// When tabbing
|
||||
&:focus-visible {
|
||||
outline: 2px solid $ui-vibrant-blue-25;
|
||||
outline-offset: 1px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.Select-arrow-zone {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
const pxToRem = (px: number): string => {
|
||||
const baseSize = 16; // Assuming the base font size is 16px
|
||||
return `${px / baseSize}rem`;
|
||||
};
|
||||
|
||||
export const PADDING = {
|
||||
"pad-auto": "auto",
|
||||
"pad-xxsmall": pxToRem(2),
|
||||
"pad-xsmall": pxToRem(4),
|
||||
"pad-small": pxToRem(8),
|
||||
"pad-icon": pxToRem(14),
|
||||
"pad-medium": pxToRem(16),
|
||||
"pad-large": pxToRem(24),
|
||||
"pad-xlarge": pxToRem(32),
|
||||
"pad-xxlarge": pxToRem(40),
|
||||
"pad-xxxlarge": pxToRem(80),
|
||||
};
|
||||
|
||||
export type Padding = keyof typeof PADDING;
|
||||
Reference in New Issue
Block a user