From cf30ad6ca46d44b43feac387887af349aeceb0c3 Mon Sep 17 00:00:00 2001 From: Kevin Szuchet <31735779+kevinszuchet@users.noreply.github.com> Date: Mon, 24 Jul 2023 10:49:18 +0200 Subject: [PATCH] feat: Give more visibility to the smart filter (#1942) * feat: Give OnlySmart filter more visibility * chore: Add TODO * style: Fix styles reviewed in the PR * feat: Show different sections depending on the dispositive --- .../CurrentAccountSidebar.tsx | 1 + .../components/AssetFilters/AssetFilters.tsx | 8 ++ .../MoreFilters/MoreFilters.spec.tsx | 74 +++++++++++++++++++ .../AssetFilters/MoreFilters/MoreFilters.tsx | 38 ++++------ .../OnlySmartFilter.module.css | 9 +++ .../OnlySmartFilter/OnlySmartFilter.spec.tsx | 45 +++++++++++ .../OnlySmartFilter/OnlySmartFilter.tsx | 49 ++++++++++++ .../AssetFilters/OnlySmartFilter/index.ts | 1 + webapp/src/components/AssetFilters/utils.ts | 5 +- .../AssetPage/SmartBadge/SmartBadge.css | 2 +- .../AssetPage/SmartBadge/SmartBadge.tsx | 3 +- .../AssetPage/SmartBadge/SmartBadge.types.ts | 13 +++- .../SelectedFilters/SelectedFilters.tsx | 2 +- .../src/modules/translation/locales/en.json | 5 +- .../src/modules/translation/locales/es.json | 5 +- .../src/modules/translation/locales/zh.json | 5 +- 16 files changed, 230 insertions(+), 35 deletions(-) create mode 100644 webapp/src/components/AssetFilters/MoreFilters/MoreFilters.spec.tsx create mode 100644 webapp/src/components/AssetFilters/OnlySmartFilter/OnlySmartFilter.module.css create mode 100644 webapp/src/components/AssetFilters/OnlySmartFilter/OnlySmartFilter.spec.tsx create mode 100644 webapp/src/components/AssetFilters/OnlySmartFilter/OnlySmartFilter.tsx create mode 100644 webapp/src/components/AssetFilters/OnlySmartFilter/index.ts diff --git a/webapp/src/components/AccountSidebar/CurrentAccountSidebar/CurrentAccountSidebar.tsx b/webapp/src/components/AccountSidebar/CurrentAccountSidebar/CurrentAccountSidebar.tsx index 80cdf28af8..4c0f4cfadd 100644 --- a/webapp/src/components/AccountSidebar/CurrentAccountSidebar/CurrentAccountSidebar.tsx +++ b/webapp/src/components/AccountSidebar/CurrentAccountSidebar/CurrentAccountSidebar.tsx @@ -84,6 +84,7 @@ const CurrentAccountSidebar = ({ section, onBrowse }: Props) => { + {shouldRenderFilter(AssetFilter.OnlySmart) ? ( + + ) : null} {shouldRenderFilter(AssetFilter.Rarity) ? ( ({ + useTabletAndBelowMediaQuery: jest.fn() +})) + +function renderMoreFilters(props: Partial = {}) { + return renderWithProviders( + + ) +} + +describe('MoreFilters', () => { + let useTabletAndBelowMediaQueryMock: jest.MockedFunction + + beforeEach(() => { + useTabletAndBelowMediaQueryMock = useTabletAndBelowMediaQuery as jest.MockedFunction< + typeof useTabletAndBelowMediaQuery + > + }) + + describe('when the isOnSale filter is visible', () => { + it('should render the more filters section', () => { + const { container } = renderMoreFilters({ isOnSale: true }) + expect(container).not.toBeEmptyDOMElement() + }) + }) + + describe('when the wearables category is selected and the dispositive is tablet or mobile', () => { + beforeEach(() => { + useTabletAndBelowMediaQueryMock.mockReturnValue(true) + }) + + it('should render the more filters section', () => { + const { container } = renderMoreFilters({ + category: NFTCategory.WEARABLE + }) + expect(container).not.toBeEmptyDOMElement() + }) + }) + + describe('when the isOnSale filter is not visible', () => { + describe('and the selected category is not wearables', () => { + it('should not render the more filters section', () => { + const { container } = renderMoreFilters({ + category: NFTCategory.PARCEL, + isOnSale: undefined + }) + expect(container).toBeEmptyDOMElement() + }) + }) + + describe('and the selected category is wearables but the dispositive is not mobile nor tablet', () => { + beforeEach(() => { + useTabletAndBelowMediaQueryMock.mockReturnValue(false) + }) + + it('should not render the more filters section', () => { + const { container } = renderMoreFilters({ + category: NFTCategory.WEARABLE, + isOnSale: undefined + }) + expect(container).toBeEmptyDOMElement() + }) + }) + }) +}) diff --git a/webapp/src/components/AssetFilters/MoreFilters/MoreFilters.tsx b/webapp/src/components/AssetFilters/MoreFilters/MoreFilters.tsx index 5f37f636ab..bfef4bfb0e 100644 --- a/webapp/src/components/AssetFilters/MoreFilters/MoreFilters.tsx +++ b/webapp/src/components/AssetFilters/MoreFilters/MoreFilters.tsx @@ -1,12 +1,9 @@ import { useCallback, useMemo } from 'react' -import { - Box, - CheckboxProps, - Checkbox, - useTabletAndBelowMediaQuery -} from 'decentraland-ui' +import { Box, CheckboxProps, Checkbox } from 'decentraland-ui' import { t } from 'decentraland-dapps/dist/modules/translation/utils' +import { useTabletAndBelowMediaQuery } from 'decentraland-ui/dist/components/Media' import { NFTCategory } from '@dcl/schemas' +import { OnlySmartFilterContent } from '../OnlySmartFilter' import './MoreFilters.css' export type MoreFiltersProps = { @@ -28,13 +25,7 @@ export const MoreFilters = ({ }: MoreFiltersProps) => { const isWearableCategory = category === NFTCategory.WEARABLE const isMobileOrTablet = useTabletAndBelowMediaQuery() - - const handleOnlySmartChange = useCallback( - (_, props: CheckboxProps) => { - onOnlySmartChange(!!props.checked) - }, - [onOnlySmartChange] - ) + const showOnlySmartFilter = isWearableCategory && isMobileOrTablet const handleOnSaleChange = useCallback( (_, props: CheckboxProps) => { @@ -48,11 +39,11 @@ export const MoreFilters = ({ values.push( isOnSale ? t('nft_filters.for_sale') : t('nft_filters.not_on_sale') ) - if (isOnlySmart) { - values.push(t('nft_filters.only_smart')) + if (isOnlySmart && showOnlySmartFilter) { + values.push(t('nft_filters.only_smart.selected')) } return values.join(', ') - }, [isOnSale, isOnlySmart]) + }, [isOnSale, isOnlySmart, showOnlySmartFilter]) const header = useMemo( () => @@ -69,7 +60,7 @@ export const MoreFilters = ({ [filterText, isMobileOrTablet] ) - return ( + return isOnSale !== undefined || showOnlySmartFilter ? ( ) : null} - {isWearableCategory && ( - )} - ) + ) : null } diff --git a/webapp/src/components/AssetFilters/OnlySmartFilter/OnlySmartFilter.module.css b/webapp/src/components/AssetFilters/OnlySmartFilter/OnlySmartFilter.module.css new file mode 100644 index 0000000000..a574e8c961 --- /dev/null +++ b/webapp/src/components/AssetFilters/OnlySmartFilter/OnlySmartFilter.module.css @@ -0,0 +1,9 @@ +.onlySmartFilterSection { + display: flex; + justify-content: space-between; + align-items: center; +} + +.onlySmartFilterSection :global(.SmartBadge:not(.clickable)) { + cursor: initial; +} diff --git a/webapp/src/components/AssetFilters/OnlySmartFilter/OnlySmartFilter.spec.tsx b/webapp/src/components/AssetFilters/OnlySmartFilter/OnlySmartFilter.spec.tsx new file mode 100644 index 0000000000..89cfdb5bf7 --- /dev/null +++ b/webapp/src/components/AssetFilters/OnlySmartFilter/OnlySmartFilter.spec.tsx @@ -0,0 +1,45 @@ +import { useTabletAndBelowMediaQuery } from 'decentraland-ui/dist/components/Media' +import { renderWithProviders } from '../../../utils/test' +import { OnlySmartFilter, OnlySmartFilterProps } from './OnlySmartFilter' + +jest.mock('decentraland-ui/dist/components/Media', () => ({ + useTabletAndBelowMediaQuery: jest.fn() +})) + +function renderOnlySmartFilter(props: Partial = {}) { + return renderWithProviders( + + ) +} + +describe('OnlySmartFilter', () => { + let useTabletAndBelowMediaQueryMock: jest.MockedFunction + + beforeEach(() => { + useTabletAndBelowMediaQueryMock = useTabletAndBelowMediaQuery as jest.MockedFunction< + typeof useTabletAndBelowMediaQuery + > + }) + + describe('when the dispositive is mobile or tablet', () => { + beforeEach(() => { + useTabletAndBelowMediaQueryMock.mockReturnValue(true) + }) + + it('should not render the only smart filter section', () => { + const { container } = renderOnlySmartFilter() + expect(container).toBeEmptyDOMElement() + }) + }) + + describe('when the dispositive is not mobile nor tablet', () => { + beforeEach(() => { + useTabletAndBelowMediaQueryMock.mockReturnValue(false) + }) + + it('should render the only smart filter section', () => { + const { container } = renderOnlySmartFilter() + expect(container).not.toBeEmptyDOMElement() + }) + }) +}) diff --git a/webapp/src/components/AssetFilters/OnlySmartFilter/OnlySmartFilter.tsx b/webapp/src/components/AssetFilters/OnlySmartFilter/OnlySmartFilter.tsx new file mode 100644 index 0000000000..3548757e67 --- /dev/null +++ b/webapp/src/components/AssetFilters/OnlySmartFilter/OnlySmartFilter.tsx @@ -0,0 +1,49 @@ +import { useCallback } from 'react' +import { Box, Checkbox, CheckboxProps } from 'decentraland-ui' +import { useTabletAndBelowMediaQuery } from 'decentraland-ui/dist/components/Media' +import SmartBadge from '../../AssetPage/SmartBadge' +import styles from './OnlySmartFilter.module.css' + +export type OnlySmartFilterProps = { + isOnlySmart?: boolean + onChange: (value: boolean) => void + defaultCollapsed?: boolean + 'data-testid'?: string +} + +export const OnlySmartFilterContent = ( + props: Pick +) => { + const { isOnlySmart, onChange } = props + + const handleChange = useCallback( + (_, props: CheckboxProps) => { + onChange(!!props.checked) + }, + [onChange] + ) + + return ( +
+ + +
+ ) +} + +export const OnlySmartFilter = ({ + isOnlySmart, + onChange, + defaultCollapsed = false +}: OnlySmartFilterProps) => { + const isMobileOrTablet = useTabletAndBelowMediaQuery() + + return isMobileOrTablet ? null : ( + + + + ) +} diff --git a/webapp/src/components/AssetFilters/OnlySmartFilter/index.ts b/webapp/src/components/AssetFilters/OnlySmartFilter/index.ts new file mode 100644 index 0000000000..384d0b10a8 --- /dev/null +++ b/webapp/src/components/AssetFilters/OnlySmartFilter/index.ts @@ -0,0 +1 @@ +export * from './OnlySmartFilter' diff --git a/webapp/src/components/AssetFilters/utils.ts b/webapp/src/components/AssetFilters/utils.ts index 2b11b0ca7a..cbe1bbcb96 100644 --- a/webapp/src/components/AssetFilters/utils.ts +++ b/webapp/src/components/AssetFilters/utils.ts @@ -13,10 +13,12 @@ export const enum AssetFilter { Network, BodyShape, OnSale, + OnlySmart, More } const WearablesFilters = [ + AssetFilter.OnlySmart, AssetFilter.Rarity, AssetFilter.Status, AssetFilter.Price, @@ -33,7 +35,8 @@ const EmotesFilters = [ filter => filter !== AssetFilter.BodyShape && filter !== AssetFilter.Network && - filter !== AssetFilter.More + filter !== AssetFilter.More && + filter !== AssetFilter.OnlySmart ), AssetFilter.PlayMode ] diff --git a/webapp/src/components/AssetPage/SmartBadge/SmartBadge.css b/webapp/src/components/AssetPage/SmartBadge/SmartBadge.css index 8ad1ece4da..f0737dab2b 100644 --- a/webapp/src/components/AssetPage/SmartBadge/SmartBadge.css +++ b/webapp/src/components/AssetPage/SmartBadge/SmartBadge.css @@ -2,4 +2,4 @@ top: 2px; width: 14px; height: 14px; -} +} \ No newline at end of file diff --git a/webapp/src/components/AssetPage/SmartBadge/SmartBadge.tsx b/webapp/src/components/AssetPage/SmartBadge/SmartBadge.tsx index 755318b5db..147c72f59c 100644 --- a/webapp/src/components/AssetPage/SmartBadge/SmartBadge.tsx +++ b/webapp/src/components/AssetPage/SmartBadge/SmartBadge.tsx @@ -1,4 +1,5 @@ import React, { useMemo } from 'react' +import classNames from 'classnames' import { t } from 'decentraland-dapps/dist/modules/translation/utils' import { SmartIcon } from 'decentraland-ui' import { Section } from '../../../modules/vendor/decentraland' @@ -20,7 +21,7 @@ const SmartBadge = ({ assetType, clickable = true }: Props) => { return ( diff --git a/webapp/src/components/AssetPage/SmartBadge/SmartBadge.types.ts b/webapp/src/components/AssetPage/SmartBadge/SmartBadge.types.ts index b2e658395e..268395335b 100644 --- a/webapp/src/components/AssetPage/SmartBadge/SmartBadge.types.ts +++ b/webapp/src/components/AssetPage/SmartBadge/SmartBadge.types.ts @@ -1,8 +1,13 @@ import { AssetType } from '../../../modules/asset/types' -export type Props = { - assetType: AssetType - clickable?: boolean -} +export type Props = + | { + assetType?: AssetType + clickable?: false + } + | { + assetType: AssetType + clickable: true + } export type OwnProps = Pick diff --git a/webapp/src/components/AssetTopbar/SelectedFilters/SelectedFilters.tsx b/webapp/src/components/AssetTopbar/SelectedFilters/SelectedFilters.tsx index fb648750ab..d74f2d6b19 100644 --- a/webapp/src/components/AssetTopbar/SelectedFilters/SelectedFilters.tsx +++ b/webapp/src/components/AssetTopbar/SelectedFilters/SelectedFilters.tsx @@ -195,7 +195,7 @@ export const SelectedFilters = ({ ) : null} {onlySmart ? ( diff --git a/webapp/src/modules/translation/locales/en.json b/webapp/src/modules/translation/locales/en.json index aba5bedbaa..7af9678eb7 100644 --- a/webapp/src/modules/translation/locales/en.json +++ b/webapp/src/modules/translation/locales/en.json @@ -402,6 +402,10 @@ "all_items": "All rarities", "count_items": "{count} {count, plural, one {rarity} other {rarities}}" }, + "only_smart": { + "title": "Smart Wearables", + "selected": "Only Smart" + }, "periods": { "title": "Rental Period", "all_items": "All periods", @@ -451,7 +455,6 @@ }, "more_filters": "More filters", "location": "Location", - "only_smart": "Only smart", "not_on_sale": "Includes not on sale", "for_sale": "For sale", "category": "Category", diff --git a/webapp/src/modules/translation/locales/es.json b/webapp/src/modules/translation/locales/es.json index 1f8b59a3b3..6589023e1c 100644 --- a/webapp/src/modules/translation/locales/es.json +++ b/webapp/src/modules/translation/locales/es.json @@ -445,7 +445,10 @@ }, "more_filters": "Más filtros", "location": "Ubicación", - "only_smart": "Solo vestimentas interactivas", + "only_smart": { + "title": "Vestimentas interactivas", + "selected": "Solo vestimentas interactivas" + }, "not_on_sale": "Incluye no a la venta", "for_sale": "A la venta", "category": "Categoría", diff --git a/webapp/src/modules/translation/locales/zh.json b/webapp/src/modules/translation/locales/zh.json index cee8da8031..a220cfec8c 100644 --- a/webapp/src/modules/translation/locales/zh.json +++ b/webapp/src/modules/translation/locales/zh.json @@ -448,7 +448,10 @@ }, "more_filters": "更多过滤器", "location": "地点", - "only_smart": "只有聪明", + "only_smart": { + "title": "智能穿戴设备", + "selected": "只有聪明" + }, "not_on_sale": "包括非卖品", "adjacent_to_road": "毗邻道路", "distance_to_plaza": {