diff --git a/src/__data__/categories.ts b/src/__data__/categories.ts index cf9c772a..8fab00fe 100644 --- a/src/__data__/categories.ts +++ b/src/__data__/categories.ts @@ -1,16 +1,10 @@ -import { CategoryAttributes } from "../entities/Category/types" - -export const categories: CategoryAttributes[] = [ +export const categoriesWithPlacesCount = [ { name: "Art", - active: true, - created_at: new Date("2023-04-01T04:53:07.000Z"), - updated_at: new Date("2023-04-01T04:53:07.000Z"), + count: "10", }, { name: "music", - active: true, - created_at: new Date("2023-04-01T04:53:07.000Z"), - updated_at: new Date("2023-04-01T04:53:07.000Z"), + count: "10", }, ] diff --git a/src/__data__/placeGenesisPlaza.ts b/src/__data__/placeGenesisPlaza.ts index 1df5654f..0043edac 100644 --- a/src/__data__/placeGenesisPlaza.ts +++ b/src/__data__/placeGenesisPlaza.ts @@ -11,11 +11,8 @@ export const placeGenesisPlaza: PlaceAttributes = { like_score: 0, highlighted: false, highlighted_image: null, - featured: false, - featured_image: null, disabled: false, updated_at: new Date("2023-03-28T18:37:39.918Z"), - categories: [], world: false, world_name: null, title: "Genesis Plaza", @@ -23,7 +20,6 @@ export const placeGenesisPlaza: PlaceAttributes = { "Jump in to strike up a chat with other visitors, retake the commands tutorial with a cute floating robot, or dive into the swirling portal to get to Decentraland's visitor center.", owner: null, image: "https://decentraland.org/images/thumbnail/genesis-plaza.png", - tags: ["plaza"], base_position: "-9,-9", positions: [ "-1,-1", @@ -415,4 +411,5 @@ export const placeGenesisPlaza: PlaceAttributes = { deployed_at: new Date("2022-11-14T17:22:05.307Z"), hidden: false, textsearch: undefined, + categories: [], } diff --git a/src/__data__/placeGenesisPlazaWithAggregatedAttributes.ts b/src/__data__/placeGenesisPlazaWithAggregatedAttributes.ts index a14abfae..3f578161 100644 --- a/src/__data__/placeGenesisPlazaWithAggregatedAttributes.ts +++ b/src/__data__/placeGenesisPlazaWithAggregatedAttributes.ts @@ -10,7 +10,6 @@ export const placeGenesisPlazaWithAggregatedAttributes: AggregatePlaceAttributes "Jump in to strike up a chat with other visitors, retake the commands tutorial with a cute floating robot, or dive into the swirling portal to get to Decentraland's visitor center.", image: "https://localhost:8000/images/places/genesis_plaza.jpg", owner: null, - tags: [], positions: [ "-1,-1", "-1,-2", @@ -408,8 +407,6 @@ export const placeGenesisPlazaWithAggregatedAttributes: AggregatePlaceAttributes like_score: 0, highlighted: true, highlighted_image: "/images/places/genesis_plaza_banner.jpg", - featured: false, - featured_image: null, user_favorite: false, user_like: false, user_dislike: false, diff --git a/src/__data__/placeRoad.ts b/src/__data__/placeRoad.ts index d2be41f2..1f4b7d2e 100644 --- a/src/__data__/placeRoad.ts +++ b/src/__data__/placeRoad.ts @@ -11,18 +11,14 @@ export const placeRoad: PlaceAttributes = { like_score: 0, highlighted: false, highlighted_image: null, - featured: false, - featured_image: null, disabled: false, updated_at: new Date("2023-03-28T18:37:39.918Z"), - categories: [], world: false, world_name: null, title: "Road at -89,11 (open road OpenRoad_C)", description: null, owner: null, image: "https://decentraland.org/images/thumbnail/road.png", - tags: ["road"], base_position: "-89,11", positions: ["-89,11"], contact_name: "Decentraland Foundation", @@ -33,4 +29,5 @@ export const placeRoad: PlaceAttributes = { deployed_at: new Date("2022-11-14T17:22:05.307Z"), hidden: false, textsearch: undefined, + categories: [], } diff --git a/src/__data__/world.ts b/src/__data__/world.ts index 7ba97737..f64c12ec 100644 --- a/src/__data__/world.ts +++ b/src/__data__/world.ts @@ -17,7 +17,6 @@ export const worldPlaceParalax: PlaceAttributes = { image: "https://api.decentraland.org/v2/map.png?height=1024&width=1024&selected=0%2C0¢er=0%2C0&size=20", owner: null, - tags: [], positions: ["0,0"], base_position: "0,0", contact_name: "paralax", @@ -31,17 +30,15 @@ export const worldPlaceParalax: PlaceAttributes = { likes: 0, dislikes: 0, like_score: 0, - categories: [], like_rate: 0.5, highlighted: false, highlighted_image: null, - featured: false, - featured_image: null, world: true, world_name: "paralax.dcl.eth", deployed_at: new Date("2023-03-28T13:05:45.437Z"), hidden: false, textsearch: undefined, + categories: [], } export const worldPlaceParalaxWithAggregated: AggregatePlaceAttributes = { @@ -51,7 +48,6 @@ export const worldPlaceParalaxWithAggregated: AggregatePlaceAttributes = { image: "https://api.decentraland.org/v2/map.png?height=1024&width=1024&selected=0%2C0¢er=0%2C0&size=20", owner: null, - tags: [], positions: ["0,0"], base_position: "0,0", contact_name: "paralax", @@ -64,13 +60,10 @@ export const worldPlaceParalaxWithAggregated: AggregatePlaceAttributes = { favorites: 0, likes: 0, dislikes: 0, - categories: [], like_rate: 0.5, like_score: 0, highlighted: false, highlighted_image: null, - featured: false, - featured_image: null, world: true, world_name: "paralax.dcl.eth", deployed_at: new Date("2023-03-28T13:05:45.437Z"), @@ -79,6 +72,7 @@ export const worldPlaceParalaxWithAggregated: AggregatePlaceAttributes = { user_like: false, user_dislike: false, textsearch: undefined, + categories: [], } export const worldContentEntitySceneParalax: ContentEntityScene = { @@ -199,7 +193,6 @@ export const worldPlaceTemplegame: AggregatePlaceAttributes = { image: "https://peer.decentraland.org/content/contents/bafkreiag7fylur5qlntcxb2oyaw3asmchxsbhoxnw6iipayfgj4wwmqkli", owner: null, - tags: [], positions: [ "-1,0", "-1,1", @@ -273,13 +266,10 @@ export const worldPlaceTemplegame: AggregatePlaceAttributes = { favorites: 0, likes: 0, dislikes: 0, - categories: [], like_rate: 0.5, like_score: 0, highlighted: false, highlighted_image: null, - featured: false, - featured_image: null, world: true, world_name: "templegame.dcl.eth", deployed_at: new Date("2023-05-16T15:44:26.395Z"), @@ -289,4 +279,5 @@ export const worldPlaceTemplegame: AggregatePlaceAttributes = { user_dislike: false, user_count: 3, textsearch: undefined, + categories: [], } diff --git a/src/api/Places.ts b/src/api/Places.ts index 33fb9976..671d716b 100644 --- a/src/api/Places.ts +++ b/src/api/Places.ts @@ -4,9 +4,11 @@ import Options from "decentraland-gatsby/dist/utils/api/Options" import Time from "decentraland-gatsby/dist/utils/date/Time" import env from "decentraland-gatsby/dist/utils/env" +import { DecentralandCategories } from "../entities/Category/types" import { AggregatePlaceAttributes, PlaceListOptions, + PlaceListOrderBy, } from "../entities/Place/types" import { UpdateUserFavoriteResponse } from "../entities/UserFavorite/types" import { UpdateUserLikeResponse } from "../entities/UserLikes/types" @@ -121,7 +123,7 @@ export default class Places extends API { async getPlacesHightRated(options?: { limit: number; offset: number }) { return this.getPlaces({ - order_by: "like_rate", + order_by: PlaceListOrderBy.LIKE_SCORE_BEST, order: "desc", ...options, }) @@ -137,7 +139,7 @@ export default class Places extends API { async getPlacesMostActive(options?: { limit: number; offset: number }) { return this.getPlaces({ - order_by: "most_active", + order_by: PlaceListOrderBy.MOST_ACTIVE, order: "desc", ...options, }) @@ -145,8 +147,8 @@ export default class Places extends API { async getPlacesFeatured(options?: { limit: number; offset: number }) { return this.getPlaces({ - only_featured: true, ...options, + categories: [DecentralandCategories.FEATURED], }) } @@ -192,4 +194,22 @@ export default class Places extends API { this.options({ method: "PUT" }).json(params).authorization({ sign: true }) ) } + + async getCategories() { + const result = await super.fetch<{ + ok: boolean + data: { name: string; count: number }[] + }>("/categories") + + return result.data + } + + async getPlaceCategories(placeId: string) { + const result = await super.fetch<{ + ok: boolean + data: { categories: string[] } + }>(`/places/${placeId}/categories`) + + return result.data + } } diff --git a/src/components/Button/CategoryButtons.css b/src/components/Button/CategoryButtons.css new file mode 100644 index 00000000..3b2b3bcf --- /dev/null +++ b/src/components/Button/CategoryButtons.css @@ -0,0 +1,15 @@ +.category-buttons__container { + display: flex; + flex-wrap: wrap; + width: 100%; +} + +.category-buttons__container > .category-button { + margin: 0 17px 17px 0; +} + +.category-buttons__container > .dg.Link.ui.button.category-button { + background-color: var(--text-on-primary); + font-weight: normal; + color: var(--text-on-secondary); +} diff --git a/src/components/Button/CategoryButtons.tsx b/src/components/Button/CategoryButtons.tsx new file mode 100644 index 00000000..f2bb9c94 --- /dev/null +++ b/src/components/Button/CategoryButtons.tsx @@ -0,0 +1,38 @@ +import React from "react" + +import useFormatMessage from "decentraland-gatsby/dist/hooks/useFormatMessage" +import { Link } from "decentraland-gatsby/dist/plugins/intl" +import TokenList from "decentraland-gatsby/dist/utils/dom/TokenList" +import { Button } from "decentraland-ui/dist/components/Button/Button" + +import { PlaceListOrderBy } from "../../entities/Place/types" +import locations from "../../modules/locations" + +import "./CategoryButtons.css" + +type CategoryButtonsProps = { + categories: string[] + className?: string +} + +export const CategoryButtons = (props: CategoryButtonsProps) => { + const { categories, className } = props + const l = useFormatMessage() + + return ( +
+ {categories.map((category) => ( +
+ ) +} diff --git a/src/components/Category/CategoryFilter.css b/src/components/Category/CategoryFilter.css new file mode 100644 index 00000000..a950e5c6 --- /dev/null +++ b/src/components/Category/CategoryFilter.css @@ -0,0 +1,33 @@ +.category-filter__box { + margin-right: 8px; + margin-bottom: 8px; +} + +.category-filter__box .dcl.filter .filter-background { + background-color: var(--primary); + opacity: 1; +} + +.category-filter__box--not-active .dcl.filter .filter-background { + background-color: var(--background); +} + +.category-filter__box .dcl.filter > span { + color: var(--text-on-primary); + display: flex; + align-items: center; +} + +.category-filter__box.category-filter__box--not-active .dcl.filter > span { + color: var(--text); +} + +.category-filter__icon { + width: 20px; + height: 20px; + margin-left: 2px; +} + +.category-filter__box > .dcl.filter { + margin-bottom: 2px; +} diff --git a/src/components/Category/CategoryFilter.tsx b/src/components/Category/CategoryFilter.tsx new file mode 100644 index 00000000..ec0884e2 --- /dev/null +++ b/src/components/Category/CategoryFilter.tsx @@ -0,0 +1,41 @@ +import React, { useCallback } from "react" + +import useFormatMessage from "decentraland-gatsby/dist/hooks/useFormatMessage" +import TokenList from "decentraland-gatsby/dist/utils/dom/TokenList" +import { Filter } from "decentraland-ui/dist/components/Filter/Filter" + +import "./CategoryFilter.css" + +export type CategoryFilterProps = { + category: string + onChange?: ( + e: React.MouseEvent, + props: CategoryFilterProps + ) => void + active?: boolean + actionIcon?: React.ReactNode +} + +export const CategoryFilter = (props: CategoryFilterProps) => { + const { category, active, onChange, actionIcon } = props + const l = useFormatMessage() + + const handleClick = useCallback( + (e) => onChange && onChange(e, { active: !active, category }), + [active, onChange, category] + ) + return ( + + + {l(`categories.${category}`)} + {active && {actionIcon}} + + + ) +} diff --git a/src/components/Category/CategoryFilters.tsx b/src/components/Category/CategoryFilters.tsx new file mode 100644 index 00000000..540846b4 --- /dev/null +++ b/src/components/Category/CategoryFilters.tsx @@ -0,0 +1,45 @@ +import React from "react" + +import { Category } from "../../entities/Category/types" +import { CategoryFilter, CategoryFilterProps } from "./CategoryFilter" + +type CategoryFiltersProps = { + categories: Category[] + onChange: ( + e: React.MouseEvent, + props: CategoryFilterProps + ) => void + onlyActives?: boolean + filtersIcon?: React.ReactNode + unremovableFilters?: boolean + className?: string +} + +export const CategoryFilters = (props: CategoryFiltersProps) => { + const { + categories, + onChange, + filtersIcon, + onlyActives, + // TODO: review a way to do this or if this is needed (@lauti7) + // unremovableFilters, + className, + } = props + + return ( +
+ {categories + .filter(({ active }) => (onlyActives ? active : true)) + .sort((a, b) => (a.name === b.name ? 0 : a.name < b.name ? -1 : 1)) + .map((category) => ( + + ))} +
+ ) +} diff --git a/src/components/Category/CategoryList.css b/src/components/Category/CategoryList.css new file mode 100644 index 00000000..5d09098c --- /dev/null +++ b/src/components/Category/CategoryList.css @@ -0,0 +1,41 @@ +.category-list__box { + background-color: var(--secondary-on-modal-hover); + width: 248px; + padding: 16px; + border-radius: 10px; +} + +.category-list__title { + margin-bottom: 15px; + display: flex; + align-items: center; +} + +.category-list__title > p { + color: var(--secondary-text); + letter-spacing: 1px; + font-weight: 700; + font-size: 14px; + margin: 0 16px 0 0; + text-transform: uppercase; +} + +.category-list__title > .ui.label { + font-weight: 700; + font-size: 8px; + background-color: var(--primary); + padding: 2px 5px; + line-height: normal; + border-radius: 3px; + color: var(--text-on-primary); +} + +.category-list__listing { + display: flex; + flex-wrap: wrap; +} + +.category-list__listing > .category-filter__box { + margin-bottom: 8px; + margin-right: 8px; +} diff --git a/src/components/Category/CategoryList.tsx b/src/components/Category/CategoryList.tsx new file mode 100644 index 00000000..ba7a0f5d --- /dev/null +++ b/src/components/Category/CategoryList.tsx @@ -0,0 +1,41 @@ +import React from "react" + +import useFormatMessage from "decentraland-gatsby/dist/hooks/useFormatMessage" + +import { Category } from "../../entities/Category/types" +import { Check } from "../Icon/Check" +import { NewLabel } from "../Label/NewLabel/NewLabel" +import { CategoryFilterProps } from "./CategoryFilter" +import { CategoryFilters } from "./CategoryFilters" + +import "./CategoryList.css" + +type CategoryList = { + onChange: ( + e: React.MouseEvent, + props: CategoryFilterProps + ) => void + categories: Category[] + applyfilter?: boolean +} + +export const CategoryList = React.memo((props: CategoryList) => { + const { categories, onChange } = props + const l = useFormatMessage() + + return ( +
+ + } + unremovableFilters + className="category-list__listing" + /> +
+ ) +}) diff --git a/src/components/Category/CategorySections.css b/src/components/Category/CategorySections.css new file mode 100644 index 00000000..69c8cdee --- /dev/null +++ b/src/components/Category/CategorySections.css @@ -0,0 +1,30 @@ +.category-sections__box { + background-color: var(--secondary-on-modal-hover); + width: 100%; + padding: 35px; +} + +.category-sections__title { + margin-bottom: 25px; + display: flex; + align-items: center; +} + +.category-sections__title > p { + color: var(--secondary-text); + letter-spacing: 1px; + font-weight: 700; + font-size: 14px; + margin: 0 16px 0 0; + text-transform: uppercase; +} + +.category-sections__title > .ui.label { + font-weight: 700; + font-size: 8px; + background-color: var(--primary); + line-height: normal; + padding: 2px 5px; + border-radius: 3px; + color: var(--text-on-primary); +} diff --git a/src/components/Category/CategorySections.tsx b/src/components/Category/CategorySections.tsx new file mode 100644 index 00000000..bd90ff67 --- /dev/null +++ b/src/components/Category/CategorySections.tsx @@ -0,0 +1,27 @@ +import React from "react" + +import useFormatMessage from "decentraland-gatsby/dist/hooks/useFormatMessage" + +import { CategoryButtons } from "../Button/CategoryButtons" +import { NewLabel } from "../Label/NewLabel/NewLabel" + +import "./CategorySections.css" + +type CategorySectionsProps = { + categories: string[] +} + +export const CategorySections = (props: CategorySectionsProps) => { + const { categories } = props + const l = useFormatMessage() + + return ( +
+ + +
+ ) +} diff --git a/src/components/Icon/Check.tsx b/src/components/Icon/Check.tsx new file mode 100644 index 00000000..91e5c58d --- /dev/null +++ b/src/components/Icon/Check.tsx @@ -0,0 +1,15 @@ +import React from "react" + +export type CheckIconProps = React.SVGAttributes + +export const Check = React.memo((props: CheckIconProps) => ( + + + + + +)) diff --git a/src/components/Icon/Close.tsx b/src/components/Icon/Close.tsx new file mode 100644 index 00000000..0466c0d0 --- /dev/null +++ b/src/components/Icon/Close.tsx @@ -0,0 +1,37 @@ +import React from "react" + +type CloseProps = React.SVGAttributes & { + type?: "primary" | "secondary" +} + +export const Close = React.memo( + ({ type = "primary", ...props }: CloseProps) => { + return ( + <> + {type === "primary" && ( + + + + + + )} + {type !== "primary" && ( + + + + + + )} + + ) + } +) diff --git a/src/components/Icon/Filter.tsx b/src/components/Icon/Filter.tsx new file mode 100644 index 00000000..f7208757 --- /dev/null +++ b/src/components/Icon/Filter.tsx @@ -0,0 +1,36 @@ +import React from "react" + +export type FilterProps = React.SVGAttributes + +export const Filter = React.memo((props: FilterProps) => ( + + + + + + + + + + + +)) diff --git a/src/components/Icon/Trash.tsx b/src/components/Icon/Trash.tsx new file mode 100644 index 00000000..d233b6dc --- /dev/null +++ b/src/components/Icon/Trash.tsx @@ -0,0 +1,15 @@ +import React from "react" + +export const Trash = React.memo((props: React.SVGAttributes) => ( + + + + + +)) diff --git a/src/components/Label/NewLabel/NewLabel.tsx b/src/components/Label/NewLabel/NewLabel.tsx new file mode 100644 index 00000000..d9d28527 --- /dev/null +++ b/src/components/Label/NewLabel/NewLabel.tsx @@ -0,0 +1,20 @@ +import React from "react" + +import useFormatMessage from "decentraland-gatsby/dist/hooks/useFormatMessage" +import Label from "semantic-ui-react/dist/commonjs/elements/Label" + +export type NewLabelProps = { + title: string + className?: string +} + +export const NewLabel = React.memo(({ title, className }: NewLabelProps) => { + const l = useFormatMessage() + + return ( +
+

{title}

+ +
+ ) +}) diff --git a/src/components/Layout/Navigation.tsx b/src/components/Layout/Navigation.tsx index d16d295c..a4d2670b 100644 --- a/src/components/Layout/Navigation.tsx +++ b/src/components/Layout/Navigation.tsx @@ -1,19 +1,16 @@ -import React, { useCallback, useMemo } from "react" +import React from "react" -import { useLocation } from "@gatsbyjs/reach-router" import NavigationMenu from "decentraland-gatsby/dist/components/Layout/NavigationMenu" import useAuthContext from "decentraland-gatsby/dist/context/Auth/useAuthContext" import useFeatureFlagContext from "decentraland-gatsby/dist/context/FeatureFlag/useFeatureFlagContext" import useTrackLinkContext from "decentraland-gatsby/dist/context/Track/useTrackLinkContext" import useFormatMessage from "decentraland-gatsby/dist/hooks/useFormatMessage" -import { navigate } from "decentraland-gatsby/dist/plugins/intl" import { PlaceListOrderBy } from "../../entities/Place/types" import { WorldListOrderBy } from "../../entities/World/types" import { FeatureFlags } from "../../modules/ff" import locations from "../../modules/locations" import { OpenBlank } from "../Icon/OpenBlank" -import SearchInput from "./SearchInput" import "./Navigation.css" @@ -34,35 +31,6 @@ export default function Navigation(props: NavigationProps) { const track = useTrackLinkContext() const [ff] = useFeatureFlagContext() - const location = useLocation() - - const params = useMemo( - () => new URLSearchParams(location.search), - [location.search] - ) - - const handleSearchChange = useCallback( - (e: React.ChangeEvent) => { - const newParams = new URLSearchParams(params) - if (e.target.value) { - newParams.set("search", e.target.value) - } else { - newParams.delete("search") - } - - let target = location.pathname - const search = newParams.toString() - // location - // navigate to /search+=?search=${search} - if (search) { - target += "?" + search - } - - navigate(target) - }, - [location.pathname, params] - ) - return ( } - rightMenu={ - props.activeTab && ( - - ) - } /> ) } diff --git a/src/components/Layout/OverviewList.tsx b/src/components/Layout/OverviewList.tsx index a0b4b26f..d775f278 100644 --- a/src/components/Layout/OverviewList.tsx +++ b/src/components/Layout/OverviewList.tsx @@ -16,10 +16,9 @@ import PlaceList from "../Place/PlaceList/PlaceList" import "./OverviewList.css" -export type OverviewListProps = { +type BaseOverviewListProps = { places: AggregatePlaceAttributes[] title: string | React.ReactNode - href: string loading: boolean onClickFavorite: ( e: React.MouseEvent, @@ -32,12 +31,24 @@ export type OverviewListProps = { searchResultCount?: number } +type OverviewListPropsWithHref = BaseOverviewListProps & { + href: string + onClick?: never +} +type OverviewListPropsWithOnClick = BaseOverviewListProps & { + onClick: () => void + href?: never +} + +export type OverviewListProps = + | OverviewListPropsWithHref + | OverviewListPropsWithOnClick + export default React.memo(function OverviewList(props: OverviewListProps) { const { places, title, loading, - href, onClickFavorite, loadingFavorites, dataPlace, @@ -68,18 +79,39 @@ export default React.memo(function OverviewList(props: OverviewListProps) { )} - + {props.href && ( + + )} + {props.onClick && ( + + )} diff --git a/src/components/Layout/SearchInput.css b/src/components/Layout/SearchInput.css index 72a4fa06..0b0e22ef 100644 --- a/src/components/Layout/SearchInput.css +++ b/src/components/Layout/SearchInput.css @@ -5,6 +5,7 @@ background: white no-repeat 8px center; background-image: url("../../images/magnify.svg"); min-width: 356px; + width: 95%; height: 40px; } diff --git a/src/components/Layout/SearchList.tsx b/src/components/Layout/SearchList.tsx index 0e41e756..b40e9928 100644 --- a/src/components/Layout/SearchList.tsx +++ b/src/components/Layout/SearchList.tsx @@ -2,12 +2,7 @@ import React from "react" import useFormatMessage from "decentraland-gatsby/dist/hooks/useFormatMessage" -import { - AggregatePlaceAttributes, - PlaceListOrderBy, -} from "../../entities/Place/types" -import { WorldListOrderBy } from "../../entities/World/types" -import watermelonIcon from "../../images/watermelon-icon.svg" +import { AggregatePlaceAttributes } from "../../entities/Place/types" import locations from "../../modules/locations" import { SegmentPlace } from "../../modules/segment" import NoResults from "./NoResults" diff --git a/src/components/Modal/CategoryModal.css b/src/components/Modal/CategoryModal.css new file mode 100644 index 00000000..5da1620f --- /dev/null +++ b/src/components/Modal/CategoryModal.css @@ -0,0 +1,123 @@ +.categories-modal__layer { + height: 100vh; + width: 100%; + position: absolute; + top: 0; + background-color: rgba(0, 0, 0, 0.248); + z-index: 21; /* due to navbar */ +} + +.ui.grid > .categories-modal__layer { + padding: 0; +} + +.ui.grid > .categories-modal__layer > .categories-modal__box { + padding: 0; +} + +.ui.page.modals.dimmer.transition.visible.active { + z-index: 2147483004; +} + +.ui.modal.categories-modal__box { + animation: slide-up 0.3s ease-out; + display: flex !important; + flex-direction: column; + border-radius: 16px 16px 0 0; + height: 50vh; + position: fixed; + bottom: 0; + background-color: #ecebed; + justify-content: space-between; +} + +.ui.modal .header.categories-modal__header .categories-modal__text--smaller { + width: 160px; + margin-left: 0; +} + +.ui.modal .header.categories-modal__header { + display: flex; + justify-content: space-between; + border-radius: 16px 16px 0 0; + align-items: center; + background-color: white; + padding: 10px 0 !important; + margin: 0; +} + +.ui.modal .header.categories-modal__header > .clear-all { + width: 86px; +} + +.ui.modal .header.categories-modal__header > p { + margin-bottom: 0; + color: #a09ba8; + font-size: 14px; + width: 240px; + margin-left: 16px; +} + +.ui.modal .header.categories-modal__header > .close-btn { + height: 24px; + margin-bottom: 2px; + width: 24px; + padding: 0; + margin-right: 16px; +} + +.ui.modal .content.categories-modal__content { + display: flex; + flex-wrap: wrap; + align-content: baseline; + width: 100%; + padding: 10px 8px; + background: none; + margin: 0; + flex-grow: 1; +} + +.ui.modal .content.categories-modal__content .category-filter__box { + margin: 8px; + display: inline-block; +} + +.ui.modal .actions.categories-modal__footer { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + background: none; + margin: 0; +} + +.ui.modal .actions.categories-modal__footer > .ui.button { + width: 75%; + margin: 0 0 10px 0; +} + +.ui.page.dimmer { + perspective: none; +} + +@keyframes slide-up { + 0% { + transform: translateY(100%); + opacity: 0; + } + 100% { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes slide-down { + 0% { + transform: translateY(0); + opacity: 1; + } + 100% { + transform: translateY(100%); + opacity: 0; + } +} diff --git a/src/components/Modal/CategoryModal.tsx b/src/components/Modal/CategoryModal.tsx new file mode 100644 index 00000000..bb8cc180 --- /dev/null +++ b/src/components/Modal/CategoryModal.tsx @@ -0,0 +1,95 @@ +import React, { useCallback, useMemo } from "react" + +import useFormatMessage from "decentraland-gatsby/dist/hooks/useFormatMessage" +import TokenList from "decentraland-gatsby/dist/utils/dom/TokenList" +import { Button } from "decentraland-ui/dist/components/Button/Button" +import { Modal, ModalProps } from "decentraland-ui/dist/components/Modal/Modal" + +import { Category } from "../../entities/Category/types" +import { CategoryFilterProps } from "../Category/CategoryFilter" +import { CategoryFilters } from "../Category/CategoryFilters" +import { Check } from "../Icon/Check" +import { Close } from "../Icon/Close" + +import "./CategoryModal.css" + +export type CategoryModalProps = ModalProps & { + categories: Category[] + onChange: ( + e: React.MouseEvent, + props: CategoryFilterProps + ) => void + onClearAll: () => void +} + +export const CategoryModal = React.memo((props: CategoryModalProps) => { + const { categories, onChange, onClearAll, onClose, onActionClick, ...rest } = + props + + const l = useFormatMessage() + + const selectedCategories = useMemo( + () => + new Set( + categories.filter(({ active }) => active).map(({ name }) => name) + ), + [categories] + ) + + const handleClose = useCallback( + (e) => onClose && onClose(e, { ...props, open: false }), + [onClose] + ) + + const handleAction = useCallback( + (e) => onActionClick && onActionClick(e, props), + [onActionClick] + ) + + return ( + + +