diff --git a/apps/console-v5/src/routes/_authenticated/organization/$organizationId/settings/cloud-credentials.tsx b/apps/console-v5/src/routes/_authenticated/organization/$organizationId/settings/cloud-credentials.tsx index 6b3ca40bdac..f177678a8f0 100644 --- a/apps/console-v5/src/routes/_authenticated/organization/$organizationId/settings/cloud-credentials.tsx +++ b/apps/console-v5/src/routes/_authenticated/organization/$organizationId/settings/cloud-credentials.tsx @@ -1,9 +1,10 @@ import { createFileRoute } from '@tanstack/react-router' +import { SettingsCloudCredentials } from '@qovery/domains/organizations/feature' export const Route = createFileRoute('/_authenticated/organization/$organizationId/settings/cloud-credentials')({ component: RouteComponent, }) function RouteComponent() { - return
Hello "/_authenticated/organization/$organizationId/settings/cloud-credentials"!
+ return } diff --git a/libs/domains/clusters/feature/src/lib/cluster-credentials-modal/cluster-credentials-modal.spec.tsx b/libs/domains/clusters/feature/src/lib/cluster-credentials-modal/cluster-credentials-modal.spec.tsx index 5766797513f..931667a387a 100644 --- a/libs/domains/clusters/feature/src/lib/cluster-credentials-modal/cluster-credentials-modal.spec.tsx +++ b/libs/domains/clusters/feature/src/lib/cluster-credentials-modal/cluster-credentials-modal.spec.tsx @@ -2,7 +2,6 @@ import { wrapWithReactHookForm } from '__tests__/utils/wrap-with-react-hook-form import { CloudProviderEnum } from 'qovery-typescript-axios' import * as useCreateCloudProviderCredentialHook from '@qovery/domains/cloud-providers/feature' import * as useEditCloudProviderCredentialHook from '@qovery/domains/cloud-providers/feature' -import * as useDeleteCloudProviderCredentialHook from '@qovery/domains/cloud-providers/feature' import { getByText, renderWithProviders, screen } from '@qovery/shared/util-tests' import * as useClusterCloudProviderInfoHook from '../hooks/use-cluster-cloud-provider-info/use-cluster-cloud-provider-info' import ClusterCredentialsModal, { type ClusterCredentialsModalProps, handleSubmit } from './cluster-credentials-modal' @@ -10,7 +9,6 @@ import ClusterCredentialsModal, { type ClusterCredentialsModalProps, handleSubmi jest.mock('@qovery/domains/cloud-providers/feature', () => ({ useCreateCloudProviderCredential: jest.fn(), useEditCloudProviderCredential: jest.fn(), - useDeleteCloudProviderCredential: jest.fn(), })) jest.mock('../hooks/use-cluster-cloud-provider-info/use-cluster-cloud-provider-info', () => ({ @@ -21,7 +19,6 @@ let props: ClusterCredentialsModalProps const mockCreateCredential = jest.fn() const mockEditCredential = jest.fn() -const mockDeleteCredential = jest.fn() describe('ClusterCredentialsModal', () => { beforeEach(() => { @@ -42,10 +39,6 @@ describe('ClusterCredentialsModal', () => { isLoading: false, }) - jest.spyOn(useDeleteCloudProviderCredentialHook, 'useDeleteCloudProviderCredential').mockReturnValue({ - mutateAsync: mockDeleteCredential, - }) - jest.spyOn(useClusterCloudProviderInfoHook, 'useClusterCloudProviderInfo').mockReturnValue({ data: { cloud_provider: 'AWS' }, isLoading: false, @@ -132,27 +125,5 @@ describe('ClusterCredentialsModal', () => { expect(mockEditCredential).toHaveBeenCalled() }) - - it('should handle delete confirmation', async () => { - const { userEvent } = renderWithProviders(wrapWithReactHookForm()) - - const deleteButton = screen.getByTestId('delete-button') - await userEvent.click(deleteButton) - - const confirmationInput = screen.getByTestId('input-value') - await userEvent.type(confirmationInput, 'delete') - - const modalTitle = screen.getByText('Delete credential') - expect(modalTitle).toBeInTheDocument() - - const confirmationModal = modalTitle.parentElement - - if (confirmationModal) { - const confirmButton = getByText(confirmationModal, 'Confirm') - await userEvent.click(confirmButton) - } - - expect(mockDeleteCredential).toHaveBeenCalled() - }) }) }) diff --git a/libs/domains/clusters/feature/src/lib/cluster-credentials-modal/cluster-credentials-modal.tsx b/libs/domains/clusters/feature/src/lib/cluster-credentials-modal/cluster-credentials-modal.tsx index 2d8761120da..f0ee6265d58 100644 --- a/libs/domains/clusters/feature/src/lib/cluster-credentials-modal/cluster-credentials-modal.tsx +++ b/libs/domains/clusters/feature/src/lib/cluster-credentials-modal/cluster-credentials-modal.tsx @@ -5,7 +5,6 @@ import { Controller, type FieldValues, FormProvider, useForm } from 'react-hook- import { P, match } from 'ts-pattern' import { useCreateCloudProviderCredential, - useDeleteCloudProviderCredential, useEditCloudProviderCredential, } from '@qovery/domains/cloud-providers/feature' import { CLUSTER_SETTINGS_IMAGE_REGISTRY_URL, CLUSTER_SETTINGS_URL, CLUSTER_URL } from '@qovery/shared/routes' @@ -125,7 +124,7 @@ function CalloutEdit({ - + The credential change won't be applied to the mirroring registry of this cluster. Make sure to update the credentials properly in this cluster's mirroring registry section. {clusterId && ( @@ -162,7 +161,6 @@ export function ClusterCredentialsModal({ const { mutateAsync: createCloudProviderCredential, isLoading: isLoadingCreate } = useCreateCloudProviderCredential() const { mutateAsync: editCloudProviderCredential, isLoading: isLoadingEdit } = useEditCloudProviderCredential() - const { mutateAsync: deleteCloudProviderCredential } = useDeleteCloudProviderCredential() const [fileDetails, setFileDetails] = useState<{ name: string; size: number }>() const { getRootProps, getInputProps, isDragActive } = useDropzone({ @@ -293,33 +291,6 @@ export function ClusterCredentialsModal({ } }) - const onDelete = async () => { - openModalConfirmation({ - title: 'Delete credential', - description: ( -

- To confirm the deletion of {credential?.name}, please type "delete" -

- ), - name: credential?.name, - confirmationMethod: 'action', - action: async () => { - if (credential?.id) { - try { - await deleteCloudProviderCredential({ - organizationId, - cloudProvider: cloudProviderLocal, - credentialId: credential.id, - }) - onClose() - } catch (error) { - console.error(error) - } - } - }, - }) - } - const watchType = methods.watch('type') const watchAzureApplicationId = methods.watch('azure_application_id') const watchAzureSubscriptionId = methods.watch('azure_subscription_id') @@ -368,7 +339,6 @@ export function ClusterCredentialsModal({ } onSubmit={onSubmit} onClose={onClose} - onDelete={onDelete} loading={isLoadingCreate || isLoadingEdit} isEdit={isEdit} submitLabel={submitLabel} @@ -399,7 +369,7 @@ export function ClusterCredentialsModal({ {watchType === 'STATIC' && ( <> {cloudProviderLocal === 'AWS' && ( -
+

1. Create a user for Qovery

Follow the instructions available on this page

-
+

1. Connect to your GCP Console and create/open a project

@@ -421,11 +391,11 @@ export function ClusterCredentialsModal({ https://console.cloud.google.com/
-
+

2. Open the embedded Google shell and run the following command

-
+
$ curl https://setup.qovery.com/create_credentials_gcp.sh | \ bash -s -- $GOOGLE_CLOUD_PROJECT @@ -440,7 +410,7 @@ bash -s -- $GOOGLE_CLOUD_PROJECT qovery_role qovery-service-account" )} {cloudProviderLocal === 'SCW' && ( -
+

1. Generate Access key Id/Secret Access Key

Follow the instructions available on this page

-
+

1. Connect to your AWS Console

Make sure you are connected to the right AWS account

https://aws.amazon.com/fr/console/
-
+

2. Create a role for Qovery and grant assume role permissions

@@ -476,7 +446,7 @@ bash -s -- $GOOGLE_CLOUD_PROJECT qovery_role qovery-service-account" Cloudformation stack
-
+

3. Insert here the role ARN

) : ( -
+

{cloudProviderLocal === 'GCP' ? '3. Download the key.json generated and drag and drop it here' @@ -562,7 +532,7 @@ bash -s -- $GOOGLE_CLOUD_PROJECT qovery_role qovery-service-account" /> {isEditDirty && ( <> -
+
Confirm your secret key )} @@ -609,7 +579,7 @@ bash -s -- $GOOGLE_CLOUD_PROJECT qovery_role qovery-service-account" /> {isEditDirty && ( <> -
+
Confirm your secret key )} @@ -759,7 +729,7 @@ bash -s -- $GOOGLE_CLOUD_PROJECT qovery_role qovery-service-account" return ( <> -
+

2. Connect to your Azure Console and go to shell console

@@ -770,7 +740,7 @@ bash -s -- $GOOGLE_CLOUD_PROJECT qovery_role qovery-service-account" https://portal.azure.com/
-
+

3. Open the embedded Azure shell and run the following command

@@ -782,7 +752,7 @@ bash -s -- $GOOGLE_CLOUD_PROJECT qovery_role qovery-service-account" Please note that this script can take up to 30 seconds to complete.

-
+
$ {snippet} diff --git a/libs/domains/clusters/feature/src/lib/credentials-list-clusters-modal/credentials-list-clusters-modal.tsx b/libs/domains/clusters/feature/src/lib/credentials-list-clusters-modal/credentials-list-clusters-modal.tsx index 9ce32d52478..cbb546167da 100644 --- a/libs/domains/clusters/feature/src/lib/credentials-list-clusters-modal/credentials-list-clusters-modal.tsx +++ b/libs/domains/clusters/feature/src/lib/credentials-list-clusters-modal/credentials-list-clusters-modal.tsx @@ -1,8 +1,7 @@ import * as Dialog from '@radix-ui/react-dialog' import { type ClusterCredentials, type CredentialCluster } from 'qovery-typescript-axios' -import { Link } from 'react-router-dom' import { CLUSTER_SETTINGS_CREDENTIALS_URL, CLUSTER_SETTINGS_URL, CLUSTER_URL } from '@qovery/shared/routes' -import { Heading, Section } from '@qovery/shared/ui' +import { Heading, Link, Section } from '@qovery/shared/ui' import { pluralize } from '@qovery/shared/util-js' import { ClusterAvatar } from '../cluster-avatar/cluster-avatar' @@ -21,20 +20,23 @@ export function CredentialsListClustersModal({ return (
- + Attached {pluralize(clusters.length, 'cluster', 'clusters')} ({clusters.length}) - Credential: {credential.name} -
+ + Credential: {credential.name} + +
{clusters.map((cluster) => ( -
+
{cluster.name}
diff --git a/libs/domains/organizations/feature/src/index.ts b/libs/domains/organizations/feature/src/index.ts index 22aa22f8ebb..192c25fa35b 100644 --- a/libs/domains/organizations/feature/src/index.ts +++ b/libs/domains/organizations/feature/src/index.ts @@ -90,6 +90,7 @@ export * from './lib/invoice-banner/invoice-banner' export * from './lib/free-trial-banner/free-trial-banner' export * from './lib/settings-general/settings-general' export * from './lib/settings-labels-annotations/settings-labels-annotations' +export * from './lib/settings-cloud-credentials/settings-cloud-credentials' export * from './lib/settings-git-repository-access/settings-git-repository-access' export * from './lib/settings-helm-repositories/settings-helm-repositories' export * from './lib/settings-container-registries/settings-container-registries' diff --git a/libs/domains/organizations/feature/src/lib/settings-cloud-credentials/settings-cloud-credentials.spec.tsx b/libs/domains/organizations/feature/src/lib/settings-cloud-credentials/settings-cloud-credentials.spec.tsx new file mode 100644 index 00000000000..f2c5a9016a6 --- /dev/null +++ b/libs/domains/organizations/feature/src/lib/settings-cloud-credentials/settings-cloud-credentials.spec.tsx @@ -0,0 +1,108 @@ +import { type OrganizationCrendentialsResponseListResultsInner } from 'qovery-typescript-axios' +import { useDeleteCloudProviderCredential } from '@qovery/domains/cloud-providers/feature' +import { renderWithProviders, screen } from '@qovery/shared/util-tests' +import { useOrganizationCredentials } from '../hooks/use-organization-credentials/use-organization-credentials' +import { SettingsCloudCredentials } from './settings-cloud-credentials' + +jest.mock('../hooks/use-organization-credentials/use-organization-credentials') +jest.mock('@qovery/domains/cloud-providers/feature', () => ({ + ...jest.requireActual('@qovery/domains/cloud-providers/feature'), + useDeleteCloudProviderCredential: jest.fn(), +})) +jest.mock('@tanstack/react-router', () => ({ + ...jest.requireActual('@tanstack/react-router'), + useParams: () => ({ organizationId: 'org-1' }), +})) + +describe('SettingsCloudCredentials', () => { + const useOrganizationCredentialsMock = useOrganizationCredentials as jest.MockedFunction< + typeof useOrganizationCredentials + > + const useDeleteCloudProviderCredentialMock = useDeleteCloudProviderCredential as jest.MockedFunction< + typeof useDeleteCloudProviderCredential + > + + let mockCredentials: OrganizationCrendentialsResponseListResultsInner[] = [] + + beforeEach(() => { + mockCredentials = [] + useOrganizationCredentialsMock.mockReturnValue({ + data: mockCredentials, + } as ReturnType) + useDeleteCloudProviderCredentialMock.mockReturnValue({ + mutate: jest.fn(), + } as ReturnType) + }) + + it('should render', () => { + const { baseElement } = renderWithProviders() + expect(baseElement).toBeTruthy() + }) + + it('should render the content wrapper within the max-width container', () => { + const { container } = renderWithProviders() + + expect(container.querySelector('.max-w-content-with-navigation-left')).toBeInTheDocument() + }) + + it('should render an empty state when there are no credentials', () => { + renderWithProviders() + + screen.getByText('All credentials related to your clusters will appear here after creation.') + }) + + it('should render used and unused credentials with actions', () => { + mockCredentials = [ + { + credential: { + id: '1', + name: 'Credential 1', + object_type: 'AWS', + access_key_id: '123', + }, + clusters: [ + { + id: '1', + name: 'Cluster 1', + }, + ], + }, + { + credential: { + id: '2', + name: 'Credential 2', + object_type: 'GCP', + }, + clusters: [], + }, + ] + useOrganizationCredentialsMock.mockReturnValue({ + data: mockCredentials, + } as ReturnType) + + renderWithProviders() + + screen.getByText('Configured credentials') + screen.getByText('Unused credentials') + screen.getByText('Credential 1') + screen.getByText('Credential 2') + + expect(screen.getAllByTestId('view-credential')).toHaveLength(1) + expect(screen.getAllByTestId('delete-credential')).toHaveLength(1) + expect(screen.getByTestId('delete-credential')).toBeEnabled() + }) + + it('should show the cloud provider options in the create menu', async () => { + const { userEvent } = renderWithProviders() + + await userEvent.click(screen.getByText('New credential')) + + expect(await screen.findByText('AWS')).toBeInTheDocument() + expect(screen.getByText('GCP')).toBeInTheDocument() + expect(screen.getByText('Azure')).toBeInTheDocument() + expect(screen.getByText('Scaleway')).toBeInTheDocument() + + const awsItem = screen.getByText('AWS').closest('[role="menuitem"]') + expect(awsItem).toHaveClass('text-neutral') + }) +}) diff --git a/libs/pages/settings/src/lib/feature/page-organization-credentials-feature/page-organization-credentials-feature.tsx b/libs/domains/organizations/feature/src/lib/settings-cloud-credentials/settings-cloud-credentials.tsx similarity index 72% rename from libs/pages/settings/src/lib/feature/page-organization-credentials-feature/page-organization-credentials-feature.tsx rename to libs/domains/organizations/feature/src/lib/settings-cloud-credentials/settings-cloud-credentials.tsx index 6518ace7e9e..7ddb29b2f8e 100644 --- a/libs/pages/settings/src/lib/feature/page-organization-credentials-feature/page-organization-credentials-feature.tsx +++ b/libs/domains/organizations/feature/src/lib/settings-cloud-credentials/settings-cloud-credentials.tsx @@ -1,16 +1,16 @@ import { useQueryClient } from '@tanstack/react-query' +import { useParams } from '@tanstack/react-router' import { CloudProviderEnum, type ClusterCredentials, type CredentialCluster } from 'qovery-typescript-axios' import { Suspense, useMemo, useState } from 'react' -import { useParams } from 'react-router-dom' import { match } from 'ts-pattern' import { useDeleteCloudProviderCredential } from '@qovery/domains/cloud-providers/feature' import { ClusterAvatar, ClusterCredentialsModal, CredentialsListClustersModal } from '@qovery/domains/clusters/feature' -import { useOrganizationCredentials } from '@qovery/domains/organizations/feature' -import { NeedHelp } from '@qovery/shared/assistant/feature' -import { BlockContent, Heading, Section, Skeleton } from '@qovery/shared/ui' +import { SettingsHeading } from '@qovery/shared/console-shared' +import { BlockContent, Section, Skeleton } from '@qovery/shared/ui' import { Button, DropdownMenu, Icon, Indicator, useModal, useModalConfirmation } from '@qovery/shared/ui' import { useDocumentTitle } from '@qovery/shared/util-hooks' import { queries } from '@qovery/state/util-queries' +import { useOrganizationCredentials } from '../hooks/use-organization-credentials/use-organization-credentials' const convertToCloudProviderEnum = (cloudProvider: ClusterCredentials['object_type']): CloudProviderEnum => { return match(cloudProvider) @@ -34,7 +34,7 @@ type CredentialRowProps = { const CredentialRow = ({ credential, clusters, onEdit, onOpen, onDelete }: CredentialRowProps) => { return (
@@ -44,42 +44,42 @@ const CredentialRow = ({ credential, clusters, onEdit, onOpen, onDelete }: Crede className="-ml-1.5" />
- {credential.name} + {credential.name} {'role_arn' in credential && ( - Role ARN: - {credential.role_arn} + Role ARN: + {credential.role_arn} )} {'access_key_id' in credential && ( - Public Access Key: - {credential.access_key_id} + Public Access Key: + {credential.access_key_id} )} {'scaleway_access_key' in credential && ( - Access Key: - {credential.scaleway_access_key} + Access Key: + {credential.scaleway_access_key} )} {'scaleway_project_id' in credential && ( - Project ID: - {credential.scaleway_project_id} + Project ID: + {credential.scaleway_project_id} )} {'azure_tenant_id' in credential && ( - Tenant ID: - {credential.azure_tenant_id} + Tenant ID: + {credential.azure_tenant_id} )} {'azure_subscription_id' in credential && ( - Subscription ID: - {credential.azure_subscription_id} + Subscription ID: + {credential.azure_subscription_id} )}
@@ -88,19 +88,19 @@ const CredentialRow = ({ credential, clusters, onEdit, onOpen, onDelete }: Crede {clusters.length > 0 && ( + {clusters.length} } > @@ -109,12 +109,12 @@ const CredentialRow = ({ credential, clusters, onEdit, onOpen, onDelete }: Crede @@ -122,8 +122,9 @@ const CredentialRow = ({ credential, clusters, onEdit, onOpen, onDelete }: Crede {onDelete && ( - - - {cloudProviderOptions.map((option) => ( - } - onClick={() => onSelectProvider(option.value)} - > - {option.label} - - ))} - - -
+
+
+ + + + + + + {cloudProviderOptions.map((option) => ( + } + onClick={() => onSelectProvider(option.value)} + > + {option.label} + + ))} + + +
+
}> @@ -376,5 +381,3 @@ export function PageOrganizationCredentialsFeature() {
) } - -export default PageOrganizationCredentialsFeature diff --git a/libs/domains/organizations/feature/src/lib/settings-labels-annotations/settings-labels-annotations.tsx b/libs/domains/organizations/feature/src/lib/settings-labels-annotations/settings-labels-annotations.tsx index c17786e8e60..0e5b924528b 100644 --- a/libs/domains/organizations/feature/src/lib/settings-labels-annotations/settings-labels-annotations.tsx +++ b/libs/domains/organizations/feature/src/lib/settings-labels-annotations/settings-labels-annotations.tsx @@ -34,7 +34,7 @@ export function SettingsLabelsAnnotations() { const { openModalConfirmation } = useModalConfirmation() return ( -
+
diff --git a/libs/pages/settings/src/lib/feature/page-organization-credentials-feature/page-organization-credentials-feature.spec.tsx b/libs/pages/settings/src/lib/feature/page-organization-credentials-feature/page-organization-credentials-feature.spec.tsx deleted file mode 100644 index 200fac30f33..00000000000 --- a/libs/pages/settings/src/lib/feature/page-organization-credentials-feature/page-organization-credentials-feature.spec.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { type OrganizationCrendentialsResponseListResultsInner } from 'qovery-typescript-axios' -import { renderWithProviders, screen } from '@qovery/shared/util-tests' -import { PageOrganizationCredentialsFeature } from './page-organization-credentials-feature' - -let mockCredentials: OrganizationCrendentialsResponseListResultsInner[] = [] -jest.mock('@qovery/domains/organizations/feature', () => { - return { - ...jest.requireActual('@qovery/domains/organizations/feature'), - useOrganizationCredentials: () => ({ - data: mockCredentials, - isLoading: false, - }), - } -}) - -describe('PageOrganizationCredentialsFeature', () => { - it('should render', () => { - const { baseElement } = renderWithProviders() - expect(baseElement).toBeTruthy() - }) - - describe('when there are no credentials', () => { - it('should render an empty state', () => { - renderWithProviders() - screen.getByText('All credentials related to your clusters will appear here after creation.') - }) - }) - - describe('when there are credentials', () => { - it('should render the credentials', () => { - mockCredentials = [ - { - credential: { - id: '1', - name: 'Credential 1', - object_type: 'AWS', - access_key_id: '123', - }, - clusters: [], - }, - { - credential: { - id: '2', - name: 'Credential 2', - object_type: 'GCP', - }, - clusters: [ - { - id: '1', - name: 'Cluster 1', - }, - ], - }, - ] - renderWithProviders() - - screen.getByText('Credential 1') - screen.getByText('Credential 2') - - const row = screen.getByText('Credential 1').parentElement?.parentElement?.parentElement - const buttons = row?.querySelectorAll('button') - const deleteButton = buttons?.[1] - - expect(deleteButton).toBeInTheDocument() - expect(deleteButton).toBeEnabled() - }) - - it('delete button should be displayed only when no clusters are attached', () => { - mockCredentials = [ - { - credential: { - id: '1', - name: 'Credential 1', - object_type: 'AWS', - access_key_id: '123', - }, - clusters: [ - { - id: '1', - name: 'Cluster 1', - }, - ], - }, - { - credential: { - id: '2', - name: 'Credential 2', - object_type: 'GCP', - }, - clusters: [], - }, - ] - renderWithProviders() - - const row = screen.getByText('Credential 1').parentElement?.parentElement?.parentElement - const deleteButton = row?.querySelector('button[data-testid="delete-credential"]') - - expect(deleteButton).toBeNull() - }) - - it('view button should be displayed only if clusters are attached', () => { - mockCredentials = [ - { - credential: { - id: '1', - name: 'Credential 1', - object_type: 'AWS', - access_key_id: '123', - }, - clusters: [ - { - id: '1', - name: 'Cluster 1', - }, - ], - }, - { - credential: { - id: '2', - name: 'Credential 2', - object_type: 'GCP', - }, - clusters: [], - }, - ] - renderWithProviders() - - const row = screen.getByText('Credential 1').parentElement?.parentElement?.parentElement - const viewButton = row?.querySelector('button[data-testid="view-credential"]') // View button is the first button in the row - - expect(viewButton).toBeInTheDocument() - }) - }) -}) diff --git a/libs/shared/ui/src/lib/components/dropdown-menu/dropdown-menu.tsx b/libs/shared/ui/src/lib/components/dropdown-menu/dropdown-menu.tsx index 1f292c4dd94..e7ea341d0f1 100644 --- a/libs/shared/ui/src/lib/components/dropdown-menu/dropdown-menu.tsx +++ b/libs/shared/ui/src/lib/components/dropdown-menu/dropdown-menu.tsx @@ -26,6 +26,12 @@ const dropdownMenuItemVariants = cva( 'hover:bg-surface-brand-component', 'hover:text-brand', ], + neutral: [ + 'data-[highlighted]:bg-surface-neutral-component', + 'data-[highlighted]:text-neutral', + 'hover:bg-surface-neutral-component', + 'hover:text-neutral', + ], red: [ 'data-[highlighted]:bg-surface-negative-component', 'data-[highlighted]:text-negative', @@ -55,6 +61,16 @@ const dropdownMenuItemVariants = cva( disabled: false, className: ['text-neutral'], }, + { + color: 'neutral', + disabled: true, + className: ['text-neutral-disabled'], + }, + { + color: 'neutral', + disabled: false, + className: ['text-neutral'], + }, { color: 'red', disabled: true, @@ -82,6 +98,7 @@ const dropdownMenuItemIconVariants = cva(['text-sm', 'mr-2'], { variants: { color: { brand: [], + neutral: [], red: [], yellow: [], }, @@ -101,6 +118,16 @@ const dropdownMenuItemIconVariants = cva(['text-sm', 'mr-2'], { disabled: false, className: ['text-brand-9'], }, + { + color: 'neutral', + disabled: true, + className: ['text-neutral-disabled'], + }, + { + color: 'neutral', + disabled: false, + className: ['text-neutral'], + }, { color: 'red', disabled: true,