From c3e720a47e0218c1373a9a3d5defd8b0655da208 Mon Sep 17 00:00:00 2001 From: Dawid Tarasiuk Date: Mon, 14 Jun 2021 15:31:41 +0200 Subject: [PATCH] Saleor 3087 Paginate attribute values in filters (#1152) * Dynamic fetch attribute values in filter list * Update filter attributes fixtures * Change attribute values filter to autocomplete field * Fix unchecking attribute value filter failure * Update test snapshots * Update changelog * Fix cypress tests * Add slug node mapping util --- CHANGELOG.md | 1 + .../catalog/products/products-list.js | 9 +- schema.graphql | 6 +- src/components/Filter/Filter.tsx | 10 +- .../Filter/FilterContent/FilterContent.tsx | 120 +- .../FilterContent/FilterContentBody.tsx | 4 - .../FilterContentBodyNameField.tsx | 1 + src/components/Filter/types.ts | 1 + src/components/FilterBar/FilterBar.tsx | 2 + .../ProductListPage/ProductListPage.tsx | 2 + .../components/ProductListPage/filters.ts | 17 +- src/products/queries.ts | 33 +- .../types/InitialProductFilterAttributes.ts | 35 - .../views/ProductList/ProductList.tsx | 20 +- src/products/views/ProductList/filters.ts | 38 +- src/products/views/ProductList/fixtures.ts | 14 +- .../__snapshots__/Stories.test.ts.snap | 2748 ++++++++++++----- src/types.ts | 4 + src/utils/filters/fields.ts | 6 +- src/utils/maps.ts | 15 +- 20 files changed, 2145 insertions(+), 941 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 948968059..18c3e8fe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ All notable, unreleased changes to this project will be documented in this file. - Choosing user shipping and billing addresses for draft order - #1082 by @orzechdev - Fix EditorJS inline formatting - #1096 by @orzechdev - Add pagination on attribute values - #1112 by @orzechdev +- Paginate attribute values in filters - #1152 by @orzechdev - Fix attribute values input display - #1156 by @orzechdev # 2.11.1 diff --git a/cypress/elements/catalog/products/products-list.js b/cypress/elements/catalog/products/products-list.js index 254a1ca87..282f54403 100644 --- a/cypress/elements/catalog/products/products-list.js +++ b/cypress/elements/catalog/products/products-list.js @@ -21,10 +21,11 @@ export const PRODUCTS_LIST = { filterOption: '[data-test-id="filterOption"]', productsOutOfStockOption: '[data-test-id="OUT_OF_STOCK"]', filterBy: { - category: '[data-test-id="categories"]', - collection: '[data-test-id="collections"]', - productType: '[data-test-id="productType"]', - stock: '[data-test-id="stock"]' + category: '[data-test="filterGroupActive"][data-test-id="categories"]', + collection: '[data-test="filterGroupActive"][data-test-id="collections"]', + productType: + '[data-test="filterGroupActive"][data-test-id="productType"]', + stock: '[data-test="filterGroupActive"][data-test-id="stock"]' }, filterBySearchInput: '[data-test*="filterField"][data-test*="Input"]' }, diff --git a/schema.graphql b/schema.graphql index f46cf4b54..c049f530e 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1666,6 +1666,7 @@ input CustomerFilterInput { numberOfOrders: IntRangeInput placedOrders: DateRangeInput search: String + metadata: [MetadataInput] } input CustomerInput { @@ -6132,10 +6133,11 @@ enum WebhookSampleEventTypeEnum { PAGE_DELETED PAYMENT_AUTHORIZE PAYMENT_CAPTURE + PAYMENT_CONFIRM + PAYMENT_LIST_GATEWAYS + PAYMENT_PROCESS PAYMENT_REFUND PAYMENT_VOID - PAYMENT_CONFIRM - PAYMENT_PROCESS } type WebhookUpdate { diff --git a/src/components/Filter/Filter.tsx b/src/components/Filter/Filter.tsx index 288738fe1..7e801026e 100644 --- a/src/components/Filter/Filter.tsx +++ b/src/components/Filter/Filter.tsx @@ -21,6 +21,7 @@ export interface FilterProps { errorMessages?: FilterErrorMessages; menu: IFilter; onFilterAdd: (filter: Array>) => void; + onFilterAttributeFocus?: (id?: string) => void; } const useStyles = makeStyles( @@ -91,7 +92,13 @@ const useStyles = makeStyles( { name: "Filter" } ); const Filter: React.FC = props => { - const { currencySymbol, menu, onFilterAdd, errorMessages } = props; + const { + currencySymbol, + menu, + onFilterAdd, + onFilterAttributeFocus, + errorMessages + } = props; const classes = useStyles(props); const anchor = React.useRef(); @@ -190,6 +197,7 @@ const Filter: React.FC = props => { filters={data} onClear={reset} onFilterPropertyChange={dispatch} + onFilterAttributeFocus={onFilterAttributeFocus} onSubmit={handleSubmit} /> diff --git a/src/components/Filter/FilterContent/FilterContent.tsx b/src/components/Filter/FilterContent/FilterContent.tsx index 7a467a7fa..54dbeac5a 100644 --- a/src/components/Filter/FilterContent/FilterContent.tsx +++ b/src/components/Filter/FilterContent/FilterContent.tsx @@ -1,8 +1,15 @@ -import { Paper, Typography } from "@material-ui/core"; +import { + ExpansionPanel, + ExpansionPanelSummary, + makeStyles, + Paper, + Typography +} from "@material-ui/core"; import CollectionWithDividers from "@saleor/components/CollectionWithDividers"; import Hr from "@saleor/components/Hr"; import useStateFromProps from "@saleor/hooks/useStateFromProps"; -import React from "react"; +import IconChevronDown from "@saleor/icons/ChevronDown"; +import React, { useState } from "react"; import { FilterAutocompleteDisplayValues } from "../FilterAutocompleteField"; import { FilterReducerAction } from "../reducer"; @@ -18,9 +25,57 @@ import FilterContentBodyNameField from "./FilterContentBodyNameField"; import FilterContentHeader from "./FilterContentHeader"; import FilterErrorsList from "./FilterErrorsList"; +const useExpanderStyles = makeStyles( + () => ({ + expanded: {}, + root: { + boxShadow: "none", + margin: 0, + padding: 0, + + "&:before": { + content: "none" + }, + + "&$expanded": { + margin: 0, + border: "none" + } + } + }), + { name: "FilterContentExpander" } +); + +const useSummaryStyles = makeStyles( + theme => ({ + expanded: {}, + root: { + width: "100%", + border: "none", + margin: 0, + padding: 0, + minHeight: 0, + paddingRight: theme.spacing(2), + + "&$expanded": { + minHeight: 0 + } + }, + content: { + margin: 0, + + "&$expanded": { + margin: 0 + } + } + }), + { name: "FilterContentExpanderSummary" } +); + export interface FilterContentProps { filters: IFilter; onFilterPropertyChange: React.Dispatch>; + onFilterAttributeFocus?: (id?: string) => void; onClear: () => void; onSubmit: () => void; currencySymbol?: string; @@ -36,9 +91,15 @@ const FilterContent: React.FC = ({ filters, onClear, onFilterPropertyChange, + onFilterAttributeFocus, onSubmit, dataStructure }) => { + const expanderClasses = useExpanderStyles({}); + const summaryClasses = useSummaryStyles({}); + + const [openedFilter, setOpenedFilter] = useState>(); + const getAutocompleteValuesWithNewValues = ( autocompleteDisplayValues: FilterAutocompleteDisplayValues, filterField: IFilterElement @@ -84,6 +145,36 @@ const FilterContent: React.FC = ({ initialAutocompleteDisplayValues }; + const handleFilterAttributeFocus = (filter?: IFilterElement) => { + setOpenedFilter(filter); + if (onFilterAttributeFocus) { + onFilterAttributeFocus(filter?.id); + } + }; + + const handleFilterOpen = (filter: IFilterElement) => { + if (filter.name !== openedFilter?.name) { + handleFilterAttributeFocus(filter); + } else { + handleFilterAttributeFocus(undefined); + } + }; + + const handleFilterPropertyGroupChange = function( + action: FilterReducerAction, + filter: IFilterElement + ) { + const switchToActive = action.payload.update.active; + + if (switchToActive && filter.name !== openedFilter?.name) { + handleFilterAttributeFocus(filter); + } else if (!switchToActive && filter.name === openedFilter?.name) { + handleFilterAttributeFocus(undefined); + } + + onFilterPropertyChange(action); + }; + const handleMultipleFieldPropertyChange = function( action: FilterReducerAction ) { @@ -114,11 +205,24 @@ const FilterContent: React.FC = ({ {dataStructure .sort((a, b) => (a.name > b.name ? 1 : -1)) .map(filter => ( - - + + } + classes={summaryClasses} + onClick={() => handleFilterOpen(filter)} + > + + handleFilterPropertyGroupChange(action, filter) + } + /> + = ({ filter={getFilterFromCurrentData(filter)} /> )} - + ))} diff --git a/src/components/Filter/FilterContent/FilterContentBody.tsx b/src/components/Filter/FilterContent/FilterContentBody.tsx index 3372d29a9..7e0bebde8 100644 --- a/src/components/Filter/FilterContent/FilterContentBody.tsx +++ b/src/components/Filter/FilterContent/FilterContentBody.tsx @@ -74,10 +74,6 @@ const FilterContentBody: React.FC = ({ const intl = useIntl(); const classes = useStyles({}); - if (!filter?.active) { - return null; - } - return (
{children} diff --git a/src/components/Filter/FilterContent/FilterContentBodyNameField.tsx b/src/components/Filter/FilterContent/FilterContentBodyNameField.tsx index 8f78d0887..47bef0950 100644 --- a/src/components/Filter/FilterContent/FilterContentBodyNameField.tsx +++ b/src/components/Filter/FilterContent/FilterContentBodyNameField.tsx @@ -43,6 +43,7 @@ const FilterContentBodyNameField: React.FC = ({ /> } label={filter.label} + onClick={event => event.stopPropagation()} onChange={() => onFilterPropertyChange({ payload: { diff --git a/src/components/Filter/types.ts b/src/components/Filter/types.ts index f157283c6..0337ea79d 100644 --- a/src/components/Filter/types.ts +++ b/src/components/Filter/types.ts @@ -32,6 +32,7 @@ export interface IFilterElement type?: FieldType; required?: boolean; multipleFields?: IFilterElement[]; + id?: string; } export interface FilterBaseFieldProps { diff --git a/src/components/FilterBar/FilterBar.tsx b/src/components/FilterBar/FilterBar.tsx index 16d7c250b..f25e75cc2 100644 --- a/src/components/FilterBar/FilterBar.tsx +++ b/src/components/FilterBar/FilterBar.tsx @@ -48,6 +48,7 @@ const FilterBar: React.FC = props => { onAll, onSearchChange, onFilterChange, + onFilterAttributeFocus, onTabChange, onTabDelete, onTabSave, @@ -90,6 +91,7 @@ const FilterBar: React.FC = props => { menu={filterStructure} currencySymbol={currencySymbol} onFilterAdd={onFilterChange} + onFilterAttributeFocus={onFilterAttributeFocus} /> = props => { onExport, onFetchMore, onFilterChange, + onFilterAttributeFocus, onSearchChange, onTabChange, onTabDelete, @@ -196,6 +197,7 @@ export const ProductListPage: React.FC = props => { initialSearch={initialSearch} onAll={onAll} onFilterChange={onFilterChange} + onFilterAttributeFocus={onFilterAttributeFocus} onSearchChange={onSearchChange} onTabChange={onTabChange} onTabDelete={onTabDelete} diff --git a/src/products/components/ProductListPage/filters.ts b/src/products/components/ProductListPage/filters.ts index fb12b6695..8a30d5a75 100644 --- a/src/products/components/ProductListPage/filters.ts +++ b/src/products/components/ProductListPage/filters.ts @@ -1,5 +1,4 @@ import { IFilter } from "@saleor/components/Filter"; -import { MultiAutocompleteChoiceType } from "@saleor/components/MultiAutocompleteSelectField"; import { sectionNames } from "@saleor/intl"; import { AutocompleteFilterOpts, FilterOpts, MinMax } from "@saleor/types"; import { StockAvailability } from "@saleor/types/globalTypes"; @@ -22,11 +21,12 @@ export enum ProductFilterKeys { export interface ProductListFilterOpts { attributes: Array< FilterOpts & { - choices: MultiAutocompleteChoiceType[]; + id: string; name: string; slug: string; } >; + attributeChoices: FilterOpts & AutocompleteFilterOpts; categories: FilterOpts & AutocompleteFilterOpts; collections: FilterOpts & AutocompleteFilterOpts; price: FilterOpts; @@ -151,12 +151,21 @@ export function createFilterStructure( active: opts.productType.active }, ...opts.attributes.map(attr => ({ - ...createOptionsField( + ...createAutocompleteField( attr.slug as any, attr.name, attr.value, + opts.attributeChoices.displayValues, true, - attr.choices + opts.attributeChoices.choices, + { + hasMore: opts.attributeChoices.hasMore, + initialSearch: "", + loading: opts.attributeChoices.loading, + onFetchMore: opts.attributeChoices.onFetchMore, + onSearchChange: opts.attributeChoices.onSearchChange + }, + attr.id ), active: attr.active, group: ProductFilterKeys.attributes diff --git a/src/products/queries.ts b/src/products/queries.ts index 906d0a9f2..c38b92c8e 100644 --- a/src/products/queries.ts +++ b/src/products/queries.ts @@ -27,10 +27,7 @@ import { GridAttributes, GridAttributesVariables } from "./types/GridAttributes"; -import { - InitialProductFilterAttributes, - InitialProductFilterAttributesVariables -} from "./types/InitialProductFilterAttributes"; +import { InitialProductFilterAttributes } from "./types/InitialProductFilterAttributes"; import { InitialProductFilterCategories, InitialProductFilterCategoriesVariables @@ -60,13 +57,7 @@ import { } from "./types/ProductVariantDetails"; const initialProductFilterAttributesQuery = gql` - ${pageInfoFragment} - query InitialProductFilterAttributes( - $firstValues: Int - $afterValues: String - $lastValues: Int - $beforeValues: String - ) { + query InitialProductFilterAttributes { attributes( first: 100 filter: { filterableInDashboard: true, type: PRODUCT_TYPE } @@ -76,24 +67,6 @@ const initialProductFilterAttributesQuery = gql` id name slug - choices( - first: $firstValues - after: $afterValues - last: $lastValues - before: $beforeValues - ) { - pageInfo { - ...PageInfoFragment - } - edges { - cursor - node { - id - name - slug - } - } - } } } } @@ -101,7 +74,7 @@ const initialProductFilterAttributesQuery = gql` `; export const useInitialProductFilterAttributesQuery = makeQuery< InitialProductFilterAttributes, - InitialProductFilterAttributesVariables + never >(initialProductFilterAttributesQuery); const initialProductFilterCategoriesQuery = gql` diff --git a/src/products/types/InitialProductFilterAttributes.ts b/src/products/types/InitialProductFilterAttributes.ts index ab792e3db..f99b942ae 100644 --- a/src/products/types/InitialProductFilterAttributes.ts +++ b/src/products/types/InitialProductFilterAttributes.ts @@ -7,39 +7,11 @@ // GraphQL query operation: InitialProductFilterAttributes // ==================================================== -export interface InitialProductFilterAttributes_attributes_edges_node_choices_pageInfo { - __typename: "PageInfo"; - endCursor: string | null; - hasNextPage: boolean; - hasPreviousPage: boolean; - startCursor: string | null; -} - -export interface InitialProductFilterAttributes_attributes_edges_node_choices_edges_node { - __typename: "AttributeValue"; - id: string; - name: string | null; - slug: string | null; -} - -export interface InitialProductFilterAttributes_attributes_edges_node_choices_edges { - __typename: "AttributeValueCountableEdge"; - cursor: string; - node: InitialProductFilterAttributes_attributes_edges_node_choices_edges_node; -} - -export interface InitialProductFilterAttributes_attributes_edges_node_choices { - __typename: "AttributeValueCountableConnection"; - pageInfo: InitialProductFilterAttributes_attributes_edges_node_choices_pageInfo; - edges: InitialProductFilterAttributes_attributes_edges_node_choices_edges[]; -} - export interface InitialProductFilterAttributes_attributes_edges_node { __typename: "Attribute"; id: string; name: string | null; slug: string | null; - choices: InitialProductFilterAttributes_attributes_edges_node_choices | null; } export interface InitialProductFilterAttributes_attributes_edges { @@ -55,10 +27,3 @@ export interface InitialProductFilterAttributes_attributes { export interface InitialProductFilterAttributes { attributes: InitialProductFilterAttributes_attributes | null; } - -export interface InitialProductFilterAttributesVariables { - firstValues?: number | null; - afterValues?: string | null; - lastValues?: number | null; - beforeValues?: string | null; -} diff --git a/src/products/views/ProductList/ProductList.tsx b/src/products/views/ProductList/ProductList.tsx index 6c5a378af..22d797043 100644 --- a/src/products/views/ProductList/ProductList.tsx +++ b/src/products/views/ProductList/ProductList.tsx @@ -48,6 +48,7 @@ import { productUrl } from "@saleor/products/urls"; import useAttributeSearch from "@saleor/searches/useAttributeSearch"; +import useAttributeValueSearch from "@saleor/searches/useAttributeValueSearch"; import useCategorySearch from "@saleor/searches/useCategorySearch"; import useCollectionSearch from "@saleor/searches/useCollectionSearch"; import useProductTypeSearch from "@saleor/searches/useProductTypeSearch"; @@ -57,7 +58,7 @@ import createFilterHandlers from "@saleor/utils/handlers/filterHandlers"; import { mapEdgesToItems } from "@saleor/utils/maps"; import { getSortUrlVariables } from "@saleor/utils/sort"; import { useWarehouseList } from "@saleor/warehouses/queries"; -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { FormattedMessage, useIntl } from "react-intl"; import ProductListPage from "../../components/ProductListPage"; @@ -95,11 +96,7 @@ export const ProductList: React.FC = ({ params }) => { const intl = useIntl(); const { data: initialFilterAttributes - } = useInitialProductFilterAttributesQuery({ - variables: { - firstValues: 100 - } - }); + } = useInitialProductFilterAttributesQuery({}); const { data: initialFilterCategories } = useInitialProductFilterCategoriesQuery({ @@ -148,6 +145,15 @@ export const ProductList: React.FC = ({ params }) => { first: 10 } }); + const [focusedAttribute, setFocusedAttribute] = useState(); + const searchAttributeValues = useAttributeValueSearch({ + variables: { + id: focusedAttribute, + ...DEFAULT_INITIAL_SEARCH_DATA, + first: 10 + }, + skip: !focusedAttribute + }); const warehouses = useWarehouseList({ variables: { first: 100 @@ -321,6 +327,7 @@ export const ProductList: React.FC = ({ params }) => { const filterOpts = getFilterOpts( params, mapEdgesToItems(initialFilterAttributes?.attributes), + searchAttributeValues, { initial: mapEdgesToItems(initialFilterCategories?.categories), search: searchCategories @@ -422,6 +429,7 @@ export const ProductList: React.FC = ({ params }) => { toggleAll={toggleAll} onSearchChange={handleSearchChange} onFilterChange={changeFilters} + onFilterAttributeFocus={setFocusedAttribute} onTabSave={() => openModal("save-search")} onTabDelete={() => openModal("delete-search")} onTabChange={handleTabChange} diff --git a/src/products/views/ProductList/filters.ts b/src/products/views/ProductList/filters.ts index 9ecb52641..9b0777769 100644 --- a/src/products/views/ProductList/filters.ts +++ b/src/products/views/ProductList/filters.ts @@ -8,6 +8,10 @@ import { InitialProductFilterAttributes_attributes_edges_node } from "@saleor/pr import { InitialProductFilterCategories_categories_edges_node } from "@saleor/products/types/InitialProductFilterCategories"; import { InitialProductFilterCollections_collections_edges_node } from "@saleor/products/types/InitialProductFilterCollections"; import { InitialProductFilterProductTypes_productTypes_edges_node } from "@saleor/products/types/InitialProductFilterProductTypes"; +import { + SearchAttributeValues, + SearchAttributeValuesVariables +} from "@saleor/searches/types/SearchAttributeValues"; import { SearchCategories, SearchCategoriesVariables @@ -20,7 +24,11 @@ import { SearchProductTypes, SearchProductTypesVariables } from "@saleor/searches/types/SearchProductTypes"; -import { mapEdgesToItems, mapNodeToChoice } from "@saleor/utils/maps"; +import { + mapEdgesToItems, + mapNodeToChoice, + mapSlugNodeToChoice +} from "@saleor/utils/maps"; import isArray from "lodash/isArray"; import { IFilterElement } from "../../../components/Filter"; @@ -50,6 +58,10 @@ export const PRODUCT_FILTERS_KEY = "productFilters"; export function getFilterOpts( params: ProductListUrlFilters, attributes: InitialProductFilterAttributes_attributes_edges_node[], + focusedAttributeChoices: UseSearchResult< + SearchAttributeValues, + SearchAttributeValuesVariables + >, categories: { initial: InitialProductFilterCategories_categories_edges_node[]; search: UseSearchResult; @@ -68,17 +80,31 @@ export function getFilterOpts( .sort((a, b) => (a.name > b.name ? 1 : -1)) .map(attr => ({ active: maybe(() => params.attributes[attr.slug].length > 0, false), - choices: attr.choices?.edges?.map(val => ({ - label: val.node.name, - value: val.node.slug - })), + id: attr.id, name: attr.name, slug: attr.slug, value: !!params.attributes && params.attributes[attr.slug] - ? params.attributes[attr.slug] + ? dedupeFilter(params.attributes[attr.slug]) : [] })), + attributeChoices: { + active: true, + choices: mapSlugNodeToChoice( + mapEdgesToItems(focusedAttributeChoices.result.data?.attribute?.choices) + ), + displayValues: mapNodeToChoice( + mapEdgesToItems(focusedAttributeChoices.result.data?.attribute?.choices) + ), + hasMore: + focusedAttributeChoices.result.data?.attribute?.choices?.pageInfo + ?.hasNextPage || false, + initialSearch: "", + loading: focusedAttributeChoices.result.loading, + onFetchMore: focusedAttributeChoices.loadMore, + onSearchChange: focusedAttributeChoices.search, + value: null + }, categories: { active: !!params.categories, choices: mapNodeToChoice( diff --git a/src/products/views/ProductList/fixtures.ts b/src/products/views/ProductList/fixtures.ts index 6dedc2d86..3d8fd6d0f 100644 --- a/src/products/views/ProductList/fixtures.ts +++ b/src/products/views/ProductList/fixtures.ts @@ -5,14 +5,12 @@ import { fetchMoreProps, searchPageProps } from "@saleor/fixtures"; import { ProductListFilterOpts } from "@saleor/products/components/ProductListPage"; import { productTypes } from "@saleor/productTypes/fixtures"; import { StockAvailability } from "@saleor/types/globalTypes"; +import { mapEdgesToItems, mapSlugNodeToChoice } from "@saleor/utils/maps"; export const productListFilterOpts: ProductListFilterOpts = { attributes: attributes.map(attr => ({ + id: attr.id, active: false, - choices: attr.choices.edges.map(val => ({ - label: val.node.name, - value: val.node.slug - })), name: attr.name, slug: attr.slug, value: [ @@ -20,6 +18,14 @@ export const productListFilterOpts: ProductListFilterOpts = { attr.choices.edges.length > 2 && attr.choices.edges[2].node.slug ] })), + attributeChoices: { + ...fetchMoreProps, + ...searchPageProps, + active: false, + value: null, + choices: mapSlugNodeToChoice(mapEdgesToItems(attributes[0].choices)), + displayValues: mapSlugNodeToChoice(mapEdgesToItems(attributes[0].choices)) + }, categories: { ...fetchMoreProps, ...searchPageProps, diff --git a/src/storybook/__snapshots__/Stories.test.ts.snap b/src/storybook/__snapshots__/Stories.test.ts.snap index 86cd9bc57..819d7be67 100644 --- a/src/storybook/__snapshots__/Stories.test.ts.snap +++ b/src/storybook/__snapshots__/Stories.test.ts.snap @@ -5585,507 +5585,1047 @@ exports[`Storyshots Generics / Filter default 1`] = ` class="Hr-root-id" />
-
+ -
-
-
-
-
- - - -
- - -
-
- - and - -
-
- - +
+
+
+
+
+ + + +
+
+
+ + +
+
+ + and + +
+
+ + +
+
+
+
+
- -
-
- -
-
- -
-
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+
+
- +
+
+
+ + + +
+
+
+ + +
+
+ + and + +
+
+ + +
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
@@ -6145,507 +6685,1047 @@ exports[`Storyshots Generics / Filter interactive 1`] = ` class="Hr-root-id" />
-
+ -
-
-
-
-
- - - -
- - -
-
- - and - -
-
- - +
+
+
+
+
+ + + +
+
+
+ + +
+
+ + and + +
+
+ + +
+
+
+
+
- -
-
- -
-
- -
-
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+
+ +
+ +
+
+
+
+
- +
+
+
+ + + +
+
+
+ + +
+
+ + and + +
+
+ + +
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+
+
+
diff --git a/src/types.ts b/src/types.ts index 22905ccbe..6aed7ce63 100644 --- a/src/types.ts +++ b/src/types.ts @@ -107,6 +107,7 @@ export interface FilterPageProps export interface FilterProps { currencySymbol?: string; onFilterChange: (filter: IFilter) => void; + onFilterAttributeFocus?: (id?: string) => void; } export interface TabPageProps { @@ -133,6 +134,9 @@ export interface PartialMutationProviderOutput< export interface Node { id: string; } +export interface SlugNode { + slug: string; +} export type Pagination = Partial<{ after: string; diff --git a/src/utils/filters/fields.ts b/src/utils/filters/fields.ts index 35959ff44..bb988d9ec 100644 --- a/src/utils/filters/fields.ts +++ b/src/utils/filters/fields.ts @@ -72,7 +72,8 @@ export function createAutocompleteField( displayValues: MultiAutocompleteChoiceType[], multiple: boolean, options: MultiAutocompleteChoiceType[], - opts: FetchMoreProps & SearchPageProps + opts: FetchMoreProps & SearchPageProps, + id?: string ): IFilterElement { return { ...opts, @@ -83,7 +84,8 @@ export function createAutocompleteField( name, options, type: FieldType.autocomplete, - value: defaultValue + value: defaultValue, + id }; } diff --git a/src/utils/maps.ts b/src/utils/maps.ts index 548051ad9..8b93e2ee6 100644 --- a/src/utils/maps.ts +++ b/src/utils/maps.ts @@ -3,7 +3,7 @@ import { ShopInfo_shop_countries } from "@saleor/components/Shop/types/ShopInfo" import { SingleAutocompleteChoiceType } from "@saleor/components/SingleAutocompleteSelectField"; import { MetadataItem } from "@saleor/fragments/types/MetadataItem"; import { SearchPages_search_edges_node } from "@saleor/searches/types/SearchPages"; -import { Node } from "@saleor/types"; +import { Node, SlugNode } from "@saleor/types"; import { MetadataInput } from "@saleor/types/globalTypes"; interface EdgesType { @@ -49,6 +49,19 @@ export function mapNodeToChoice( })); } +export function mapSlugNodeToChoice( + nodes: Array> +): Array { + if (!nodes) { + return []; + } + + return nodes.map(node => ({ + label: node.name, + value: node.slug + })); +} + export function mapMetadataItemToInput(item: MetadataItem): MetadataInput { return { key: item.key,