fix(ui): replace HeroUI dropdowns with shadcn selects (#10097)

This commit is contained in:
Alejandro Bailo
2026-02-18 13:46:57 +01:00
committed by GitHub
parent 639333b540
commit 5f2e4eb2a6
34 changed files with 846 additions and 998 deletions
-1
View File
@@ -1,4 +1,3 @@
export * from "../providers/enhanced-provider-selector";
export * from "./api-key/api-key-link-card";
export * from "./jira/jira-integration-card";
export * from "./jira/jira-integration-form";
@@ -8,12 +8,18 @@ import { useState } from "react";
import { Control, useForm } from "react-hook-form";
import { createIntegration, updateIntegration } from "@/actions/integrations";
import { EnhancedProviderSelector } from "@/components/providers/enhanced-provider-selector";
import { PROVIDER_ICONS } from "@/components/findings/table/provider-icon-cell";
import { AWSRoleCredentialsForm } from "@/components/providers/workflow/forms/select-credentials-type/aws/credentials-type/aws-role-credentials-form";
import { EnhancedMultiSelect } from "@/components/shadcn/select/enhanced-multi-select";
import { useToast } from "@/components/ui";
import { CustomInput } from "@/components/ui/custom";
import { CustomLink } from "@/components/ui/custom/custom-link";
import { Form } from "@/components/ui/form";
import {
Form,
FormControl,
FormField,
FormMessage,
} from "@/components/ui/form";
import { FormButtons } from "@/components/ui/form/form-buttons";
import { getAWSCredentialsTemplateLinks } from "@/lib";
import { AWSCredentialsRole } from "@/types";
@@ -272,18 +278,40 @@ export const S3IntegrationForm = ({
// Show configuration step (step 0 or editing configuration)
if (isEditingConfig || currentStep === 0) {
const providerOptions = providers.map((provider) => {
const Icon = PROVIDER_ICONS[provider.attributes.provider];
return {
value: provider.id,
label: provider.attributes.alias || provider.attributes.uid,
icon: Icon ? <Icon width={20} height={20} /> : undefined,
description: provider.attributes.connection.connected
? "Connected"
: "Disconnected",
};
});
return (
<>
{/* Provider Selection */}
<div className="flex flex-col gap-4">
<EnhancedProviderSelector
<FormField
control={form.control}
name="providers"
providers={providers}
label="Cloud Providers"
placeholder="Select providers to integrate with"
selectionMode="multiple"
enableSearch={true}
render={({ field }) => (
<>
<FormControl>
<EnhancedMultiSelect
options={providerOptions}
onValueChange={field.onChange}
defaultValue={field.value || []}
placeholder="Select providers to integrate with"
searchable={true}
maxCount={1}
/>
</FormControl>
<FormMessage className="text-text-error max-w-full text-xs" />
</>
)}
/>
</div>
@@ -6,15 +6,21 @@ import { Radio, RadioGroup } from "@heroui/radio";
import { zodResolver } from "@hookform/resolvers/zod";
import { ArrowLeftIcon, ArrowRightIcon } from "lucide-react";
import { useSession } from "next-auth/react";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";
import { Control, useForm } from "react-hook-form";
import { createIntegration, updateIntegration } from "@/actions/integrations";
import { EnhancedProviderSelector } from "@/components/providers/enhanced-provider-selector";
import { PROVIDER_ICONS } from "@/components/findings/table/provider-icon-cell";
import { AWSRoleCredentialsForm } from "@/components/providers/workflow/forms/select-credentials-type/aws/credentials-type/aws-role-credentials-form";
import { EnhancedMultiSelect } from "@/components/shadcn/select/enhanced-multi-select";
import { useToast } from "@/components/ui";
import { CustomLink } from "@/components/ui/custom/custom-link";
import { Form, FormControl, FormField } from "@/components/ui/form";
import {
Form,
FormControl,
FormField,
FormMessage,
} from "@/components/ui/form";
import { FormButtons } from "@/components/ui/form/form-buttons";
import { getAWSCredentialsTemplateLinks } from "@/lib";
import { AWSCredentialsRole } from "@/types";
@@ -52,7 +58,7 @@ export const SecurityHubIntegrationForm = ({
const isEditingConfig = editMode === "configuration";
const isEditingCredentials = editMode === "credentials";
const disabledProviderIds = useMemo(() => {
const disabledProviderIds = (() => {
// When editing, no providers should be disabled since we're not changing it
if (isEditing) {
return [];
@@ -69,7 +75,7 @@ export const SecurityHubIntegrationForm = ({
});
return usedProviderIds;
}, [isEditing, existingIntegrations]);
})();
const form = useForm({
resolver: zodResolver(
@@ -107,6 +113,26 @@ export const SecurityHubIntegrationForm = ({
const providerIdValue = form.watch("provider_id");
const hasErrors = !!form.formState.errors.provider_id || !providerIdValue;
const providerOptions = providers
.filter((provider) => provider.attributes.provider === "aws")
.map((provider) => {
const isDisabled = disabledProviderIds.includes(provider.id);
const connectionLabel = provider.attributes.connection.connected
? "Connected"
: "Disconnected";
const Icon = PROVIDER_ICONS[provider.attributes.provider];
return {
value: provider.id,
label: provider.attributes.alias || provider.attributes.uid,
icon: Icon ? <Icon width={20} height={20} /> : undefined,
description: isDisabled
? `${connectionLabel} (Already in use)`
: connectionLabel,
disabled: isDisabled,
};
});
useEffect(() => {
if (!useCustomCredentials && isCreating) {
setCurrentStep(0);
@@ -325,17 +351,29 @@ export const SecurityHubIntegrationForm = ({
{!isEditingConfig && (
<>
<div className="flex flex-col gap-4">
<EnhancedProviderSelector
<FormField
control={form.control}
name="provider_id"
providers={providers}
label="AWS Provider"
placeholder="Search and select an AWS provider"
isInvalid={!!form.formState.errors.provider_id}
selectionMode="single"
providerType="aws"
enableSearch={true}
disabledProviderIds={disabledProviderIds}
render={({ field }) => (
<>
<FormControl>
<EnhancedMultiSelect
options={providerOptions}
onValueChange={(values) => {
field.onChange(values.at(-1) ?? "");
}}
defaultValue={field.value ? [field.value] : []}
placeholder="Search and select an AWS provider"
searchable={true}
hideSelectAll={true}
maxCount={1}
closeOnSelect={true}
resetOnDefaultValueChange={true}
/>
</FormControl>
<FormMessage className="text-text-error max-w-full text-xs" />
</>
)}
/>
</div>
<Divider />
@@ -1,6 +1,5 @@
"use client";
import { Chip } from "@heroui/chip";
import { format } from "date-fns";
import { PlusIcon, Trash2Icon } from "lucide-react";
import { useState } from "react";
@@ -16,7 +15,7 @@ import {
IntegrationCardHeader,
IntegrationSkeleton,
} from "@/components/integrations/shared";
import { Button } from "@/components/shadcn";
import { Badge, Button } from "@/components/shadcn";
import { Modal } from "@/components/shadcn/modal";
import { useToast } from "@/components/ui";
import { DataTablePagination } from "@/components/ui/table/data-table-pagination";
@@ -385,14 +384,13 @@ export const SecurityHubIntegrationsManager = ({
{enabledRegions.length > 0 && (
<div className="flex flex-wrap gap-1">
{enabledRegions.map((region) => (
<Chip
<Badge
key={region}
size="sm"
variant="flat"
className="bg-bg-neutral-secondary"
variant="outline"
className="border-border-neutral-secondary bg-bg-neutral-secondary text-text-neutral-primary text-xs font-normal"
>
{region}
</Chip>
</Badge>
))}
</div>
)}
@@ -1,23 +1,18 @@
"use client";
import { Chip } from "@heroui/chip";
import { ExternalLink } from "lucide-react";
import { ReactNode } from "react";
import { Badge } from "@/components/shadcn";
import { cn } from "@/lib/utils";
interface IntegrationCardHeaderProps {
icon: ReactNode;
title: string;
subtitle?: string;
chips?: Array<{
label: string;
color?:
| "default"
| "primary"
| "secondary"
| "success"
| "warning"
| "danger";
variant?: "solid" | "bordered" | "light" | "flat" | "faded" | "shadow";
className?: string;
}>;
connectionStatus?: {
connected: boolean;
@@ -63,25 +58,30 @@ export const IntegrationCardHeader = ({
{(chips.length > 0 || connectionStatus) && (
<div className="flex flex-wrap items-center gap-2">
{chips.map((chip, index) => (
<Chip
<Badge
key={index}
size="sm"
variant={chip.variant || "flat"}
color={chip.color || "default"}
className="text-xs"
variant="outline"
className={cn(
"border-border-neutral-secondary bg-bg-neutral-secondary text-text-neutral-primary text-xs font-normal",
chip.className,
)}
>
{chip.label}
</Chip>
</Badge>
))}
{connectionStatus && (
<Chip
size="sm"
color={connectionStatus.connected ? "success" : "danger"}
variant="flat"
<Badge
variant="outline"
className={cn(
"text-xs font-normal",
connectionStatus.connected
? "bg-bg-pass-secondary text-text-success-primary border-transparent"
: "bg-bg-danger-secondary text-text-danger border-transparent",
)}
>
{connectionStatus.label ||
(connectionStatus.connected ? "Connected" : "Disconnected")}
</Chip>
</Badge>
)}
</div>
)}