From c4a71049f52aaf4af1342eb7abb215a02c7d1edb Mon Sep 17 00:00:00 2001 From: Celia Amador Date: Thu, 11 Jun 2026 11:58:52 +0200 Subject: [PATCH] EDM-4153: Improve layout of Repository select dropdown Made-with: Cursor --- libs/i18n/locales/en/translation.json | 1 - .../steps/OutputImageStep.tsx | 14 +- .../src/components/form/FormSelect.css | 13 ++ .../src/components/form/RepositorySelect.tsx | 120 ++++++++---------- 4 files changed, 65 insertions(+), 83 deletions(-) diff --git a/libs/i18n/locales/en/translation.json b/libs/i18n/locales/en/translation.json index e45250668..4e5d6473c 100644 --- a/libs/i18n/locales/en/translation.json +++ b/libs/i18n/locales/en/translation.json @@ -1216,7 +1216,6 @@ "Build image": "Build image", "You do not have permissions to promote builds to the catalog": "You do not have permissions to promote builds to the catalog", "Add to the software catalog testing channel upon successful build": "Add to the software catalog testing channel upon successful build", - "Repositories used for output images must be writable.": "Repositories used for output images must be writable.", "Management-ready by default": "Management-ready by default", "The agent is automatically included in this image. This ensures your devices are ready to be managed immediately after they are deployed.": "The agent is automatically included in this image. This ensures your devices are ready to be managed immediately after they are deployed.", "Target repository": "Target repository", diff --git a/libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/OutputImageStep.tsx b/libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/OutputImageStep.tsx index 5602295b8..945abb754 100644 --- a/libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/OutputImageStep.tsx +++ b/libs/ui-components/src/components/ImageBuilds/CreateImageBuildWizard/steps/OutputImageStep.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import { Alert, Content, FormGroup, FormSection, Gallery } from '@patternfly/react-core'; import { FormikErrors, useFormikContext } from 'formik'; -import { OciRepoSpec, RepoSpecType, Repository } from '@flightctl/types'; +import { RepoSpecType } from '@flightctl/types'; import { ExportFormatType } from '@flightctl/types/imagebuilder'; import { ImageBuildFormValues } from '../types'; import { useTranslation } from '../../../../hooks/useTranslation'; @@ -13,7 +13,6 @@ import { usePermissionsContext } from '../../../common/PermissionsContext'; import { RESOURCE, VERB } from '../../../../types/rbac'; import { SelectImageBuildExportCard } from '../../ImageExportCards'; import { getAllExportFormats } from '../../../../utils/imageBuilds'; -import { isOciRepoSpec } from '../../../Repository/CreateRepository/utils'; import { useOciRegistriesContext } from '../../OciRegistriesContext'; import ImageUrlCard from '../../ImageUrlCard'; @@ -34,16 +33,6 @@ const OutputImageStep = () => { const { ociRegistries, refetch } = useOciRegistriesContext(); const [canCreateRepo] = checkPermissions([{ kind: RESOURCE.REPOSITORY, verb: VERB.CREATE }]); - const writableRepoValidation = React.useCallback( - (repo: Repository) => { - if (isOciRepoSpec(repo.spec) && repo.spec.accessMode === OciRepoSpec.accessMode.READ) { - return t('Repositories used for output images must be writable.'); - } - return undefined; - }, - [t], - ); - const handleFormatToggle = (format: ExportFormatType, isChecked: boolean) => { const currentFormats = values.exportFormats; if (isChecked) { @@ -75,7 +64,6 @@ const OutputImageStep = () => { options={{ writeAccessOnly: true, }} - validateRepoSelection={writableRepoValidation} helperText={t( 'Only OCI-compliant registries are shown. Other repository types, such as Git or HTTP, are not supported for image builds.', )} diff --git a/libs/ui-components/src/components/form/FormSelect.css b/libs/ui-components/src/components/form/FormSelect.css index 59d6ffba2..5529c1a5d 100644 --- a/libs/ui-components/src/components/form/FormSelect.css +++ b/libs/ui-components/src/components/form/FormSelect.css @@ -7,6 +7,19 @@ overflow: auto; } +.fctl-form-select__menu .pf-v6-c-menu__item-main { + position: relative; + padding-inline-end: calc(var(--pf-t--global--icon--size--md) + var(--pf-v6-c-menu__item-main--ColumnGap)); +} + +/* Render the select checkmark with absolute position so it doesn't affect the alignment of the content */ +.fctl-form-select__menu .pf-v6-c-menu__item-select-icon { + position: absolute; + inset-inline-end: 0; + top: 50%; + transform: translateY(-50%); +} + .fctl-form-select { max-width: 30rem; } diff --git a/libs/ui-components/src/components/form/RepositorySelect.tsx b/libs/ui-components/src/components/form/RepositorySelect.tsx index bc05a8baa..7e05af7f8 100644 --- a/libs/ui-components/src/components/form/RepositorySelect.tsx +++ b/libs/ui-components/src/components/form/RepositorySelect.tsx @@ -2,10 +2,12 @@ import * as React from 'react'; import { useField, useFormikContext } from 'formik'; import { Button, + Content, + ContentVariants, Divider, - Flex, - FlexItem, FormGroup, + Grid, + GridItem, Icon, MenuFooter, SelectList, @@ -31,8 +33,6 @@ export const getRepositoryItems = ( repositories: Repository[], repoType: RepoSpecType, selectedRepoName?: string, - // Returns an error message if the repository cannot be selected - validateRepoSelection?: (repo: Repository) => string | undefined, ) => { const invalidRepoItems: Record = {}; const validRepoItems: Record = {}; @@ -42,50 +42,40 @@ export const getRepositoryItems = ( return repo.spec.type === repoType; }) .forEach((repo) => { - const selectionError = validateRepoSelection ? validateRepoSelection(repo) : undefined; const repoName = repo.metadata.name as string; - if (selectionError) { - invalidRepoItems[repoName] = { - label: repoName, - description: ( - - {getRepoUrlOrRegistry(repo.spec)} - {selectionError} - - ), - }; - } else { - const accessibleCondition = repo.status?.conditions?.find((c) => c.type === ConditionType.RepositoryAccessible); - const isAccessible = accessibleCondition && accessibleCondition.status === ConditionStatus.ConditionStatusTrue; - const isInaccessible = - accessibleCondition && accessibleCondition.status === ConditionStatus.ConditionStatusFalse; - const urlOrRegistry = getRepoUrlOrRegistry(repo.spec); - - let accessText = t('Unknown'); - let level: StatusLevel = 'unknown'; - if (isAccessible) { - accessText = t('Available'); - level = 'success'; - } else if (isInaccessible) { - accessText = t('Not available'); - level = 'danger'; - } - - validRepoItems[repoName] = { - label: repoName, - description: ( - - {urlOrRegistry} - - - - - ), - }; + const accessibleCondition = repo.status?.conditions?.find((c) => c.type === ConditionType.RepositoryAccessible); + const isAccessible = accessibleCondition && accessibleCondition.status === ConditionStatus.ConditionStatusTrue; + const isInaccessible = accessibleCondition && accessibleCondition.status === ConditionStatus.ConditionStatusFalse; + const urlOrRegistry = getRepoUrlOrRegistry(repo.spec); + + let accessText = t('Unknown'); + let level: StatusLevel = 'unknown'; + if (isAccessible) { + accessText = t('Available'); + level = 'success'; + } else if (isInaccessible) { + accessText = t('Not available'); + level = 'danger'; } + + validRepoItems[repoName] = { + label: ( + + + + {repoName} + + {urlOrRegistry} + + + + + + + + ), + selectedLabel: repoName, + }; }); // If the selected repository has been removed, we still consider it "valid" since it needs to be selected initially @@ -93,15 +83,18 @@ export const getRepositoryItems = ( selectedRepoName && !repositories.some((repo) => repo.metadata.name === selectedRepoName); if (isSelectedRepoMissing && !validRepoItems[selectedRepoName]) { validRepoItems[selectedRepoName] = { - label: selectedRepoName, - description: ( - <> - - - {' '} - {t('Missing repository')} - + label: ( + + {selectedRepoName} + + + + {' '} + {t('Missing repository')} + + ), + selectedLabel: selectedRepoName, }; } @@ -121,7 +114,6 @@ type RepositorySelectProps = { writeAccessOnly?: boolean; }; isRequired?: boolean; - validateRepoSelection?: (repo: Repository) => string | undefined; }; const ReadOnlyRepositoryListItem = ({ invalidRepoItems }: { invalidRepoItems: Record }) => { @@ -134,7 +126,7 @@ const ReadOnlyRepositoryListItem = ({ invalidRepoItems }: { invalidRepoItems: Re {itemKeys.map((key) => { const item = invalidRepoItems[key]; return ( - + {item.label} ); @@ -154,16 +146,15 @@ const RepositorySelect = ({ helperText, options, isRequired, - validateRepoSelection, }: RepositorySelectProps) => { const { t } = useTranslation(); - const { setFieldValue, setFieldError } = useFormikContext(); + const { setFieldValue } = useFormikContext(); const [field] = useField(name); const [createRepoModalOpen, setCreateRepoModalOpen] = React.useState(false); const { validRepoItems, invalidRepoItems } = React.useMemo(() => { - return getRepositoryItems(t, repositories, repoType, field.value, validateRepoSelection); - }, [t, repositories, repoType, field.value, validateRepoSelection]); + return getRepositoryItems(t, repositories, repoType, field.value); + }, [t, repositories, repoType, field.value]); const handleCreateRepository = (repo: Repository) => { setCreateRepoModalOpen(false); @@ -171,15 +162,6 @@ const RepositorySelect = ({ repoRefetch(); } - // If the created repository cannot be selected, we set the error and skip marking the repository as selected - if (validateRepoSelection) { - const selectionError = validateRepoSelection(repo); - if (selectionError) { - setFieldError(name, selectionError); - return; - } - } - void setFieldValue(name, repo.metadata.name, true); };