mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 22:27:48 +00:00
Merge pull request #3331 from Infisical/fix/groupPorjectsByProductOnInvites
Improve FilterableSelect to support optional grouping by option field
This commit is contained in:
@@ -1,81 +1,118 @@
|
|||||||
import Select, { Props } from "react-select";
|
import Select, { Props } from "react-select";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import { ClearIndicator, DropdownIndicator, MultiValueRemove, Option } from "../Select/components";
|
import {
|
||||||
|
ClearIndicator,
|
||||||
|
DropdownIndicator,
|
||||||
|
Group,
|
||||||
|
MultiValueRemove,
|
||||||
|
Option
|
||||||
|
} from "../Select/components";
|
||||||
|
|
||||||
export const FilterableSelect = <T,>({
|
export const FilterableSelect = <T,>({
|
||||||
isMulti,
|
isMulti,
|
||||||
closeMenuOnSelect,
|
closeMenuOnSelect,
|
||||||
tabSelectsValue = false,
|
tabSelectsValue = false,
|
||||||
|
groupBy = null,
|
||||||
|
getGroupHeaderLabel = null,
|
||||||
|
options = [],
|
||||||
...props
|
...props
|
||||||
}: Props<T>) => (
|
}: Props<T> & {
|
||||||
<Select
|
groupBy?: string | null;
|
||||||
isMulti={isMulti}
|
getGroupHeaderLabel?: ((groupValue: any) => string) | null;
|
||||||
closeMenuOnSelect={closeMenuOnSelect ?? !isMulti}
|
}) => {
|
||||||
hideSelectedOptions={false}
|
let processedOptions = options;
|
||||||
unstyled
|
|
||||||
styles={{
|
if (groupBy && Array.isArray(options)) {
|
||||||
input: (base) => ({
|
const groupedOptions = options.reduce((acc, option) => {
|
||||||
...base,
|
const groupValue = option[groupBy];
|
||||||
"input:focus": {
|
const groupKey = groupValue?.toString() || "undefined";
|
||||||
boxShadow: "none"
|
|
||||||
}
|
if (!acc[groupKey]) {
|
||||||
}),
|
acc[groupKey] = {
|
||||||
multiValueLabel: (base) => ({
|
label: getGroupHeaderLabel ? getGroupHeaderLabel(groupValue) : groupValue,
|
||||||
...base,
|
options: []
|
||||||
whiteSpace: "normal",
|
};
|
||||||
overflow: "visible"
|
}
|
||||||
}),
|
|
||||||
control: (base) => ({
|
acc[groupKey].options.push(option);
|
||||||
...base,
|
return acc;
|
||||||
transition: "none"
|
}, {});
|
||||||
})
|
|
||||||
}}
|
processedOptions = Object.values(groupedOptions);
|
||||||
tabSelectsValue={tabSelectsValue}
|
}
|
||||||
components={{
|
|
||||||
DropdownIndicator,
|
return (
|
||||||
ClearIndicator,
|
<Select
|
||||||
MultiValueRemove,
|
isMulti={isMulti}
|
||||||
Option,
|
closeMenuOnSelect={closeMenuOnSelect ?? !isMulti}
|
||||||
...props.components
|
hideSelectedOptions={false}
|
||||||
}}
|
unstyled
|
||||||
classNames={{
|
options={processedOptions}
|
||||||
container: ({ isDisabled }) =>
|
styles={{
|
||||||
twMerge("w-full font-inter text-sm", isDisabled && "!pointer-events-auto opacity-50"),
|
input: (base) => ({
|
||||||
control: ({ isFocused, isDisabled }) =>
|
...base,
|
||||||
twMerge(
|
"input:focus": {
|
||||||
isFocused ? "border-primary-400/50" : "border-mineshaft-600",
|
boxShadow: "none"
|
||||||
`w-full rounded-md border bg-mineshaft-900 p-0.5 font-inter text-mineshaft-200 ${
|
}
|
||||||
isDisabled ? "!cursor-not-allowed" : "hover:cursor-pointer hover:border-gray-400"
|
}),
|
||||||
} `
|
multiValueLabel: (base) => ({
|
||||||
),
|
...base,
|
||||||
placeholder: () =>
|
whiteSpace: "normal",
|
||||||
`${isMulti ? "py-[0.22rem]" : "leading-7"} text-mineshaft-400 text-sm pl-1`,
|
overflow: "visible"
|
||||||
input: () => "pl-1",
|
}),
|
||||||
valueContainer: () =>
|
control: (base) => ({
|
||||||
`px-1 max-h-[8.2rem] ${
|
...base,
|
||||||
isMulti ? "!overflow-y-auto thin-scrollbar py-1" : "py-[0.1rem]"
|
transition: "none"
|
||||||
} gap-1`,
|
})
|
||||||
singleValue: () => "leading-7 ml-1",
|
}}
|
||||||
multiValue: () => "bg-mineshaft-600 text-sm rounded items-center py-0.5 px-2 gap-1.5",
|
tabSelectsValue={tabSelectsValue}
|
||||||
multiValueLabel: () => "leading-6 text-sm",
|
components={{
|
||||||
multiValueRemove: () => "hover:text-red text-bunker-400",
|
DropdownIndicator,
|
||||||
indicatorsContainer: () => "p-1 gap-1",
|
ClearIndicator,
|
||||||
clearIndicator: () => "p-1 hover:text-red text-bunker-400",
|
MultiValueRemove,
|
||||||
indicatorSeparator: () => "bg-bunker-400",
|
Option,
|
||||||
dropdownIndicator: () => "text-bunker-200 p-1",
|
Group,
|
||||||
menuList: () => "flex flex-col gap-1",
|
...props.components
|
||||||
menu: () =>
|
}}
|
||||||
"my-2 p-2 border text-sm text-mineshaft-200 thin-scrollbar bg-mineshaft-900 border-mineshaft-600 rounded-md",
|
classNames={{
|
||||||
groupHeading: () => "ml-3 mt-2 mb-1 text-mineshaft-400 text-sm",
|
container: ({ isDisabled }) =>
|
||||||
option: ({ isFocused, isSelected }) =>
|
twMerge("w-full font-inter text-sm", isDisabled && "!pointer-events-auto opacity-50"),
|
||||||
twMerge(
|
control: ({ isFocused, isDisabled }) =>
|
||||||
isFocused && "bg-mineshaft-700 active:bg-mineshaft-600",
|
twMerge(
|
||||||
isSelected && "text-mineshaft-200",
|
isFocused ? "border-primary-400/50" : "border-mineshaft-600",
|
||||||
"rounded px-3 py-2 text-xs hover:cursor-pointer"
|
`w-full rounded-md border bg-mineshaft-900 p-0.5 font-inter text-mineshaft-200 ${
|
||||||
),
|
isDisabled ? "!cursor-not-allowed" : "hover:cursor-pointer hover:border-gray-400"
|
||||||
noOptionsMessage: () => "text-mineshaft-400 p-2 rounded-md"
|
} `
|
||||||
}}
|
),
|
||||||
{...props}
|
placeholder: () =>
|
||||||
/>
|
`${isMulti ? "py-[0.22rem]" : "leading-7"} text-mineshaft-400 text-sm pl-1`,
|
||||||
);
|
input: () => "pl-1",
|
||||||
|
valueContainer: () =>
|
||||||
|
`px-1 max-h-[8.2rem] ${
|
||||||
|
isMulti ? "!overflow-y-auto thin-scrollbar py-1" : "py-[0.1rem]"
|
||||||
|
} gap-1`,
|
||||||
|
singleValue: () => "leading-7 ml-1",
|
||||||
|
multiValue: () => "bg-mineshaft-600 text-sm rounded items-center py-0.5 px-2 gap-1.5",
|
||||||
|
multiValueLabel: () => "leading-6 text-sm",
|
||||||
|
multiValueRemove: () => "hover:text-red text-bunker-400",
|
||||||
|
indicatorsContainer: () => "p-1 gap-1",
|
||||||
|
clearIndicator: () => "p-1 hover:text-red text-bunker-400",
|
||||||
|
indicatorSeparator: () => "bg-bunker-400",
|
||||||
|
dropdownIndicator: () => "text-bunker-200 p-1",
|
||||||
|
menuList: () => "flex flex-col gap-1",
|
||||||
|
menu: () =>
|
||||||
|
"my-2 p-2 border text-sm text-mineshaft-200 thin-scrollbar bg-mineshaft-900 border-mineshaft-600 rounded-md",
|
||||||
|
groupHeading: () => "ml-3 mt-2 mb-1 text-mineshaft-400 text-sm",
|
||||||
|
option: ({ isFocused, isSelected }) =>
|
||||||
|
twMerge(
|
||||||
|
isFocused && "bg-mineshaft-700 active:bg-mineshaft-600",
|
||||||
|
isSelected && "text-mineshaft-200",
|
||||||
|
"rounded px-3 py-2 text-xs hover:cursor-pointer"
|
||||||
|
),
|
||||||
|
noOptionsMessage: () => "text-mineshaft-400 p-2 rounded-md"
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
ClearIndicatorProps,
|
ClearIndicatorProps,
|
||||||
components,
|
components,
|
||||||
DropdownIndicatorProps,
|
DropdownIndicatorProps,
|
||||||
|
GroupProps,
|
||||||
MultiValueRemoveProps,
|
MultiValueRemoveProps,
|
||||||
OptionProps
|
OptionProps
|
||||||
} from "react-select";
|
} from "react-select";
|
||||||
@@ -45,3 +46,7 @@ export const Option = <T,>({ isSelected, children, ...props }: OptionProps<T>) =
|
|||||||
</components.Option>
|
</components.Option>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Group = <T,>(props: GroupProps<T>) => {
|
||||||
|
return <components.Group {...props} />;
|
||||||
|
};
|
||||||
|
|||||||
+18
-1
@@ -23,7 +23,7 @@ import {
|
|||||||
useGetUserWorkspaces
|
useGetUserWorkspaces
|
||||||
} from "@app/hooks/api";
|
} from "@app/hooks/api";
|
||||||
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
import { ProjectMembershipRole } from "@app/hooks/api/roles/types";
|
||||||
import { ProjectVersion } from "@app/hooks/api/workspace/types";
|
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
import { OrgInviteLink } from "./OrgInviteLink";
|
import { OrgInviteLink } from "./OrgInviteLink";
|
||||||
@@ -168,6 +168,21 @@ export const AddOrgMemberModal = ({
|
|||||||
reset();
|
reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getGroupHeaderLabel = (type: ProjectType) => {
|
||||||
|
switch (type) {
|
||||||
|
case ProjectType.SecretManager:
|
||||||
|
return "Secrets";
|
||||||
|
case ProjectType.CertificateManager:
|
||||||
|
return "PKI";
|
||||||
|
case ProjectType.KMS:
|
||||||
|
return "KMS";
|
||||||
|
case ProjectType.SSH:
|
||||||
|
return "SSH";
|
||||||
|
default:
|
||||||
|
return "Other";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={popUp?.addMember?.isOpen}
|
isOpen={popUp?.addMember?.isOpen}
|
||||||
@@ -247,6 +262,8 @@ export const AddOrgMemberModal = ({
|
|||||||
getOptionLabel={(project) => project.name}
|
getOptionLabel={(project) => project.name}
|
||||||
getOptionValue={(project) => project.id}
|
getOptionValue={(project) => project.id}
|
||||||
options={projects}
|
options={projects}
|
||||||
|
groupBy="type"
|
||||||
|
getGroupHeaderLabel={getGroupHeaderLabel}
|
||||||
placeholder="Select projects..."
|
placeholder="Select projects..."
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
Reference in New Issue
Block a user