Wizard: Replace toggle with tabs

This replaces the previously used "Included/Other repos" toggle with tabs with the same functionality.
This commit is contained in:
regexowl 2025-04-09 09:59:14 +02:00 committed by Lucas Garfield
parent b0709ca9ce
commit ad2d5d8e2b
2 changed files with 144 additions and 127 deletions

View file

@ -19,8 +19,10 @@ import {
Popover, Popover,
Spinner, Spinner,
Stack, Stack,
Tab,
Tabs,
TabTitleText,
Text, Text,
TextContent,
TextInput, TextInput,
ToggleGroup, ToggleGroup,
ToggleGroupItem, ToggleGroupItem,
@ -41,6 +43,10 @@ import { useDispatch } from 'react-redux';
import CustomHelperText from './components/CustomHelperText'; import CustomHelperText from './components/CustomHelperText';
import PackageInfoNotAvailablePopover from './components/PackageInfoNotAvailablePopover'; import PackageInfoNotAvailablePopover from './components/PackageInfoNotAvailablePopover';
import {
IncludedReposPopover,
OtherReposPopover,
} from './components/RepoPopovers';
import { import {
CONTENT_URL, CONTENT_URL,
@ -91,9 +97,9 @@ export type GroupWithRepositoryInfo = {
package_list: string[]; package_list: string[];
}; };
export enum RepoToggle { export enum Repos {
INCLUDED = 'toggle-included-repos', INCLUDED = 'included-repos',
OTHER = 'toggle-other-repos', OTHER = 'other-repos',
} }
export const RedHatRepository = () => { export const RedHatRepository = () => {
@ -151,10 +157,7 @@ const Packages = () => {
const [perPage, setPerPage] = useState(10); const [perPage, setPerPage] = useState(10);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [toggleSelected, setToggleSelected] = useState('toggle-available'); const [toggleSelected, setToggleSelected] = useState('toggle-available');
const [activeTabKey, setActiveTabKey] = useState(Repos.INCLUDED);
const [toggleSourceRepos, setToggleSourceRepos] = useState<RepoToggle>(
RepoToggle.INCLUDED
);
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [ const [
@ -259,10 +262,7 @@ const Packages = () => {
} }
} }
if (debouncedSearchTerm.length > 2) { if (debouncedSearchTerm.length > 2) {
if ( if (activeTabKey === Repos.INCLUDED && customRepositories.length > 0) {
toggleSourceRepos === RepoToggle.INCLUDED &&
customRepositories.length > 0
) {
searchCustomRpms({ searchCustomRpms({
apiContentUnitSearchRequest: { apiContentUnitSearchRequest: {
search: debouncedSearchTerm, search: debouncedSearchTerm,
@ -286,7 +286,7 @@ const Packages = () => {
searchCustomRpms, searchCustomRpms,
searchDistroRpms, searchDistroRpms,
debouncedSearchTerm, debouncedSearchTerm,
toggleSourceRepos, activeTabKey,
searchRecommendedRpms, searchRecommendedRpms,
epelRepoUrlByDistribution, epelRepoUrlByDistribution,
isSuccessDistroRepositories, isSuccessDistroRepositories,
@ -316,10 +316,7 @@ const Packages = () => {
}, },
}); });
} }
if ( if (activeTabKey === Repos.INCLUDED && customRepositories.length > 0) {
toggleSourceRepos === RepoToggle.INCLUDED &&
customRepositories.length > 0
) {
searchCustomGroups({ searchCustomGroups({
apiContentUnitSearchRequest: { apiContentUnitSearchRequest: {
search: debouncedSearchTerm.substr(1), search: debouncedSearchTerm.substr(1),
@ -328,7 +325,7 @@ const Packages = () => {
}), }),
}, },
}); });
} else if (toggleSourceRepos === RepoToggle.OTHER && isSuccessEpelRepo) { } else if (activeTabKey === Repos.OTHER && isSuccessEpelRepo) {
searchRecommendedGroups({ searchRecommendedGroups({
apiContentUnitSearchRequest: { apiContentUnitSearchRequest: {
search: debouncedSearchTerm.substr(1), search: debouncedSearchTerm.substr(1),
@ -342,7 +339,7 @@ const Packages = () => {
searchCustomGroups, searchCustomGroups,
searchRecommendedGroups, searchRecommendedGroups,
debouncedSearchTerm, debouncedSearchTerm,
toggleSourceRepos, activeTabKey,
epelRepoUrlByDistribution, epelRepoUrlByDistribution,
]); ]);
@ -381,7 +378,7 @@ const Packages = () => {
<EmptyState variant={EmptyStateVariant.sm}> <EmptyState variant={EmptyStateVariant.sm}>
<EmptyStateHeader icon={<EmptyStateIcon icon={Spinner} />} /> <EmptyStateHeader icon={<EmptyStateIcon icon={Spinner} />} />
<EmptyStateBody> <EmptyStateBody>
{toggleSourceRepos === RepoToggle.OTHER {activeTabKey === Repos.OTHER
? 'Searching for recommendations' ? 'Searching for recommendations'
: 'Searching'} : 'Searching'}
</EmptyStateBody> </EmptyStateBody>
@ -427,7 +424,7 @@ const Packages = () => {
Try looking under &quot; Try looking under &quot;
<Button <Button
variant="link" variant="link"
onClick={() => setToggleSourceRepos(RepoToggle.INCLUDED)} onClick={() => setActiveTabKey(Repos.INCLUDED)}
isInline isInline
> >
Included repos Included repos
@ -442,7 +439,7 @@ const Packages = () => {
}; };
const NoResultsFound = () => { const NoResultsFound = () => {
if (toggleSourceRepos === RepoToggle.INCLUDED) { if (activeTabKey === Repos.INCLUDED) {
return ( return (
<Tr> <Tr>
<Td colSpan={5}> <Td colSpan={5}>
@ -462,7 +459,7 @@ const Packages = () => {
<Button <Button
variant="primary" variant="primary"
ouiaId="search-other-repositories" ouiaId="search-other-repositories"
onClick={() => setToggleSourceRepos(RepoToggle.OTHER)} onClick={() => setActiveTabKey(Repos.OTHER)}
> >
Search other repositories Search other repositories
</Button> </Button>
@ -620,7 +617,7 @@ const Packages = () => {
debouncedSearchTerm !== '' && debouncedSearchTerm !== '' &&
combinedPackageData.length === 0 && combinedPackageData.length === 0 &&
isSuccessRecommendedPackages && isSuccessRecommendedPackages &&
toggleSourceRepos === RepoToggle.OTHER activeTabKey === Repos.OTHER
) { ) {
transformedRecommendedData = dataRecommendedPackages!.map((values) => ({ transformedRecommendedData = dataRecommendedPackages!.map((values) => ({
name: values.package_name!, name: values.package_name!,
@ -634,7 +631,7 @@ const Packages = () => {
} }
if (toggleSelected === 'toggle-available') { if (toggleSelected === 'toggle-available') {
if (toggleSourceRepos === RepoToggle.INCLUDED) { if (activeTabKey === Repos.INCLUDED) {
return combinedPackageData.filter( return combinedPackageData.filter(
(pkg) => pkg.repository !== 'recommended' (pkg) => pkg.repository !== 'recommended'
); );
@ -648,7 +645,7 @@ const Packages = () => {
if (currentlyRemovedPackages.length > 0) { if (currentlyRemovedPackages.length > 0) {
selectedPackages.push(...currentlyRemovedPackages); selectedPackages.push(...currentlyRemovedPackages);
} }
if (toggleSourceRepos === RepoToggle.INCLUDED) { if (activeTabKey === Repos.INCLUDED) {
return selectedPackages.sort((a, b) => return selectedPackages.sort((a, b) =>
sortfn(a.name, b.name, debouncedSearchTerm) sortfn(a.name, b.name, debouncedSearchTerm)
); );
@ -667,7 +664,7 @@ const Packages = () => {
isSuccessRecommendedPackages, isSuccessRecommendedPackages,
packages, packages,
toggleSelected, toggleSelected,
toggleSourceRepos, activeTabKey,
]); ]);
const transformedGroups = useMemo(() => { const transformedGroups = useMemo(() => {
@ -705,7 +702,7 @@ const Packages = () => {
} }
if (toggleSelected === 'toggle-available') { if (toggleSelected === 'toggle-available') {
if (toggleSourceRepos === RepoToggle.INCLUDED) { if (activeTabKey === Repos.INCLUDED) {
return combinedGroupData.filter( return combinedGroupData.filter(
(pkg) => pkg.repository !== 'recommended' (pkg) => pkg.repository !== 'recommended'
); );
@ -716,7 +713,7 @@ const Packages = () => {
} }
} else { } else {
const selectedGroups = [...groups]; const selectedGroups = [...groups];
if (toggleSourceRepos === RepoToggle.INCLUDED) { if (activeTabKey === Repos.INCLUDED) {
return selectedGroups; return selectedGroups;
} else { } else {
return []; return [];
@ -734,7 +731,7 @@ const Packages = () => {
isSuccessRecommendedGroups, isSuccessRecommendedGroups,
groups, groups,
toggleSelected, toggleSelected,
toggleSourceRepos, activeTabKey,
]); ]);
const handleSearch = async ( const handleSearch = async (
@ -742,13 +739,13 @@ const Packages = () => {
selection: string selection: string
) => { ) => {
setSearchTerm(selection); setSearchTerm(selection);
setToggleSourceRepos(RepoToggle.INCLUDED); setActiveTabKey(Repos.INCLUDED);
setToggleSelected('toggle-available'); setToggleSelected('toggle-available');
}; };
const handleClear = async () => { const handleClear = async () => {
setSearchTerm(''); setSearchTerm('');
setToggleSourceRepos(RepoToggle.INCLUDED); setActiveTabKey(Repos.INCLUDED);
}; };
const handleSelect = ( const handleSelect = (
@ -823,14 +820,6 @@ const Packages = () => {
setToggleSelected(id); setToggleSelected(id);
}; };
const handleRepoToggleClick = (type: RepoToggle) => {
if (toggleSourceRepos !== type) {
setCurrentlyRemovedPackages([]);
setPage(1);
setToggleSourceRepos(type);
}
};
const handleSetPage = (_: React.MouseEvent, newPage: number) => { const handleSetPage = (_: React.MouseEvent, newPage: number) => {
setPage(newPage); setPage(newPage);
}; };
@ -882,6 +871,14 @@ const Packages = () => {
setIsRepoModalOpen(!isRepoModalOpen); setIsRepoModalOpen(!isRepoModalOpen);
}; };
const handleTabClick = (event: React.MouseEvent, tabIndex: Repos) => {
if (tabIndex !== activeTabKey) {
setCurrentlyRemovedPackages([]);
setPage(1);
setActiveTabKey(tabIndex);
}
};
const composePkgTable = () => { const composePkgTable = () => {
let rows: ReactElement[] = []; let rows: ReactElement[] = [];
@ -1071,13 +1068,13 @@ const Packages = () => {
return <EmptySearch />; return <EmptySearch />;
case (debouncedSearchTerm && case (debouncedSearchTerm &&
(isLoadingRecommendedPackages || isLoadingRecommendedGroups) && (isLoadingRecommendedPackages || isLoadingRecommendedGroups) &&
toggleSourceRepos === RepoToggle.OTHER) || activeTabKey === Repos.OTHER) ||
(debouncedSearchTerm && (debouncedSearchTerm &&
(isLoadingDistroPackages || (isLoadingDistroPackages ||
isLoadingCustomPackages || isLoadingCustomPackages ||
isLoadingDistroGroups || isLoadingDistroGroups ||
isLoadingCustomGroups) && isLoadingCustomGroups) &&
toggleSourceRepos === RepoToggle.INCLUDED): activeTabKey === Repos.INCLUDED):
return <Searching />; return <Searching />;
case debouncedSearchTerm && case debouncedSearchTerm &&
transformedPackages.length === 0 && transformedPackages.length === 0 &&
@ -1086,7 +1083,7 @@ const Packages = () => {
return <NoResultsFound />; return <NoResultsFound />;
case debouncedSearchTerm && case debouncedSearchTerm &&
toggleSelected === 'toggle-selected' && toggleSelected === 'toggle-selected' &&
toggleSourceRepos === RepoToggle.OTHER && activeTabKey === Repos.OTHER &&
packages.length > 0 && packages.length > 0 &&
groups.length > 0: groups.length > 0:
return <TryLookingUnderIncluded />; return <TryLookingUnderIncluded />;
@ -1111,7 +1108,7 @@ const Packages = () => {
packages.length, packages.length,
groups.length, groups.length,
toggleSelected, toggleSelected,
toggleSourceRepos, activeTabKey,
transformedPackages, transformedPackages,
isSelectingPackage, isSelectingPackage,
recommendedRepositories, recommendedRepositories,
@ -1119,6 +1116,28 @@ const Packages = () => {
transformedGroups.length, transformedGroups.length,
]); ]);
const PackagesTable = () => {
return (
<Table variant="compact" data-testid="packages-table">
<Thead>
<Tr>
<Th aria-label="Selected" />
<Th width={20}>Package name</Th>
<Th width={35}>
Description
{toggleSelected === 'toggle-selected' && (
<PackageInfoNotAvailablePopover />
)}
</Th>
<Th width={25}>Package repository</Th>
<Th width={20}>Support</Th>
</Tr>
</Thead>
<Tbody>{bodyContent}</Tbody>
</Table>
);
};
return ( return (
<> <>
<RepositoryModal /> <RepositoryModal />
@ -1182,72 +1201,6 @@ const Packages = () => {
/> />
</ToggleGroup> </ToggleGroup>
</ToolbarItem> </ToolbarItem>
<ToolbarItem>
<ToggleGroup>
<ToggleGroupItem
text={
<>
Included repos{' '}
<Popover
bodyContent={
<TextContent>
<Text>
View packages from the Red Hat repository and
repositories you&apos;ve selected.
</Text>
</TextContent>
}
>
<Button
variant="plain"
aria-label="About included repositories"
component="span"
className="pf-v5-u-p-0"
size="sm"
isInline
>
<HelpIcon />
</Button>
</Popover>
</>
}
buttonId={RepoToggle.INCLUDED}
isSelected={toggleSourceRepos === RepoToggle.INCLUDED}
onChange={() => handleRepoToggleClick(RepoToggle.INCLUDED)}
/>
<ToggleGroupItem
text={
<>
Other repos{' '}
<Popover
bodyContent={
<TextContent>
<Text>
View packages from popular repositories and your
other repositories not included in the image.
</Text>
</TextContent>
}
>
<Button
variant="plain"
aria-label="About other repositories"
component="span"
className="pf-v5-u-p-0"
size="sm"
isInline
>
<HelpIcon />
</Button>
</Popover>
</>
}
buttonId="toggle-other-repos"
isSelected={toggleSourceRepos === RepoToggle.OTHER}
onChange={() => handleRepoToggleClick(RepoToggle.OTHER)}
/>
</ToggleGroup>
</ToolbarItem>
<ToolbarItem variant="pagination"> <ToolbarItem variant="pagination">
<Pagination <Pagination
data-testid="packages-pagination-top" data-testid="packages-pagination-top"
@ -1277,23 +1230,31 @@ const Packages = () => {
</Stack> </Stack>
</Toolbar> </Toolbar>
<Table variant="compact" data-testid="packages-table"> <Tabs
<Thead> activeKey={activeTabKey}
<Tr> onSelect={handleTabClick}
<Th aria-label="Selected" /> aria-label="Repositories tabs on packages step"
<Th width={20}>Package name</Th> >
<Th width={35}> <Tab
Description eventKey="included-repos"
{toggleSelected === 'toggle-selected' && ( title={
<PackageInfoNotAvailablePopover /> <TabTitleText>
)} Included repos <IncludedReposPopover />
</Th> </TabTitleText>
<Th width={25}>Package repository</Th> }
<Th width={20}>Support</Th> aria-label="Included repositories"
</Tr> />
</Thead> <Tab
<Tbody>{bodyContent}</Tbody> eventKey="other-repos"
</Table> title={
<TabTitleText>
Other repos <OtherReposPopover />
</TabTitleText>
}
aria-label="Other repositories"
/>
</Tabs>
<PackagesTable />
<Pagination <Pagination
data-testid="packages-pagination-bottom" data-testid="packages-pagination-bottom"
itemCount={ itemCount={

View file

@ -0,0 +1,56 @@
import React from 'react';
import { Button, Popover, Text, TextContent } from '@patternfly/react-core';
import { HelpIcon } from '@patternfly/react-icons';
export const IncludedReposPopover = () => {
return (
<Popover
bodyContent={
<TextContent>
<Text>
View packages from the Red Hat repository and repositories
you&apos;ve selected.
</Text>
</TextContent>
}
>
<Button
variant="plain"
aria-label="About included repositories"
component="span"
className="pf-v5-u-p-0"
size="sm"
isInline
>
<HelpIcon />
</Button>
</Popover>
);
};
export const OtherReposPopover = () => {
return (
<Popover
bodyContent={
<TextContent>
<Text>
View packages from popular repositories and your other repositories
not included in the image.
</Text>
</TextContent>
}
>
<Button
variant="plain"
aria-label="About other repositories"
component="span"
className="pf-v5-u-p-0"
size="sm"
isInline
>
<HelpIcon />
</Button>
</Popover>
);
};