From ec0ca8a9c20aedc5c411a9906a9b5352a023d06a Mon Sep 17 00:00:00 2001 From: Mirek Mencel Date: Thu, 29 Jan 2026 23:09:42 +0100 Subject: [PATCH 01/11] Ads changeset --- .changeset/itchy-frogs-grab.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/itchy-frogs-grab.md diff --git a/.changeset/itchy-frogs-grab.md b/.changeset/itchy-frogs-grab.md new file mode 100644 index 00000000000..e1bcbff85eb --- /dev/null +++ b/.changeset/itchy-frogs-grab.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": patch +--- + +Attribute configuration: allow users to filter attribute values by slug or name when there are more than 5 values. From 423634bd06be955e819c6fbaf1b957650be82875 Mon Sep 17 00:00:00 2001 From: Mirek Mencel Date: Thu, 29 Jan 2026 23:10:05 +0100 Subject: [PATCH 02/11] Add attribute values filtering --- .../AttributePage/AttributePage.tsx | 2 +- .../AttributeValues/AttributeValues.tsx | 313 +++++++++++++----- .../components/AttributeValues/index.ts | 3 +- 3 files changed, 224 insertions(+), 94 deletions(-) diff --git a/src/attributes/components/AttributePage/AttributePage.tsx b/src/attributes/components/AttributePage/AttributePage.tsx index eb44c5ea004..9e9f3d09c65 100644 --- a/src/attributes/components/AttributePage/AttributePage.tsx +++ b/src/attributes/components/AttributePage/AttributePage.tsx @@ -55,7 +55,7 @@ import AttributeDetails from "../AttributeDetails"; import AttributeOrganization from "../AttributeOrganization"; import AttributeProperties from "../AttributeProperties"; import { AttributeReferenceTypesSection } from "../AttributeReferenceTypesSection/AttributeReferenceTypesSection"; -import AttributeValues from "../AttributeValues"; +import { AttributeValues } from "../AttributeValues/AttributeValues"; interface AttributePageProps { attribute?: AttributeDetailsQuery["attribute"] | null | undefined; diff --git a/src/attributes/components/AttributeValues/AttributeValues.tsx b/src/attributes/components/AttributeValues/AttributeValues.tsx index 9ee129af5ce..10bc862c701 100644 --- a/src/attributes/components/AttributeValues/AttributeValues.tsx +++ b/src/attributes/components/AttributeValues/AttributeValues.tsx @@ -15,7 +15,8 @@ import { ListProps, PaginateListProps, RelayToFlat, ReorderAction } from "@dashb import { TableCell, TableHead } from "@material-ui/core"; import { makeStyles } from "@saleor/macaw-ui"; import { Box, Button, Skeleton } from "@saleor/macaw-ui-next"; -import { Trash2 } from "lucide-react"; +import { Search, Trash2, X } from "lucide-react"; +import { useMemo, useRef, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; interface AttributeValuesProps @@ -72,6 +73,94 @@ const getSwatchCellStyle = (value?: AttributeValueFragment | undefined) => { ? { backgroundImage: `url(${value.file.url})` } : { backgroundColor: value.value ?? undefined }; }; + +interface SearchInputProps { + value: string; + onChange: (value: string) => void; + placeholder: string; +} + +const SearchInput = ({ value, onChange, placeholder }: SearchInputProps) => { + const inputRef = useRef(null); + + return ( + + + + + onChange(e.target.value)} + onKeyDown={e => { + if (e.key === "Escape") { + onChange(""); + inputRef.current?.blur(); + } + }} + placeholder={placeholder} + data-test-id="attribute-value-search-input" + style={{ + flex: 1, + border: "none", + outline: "none", + backgroundColor: "transparent", + fontSize: "14px", + color: "var(--mu-colors-text-default1)", + minWidth: 0, + }} + /> + {value && ( + { + onChange(""); + inputRef.current?.focus(); + }} + data-test-id="attribute-value-search-clear" + > + + + )} + + ); +}; + +function filterAttributeValues( + values: RelayToFlat | undefined, + searchQuery: string, +): RelayToFlat | undefined { + if (!values || !searchQuery.trim()) { + return values; + } + + const query = searchQuery.toLowerCase().trim(); + + return values.filter( + value => value.slug?.toLowerCase().includes(query) || value.name?.toLowerCase().includes(query), + ); +} + const AttributeValues = ({ disabled, onValueAdd, @@ -89,6 +178,19 @@ const AttributeValues = ({ const classes = useStyles({}); const intl = useIntl(); const isSwatch = inputType === AttributeInputTypeEnum.SWATCH; + const [searchQuery, setSearchQuery] = useState(""); + + const filteredValues = useMemo( + () => filterAttributeValues(values, searchQuery), + [values, searchQuery], + ); + + const SEARCH_THRESHOLD = 5; + const showSearch = values && values.length > SEARCH_THRESHOLD; + + // Only apply filtering when search is visible + const displayedValues = showSearch ? filteredValues : values; + const hasDisplayedValues = displayedValues && displayedValues.length > 0; return ( @@ -127,99 +229,128 @@ const AttributeValues = ({ /> ) : ( - + {showSearch && ( + - } - > - - - - {isSwatch && ( - - - - )} - - + + + ) : ( + - - - - - - - - - {renderCollection(values, (value, valueIndex) => ( - - data-test-id="attributes-rows" - className={value ? classes.link : undefined} - hover={!!value} - onClick={value ? () => onValueUpdate(value.id) : undefined} - key={value?.id} - index={valueIndex || 0} - > - {isSwatch && ( - - {value?.file ? ( - - ) : ( -
+ + + + {isSwatch && ( + + - )} + + )} + + - )} - - {value?.slug ?? } - - - {value?.name ?? } - - -