Skip to content

Commit

Permalink
feat(KaotoIOgh-115): Tag filtering in catalog
Browse files Browse the repository at this point in the history
  • Loading branch information
mkralik3 committed Sep 12, 2023
1 parent be9bc7f commit a127c5f
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 54 deletions.
9 changes: 7 additions & 2 deletions packages/ui/src/components/Catalog/BaseCatalog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface BaseCatalogProps {
tiles: ITile[];
catalogLayout: CatalogLayout;
onTileClick?: (tile: ITile) => void;
onTagClick: (_event: unknown, value: string) => void;
}

export const BaseCatalog: FunctionComponent<BaseCatalogProps> = (props) => {
Expand All @@ -34,12 +35,16 @@ export const BaseCatalog: FunctionComponent<BaseCatalogProps> = (props) => {
</Title>
{props.catalogLayout == CatalogLayout.List && (
<DataList aria-label="Catalog list" onSelectDataListItem={onSelectDataListItem} isCompact>
{props.tiles?.map((tile) => <CatalogDataListItem key={tile.name} tile={tile} />)}
{props.tiles?.map((tile) => (
<CatalogDataListItem key={tile.name} tile={tile} onTagClick={props.onTagClick} />
))}
</DataList>
)}
{props.catalogLayout == CatalogLayout.Gallery && (
<Gallery hasGutter>
{props.tiles?.map((tile) => <Tile key={tile.name} tile={tile} onClick={onTileClick} />)}
{props.tiles?.map((tile) => (
<Tile key={tile.name} tile={tile} onClick={onTileClick} onTagClick={props.onTagClick} />
))}
</Gallery>
)}
</div>
Expand Down
43 changes: 32 additions & 11 deletions packages/ui/src/components/Catalog/Catalog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,42 @@ interface CatalogProps {
onTileClick?: (tile: ITile) => void;
}

const checkThatArrayContainsAllTags = (arr: string[], tags: string[]) => tags.every((v) => arr.includes(v));

export const Catalog: FunctionComponent<PropsWithChildren<CatalogProps>> = (props) => {
const [searchTerm, setSearchTerm] = useState('');
const [groups, setGroups] = useState<string[]>([]);
const [activeGroup, setActiveGroup] = useState<string>(getFirstActiveGroup(props.tiles));
const [activeLayout, setActiveLayout] = useState(CatalogLayout.Gallery);
const [filteredTiles, setFilteredTiles] = useState<ITile[]>([]);
const [filterTags, setFilterTags] = useState<string[]>([]);

useEffect(() => {
setGroups(Object.keys(props.tiles));
setActiveGroup(getFirstActiveGroup(props.tiles));
}, [props.tiles]);

useEffect(() => {
setFilteredTiles(
props.tiles[activeGroup]?.filter((tile) => {
return (
tile.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
tile.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
tile.description?.toLowerCase().includes(searchTerm.toLowerCase()) ||
tile.tags.some((tag) => tag.toLowerCase().includes(searchTerm.toLowerCase()))
);
}),
);
}, [searchTerm, activeGroup, props.tiles]);
let toBeFiltered: ITile[] = [];
toBeFiltered = filterTags.length
? props.tiles[activeGroup]?.filter((tile) => {
return checkThatArrayContainsAllTags(tile.tags, filterTags);
})
: props.tiles[activeGroup];

toBeFiltered =
searchTerm.length <= 0
? toBeFiltered
: toBeFiltered?.filter((tile) => {
return (
tile.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
tile.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
tile.description?.toLowerCase().includes(searchTerm.toLowerCase()) ||
tile.tags.some((tag) => tag.toLowerCase().includes(searchTerm.toLowerCase()))
);
});
setFilteredTiles(toBeFiltered);
}, [searchTerm, activeGroup, props.tiles, filterTags]);

const onFilterChange = useCallback((_event: unknown, value = '') => {
setSearchTerm(value);
Expand All @@ -45,6 +57,12 @@ export const Catalog: FunctionComponent<PropsWithChildren<CatalogProps>> = (prop
[props],
);

const onTagClick = useCallback((_event: unknown, value = '') => {
setFilterTags((previousFilteredTags) => {
return previousFilteredTags.includes(value) ? previousFilteredTags : [...previousFilteredTags, value];
});
}, []);

return (
<>
<CatalogFilter
Expand All @@ -54,15 +72,18 @@ export const Catalog: FunctionComponent<PropsWithChildren<CatalogProps>> = (prop
layouts={[CatalogLayout.Gallery, CatalogLayout.List]}
activeGroup={activeGroup}
activeLayout={activeLayout}
filterTags={filterTags}
onChange={onFilterChange}
setActiveGroup={setActiveGroup}
setActiveLayout={setActiveLayout}
setFilterTags={setFilterTags}
/>
<BaseCatalog
className="catalog__base"
tiles={filteredTiles}
catalogLayout={activeLayout}
onTileClick={onTileClick}
onTagClick={onTagClick}
/>
</>
);
Expand Down
25 changes: 24 additions & 1 deletion packages/ui/src/components/Catalog/CatalogFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { Form, FormGroup, Grid, GridItem, SearchInput, ToggleGroup, ToggleGroupItem } from '@patternfly/react-core';
import {
Form,
FormGroup,
Grid,
GridItem,
Label,
LabelGroup,
SearchInput,
ToggleGroup,
ToggleGroupItem,
} from '@patternfly/react-core';
import { FunctionComponent, useEffect, useRef } from 'react';
import { CatalogLayout } from './Catalog.models';
import { CatalogLayoutIcon } from './CatalogLayoutIcon';
Expand All @@ -10,9 +20,11 @@ interface CatalogFilterProps {
layouts: CatalogLayout[];
activeGroup: string;
activeLayout: CatalogLayout;
filterTags: string[];
onChange: (event: unknown, value?: string) => void;
setActiveGroup: (group: string) => void;
setActiveLayout: (layout: CatalogLayout) => void;
setFilterTags: (tags: string[]) => void;
}

export const CatalogFilter: FunctionComponent<CatalogFilterProps> = (props) => {
Expand All @@ -22,6 +34,10 @@ export const CatalogFilter: FunctionComponent<CatalogFilterProps> = (props) => {
inputRef.current?.focus();
}, []);

const onClose = (tag: string) => {
props.setFilterTags(props.filterTags.filter((savedTag) => savedTag !== tag));
};

return (
<Form className={props.className}>
<Grid hasGutter>
Expand Down Expand Up @@ -76,6 +92,13 @@ export const CatalogFilter: FunctionComponent<CatalogFilterProps> = (props) => {
</FormGroup>
</GridItem>
</Grid>
<LabelGroup categoryName="Filtered tags" numLabels={10}>
{props.filterTags.map((tag, index) => (
<Label key={tag + index} id={tag + index} color="blue" onClose={() => onClose(tag)} isCompact>
{tag}
</Label>
))}
</LabelGroup>
</Form>
);
};
1 change: 1 addition & 0 deletions packages/ui/src/components/Catalog/DataListItem.scss
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

&__tags {
margin-right: 5px;
cursor: pointer;
}

@media (min-width: 768px) {
Expand Down
43 changes: 15 additions & 28 deletions packages/ui/src/components/Catalog/DataListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import {
DataListCell,
DataListItem,
DataListItemCells,
DataListItemRow,
Grid,
GridItem,
Label,
} from '@patternfly/react-core';
import { DataListCell, DataListItem, DataListItemCells, DataListItemRow, Grid, GridItem } from '@patternfly/react-core';
import { FunctionComponent } from 'react';
import { IconResolver } from '../IconResolver';
import { ITile } from './Catalog.models';
import './DataListItem.scss';
import { getTagColor } from './tag-color-resolver';
import { CatalogCickableTag } from './Tags/CatalogClickableTag';
import { CatalogTag } from './Tags/CatalogTag';

interface ICatalogDataListItemProps {
tile: ITile;
onTagClick: (_event: unknown, value: string) => void;
}

const titleElementOrder = {
Expand Down Expand Up @@ -51,38 +45,31 @@ export const CatalogDataListItem: FunctionComponent<ICatalogDataListItemProps> =
{props.tile.title}
</span>
{props.tile.headerTags?.map((tag, index) => (
<Label
<CatalogTag
key={`${props.tile.name}-${tag}-${index}`}
tag={tag}
className="catalog-data-list-item__tags"
isCompact
color={getTagColor(tag)}
>
{tag}
</Label>
/>
))}
{props.tile.version && (
<Label
<CatalogTag
key={`${props.tile.version}`}
isCompact
className={'catalog-data-list-item__tags'}
tag={props.tile.version}
className="catalog-data-list-item__tags"
variant="outline"
>
{props.tile.version}
</Label>
/>
)}
</div>
</GridItem>
<GridItem sm={12} md={6} order={tagsElementOrder}>
<div className="catalog-data-list-item__title-div-right">
{props.tile.tags?.map((tag, index) => (
<Label
<CatalogCickableTag
key={`${props.tile.name}-${tag}-${index}`}
isCompact
tagName={tag}
className={'catalog-data-list-item__tags'}
color={getTagColor(tag)}
>
{tag}
</Label>
onTagClick={props.onTagClick}
/>
))}
</div>
</GridItem>
Expand Down
31 changes: 31 additions & 0 deletions packages/ui/src/components/Catalog/Tags/CatalogClickableTag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Label, Tooltip } from '@patternfly/react-core';
import { FunctionComponent } from 'react';
import { getTagColor } from './tag-color-resolver';

interface ICatalogCickableTagProps {
tagName: string;
className?: string;
onTagClick: (_event: unknown, value: string) => void;
}

export const CatalogCickableTag: FunctionComponent<ICatalogCickableTagProps> = (props) => {
return (
<Tooltip content={<span>Add to filter</span>}>
<Label
isCompact
className={props.className ?? ''}
color={getTagColor(props.tagName)}
onClick={(ev) => {
ev.stopPropagation(); // ignore root click, e.g. click on tile
props.onTagClick(ev, props.tagName);
}}
render={({ className, content }) => (
// to force PF to render label as button with animation
<a className={className}>{content}</a>
)}
>
{props.tagName}
</Label>
</Tooltip>
);
};
22 changes: 22 additions & 0 deletions packages/ui/src/components/Catalog/Tags/CatalogTag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Label } from '@patternfly/react-core';
import { FunctionComponent } from 'react';
import { getTagColor } from './tag-color-resolver';

interface ICatalogTagProps {
tag: string;
className?: string;
variant?: 'outline' | 'filled';
}

export const CatalogTag: FunctionComponent<ICatalogTagProps> = (props) => {
return (
<Label
isCompact
className={props.className ?? ''}
variant={props.variant ?? 'filled'}
color={getTagColor(props.tag)}
>
{props.tag}
</Label>
);
};
3 changes: 3 additions & 0 deletions packages/ui/src/components/Catalog/Tags/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './tag-color-resolver';
export * from './CatalogTag';
export * from './CatalogClickableTag';
1 change: 1 addition & 0 deletions packages/ui/src/components/Catalog/Tile.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@
&__tags {
margin-right: 1px;
margin-left: 1px;
cursor: pointer;
}
}
24 changes: 13 additions & 11 deletions packages/ui/src/components/Catalog/Tile.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { Card, CardBody, CardFooter, CardHeader, CardTitle, Label } from '@patternfly/react-core';
import { Card, CardBody, CardFooter, CardHeader, CardTitle } from '@patternfly/react-core';
import { FunctionComponent, PropsWithChildren, useCallback } from 'react';
import { IconResolver } from '../IconResolver';
import { ITile } from './Catalog.models';
import { CatalogCickableTag } from './Tags/CatalogClickableTag';
import { CatalogTag } from './Tags/CatalogTag';

import './Tile.scss';
import { getTagColor } from './tag-color-resolver';

interface TileProps {
tile: ITile;
onClick: (tile: ITile) => void;
onTagClick: (_event: unknown, value: string) => void;
}

export const Tile: FunctionComponent<PropsWithChildren<TileProps>> = (props) => {
Expand Down Expand Up @@ -36,9 +39,7 @@ export const Tile: FunctionComponent<PropsWithChildren<TileProps>> = (props) =>
<div className="tile__header">
<IconResolver className="tile__icon" tile={props.tile} />
{props.tile.headerTags?.map((tag, index) => (
<Label key={`${props.tile.name}-${tag}-${index}`} isCompact color={getTagColor(tag)}>
{tag}
</Label>
<CatalogTag key={`${props.tile.name}-${tag}-${index}`} tag={tag} />
))}
</div>

Expand All @@ -51,14 +52,15 @@ export const Tile: FunctionComponent<PropsWithChildren<TileProps>> = (props) =>

<CardFooter className="tile__footer">
{props.tile.tags?.map((tag, index) => (
<Label key={`${props.tile.name}-${tag}-${index}`} isCompact className={'tile__tags'} color={getTagColor(tag)}>
{tag}
</Label>
<CatalogCickableTag
key={`${props.tile.name}-${tag}-${index}`}
tagName={tag}
className={'tile__tags'}
onTagClick={props.onTagClick}
/>
))}
{props.tile.version && (
<Label key={`${props.tile.version}`} isCompact className={'tile__tags'} variant="outline">
{props.tile.version}
</Label>
<CatalogTag key={`${props.tile.version}`} tag={props.tile.version} className="tile__tags" variant="outline" />
)}
</CardFooter>
</Card>
Expand Down
1 change: 0 additions & 1 deletion packages/ui/src/components/Catalog/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@ export * from './CatalogLayoutIcon';
export * from './Catalog.models';
export * from './DataListItem';
export * from './Tile';
export * from './tag-color-resolver';

0 comments on commit a127c5f

Please sign in to comment.