mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-04 03:43:34 +00:00 
			
		
		
		
	Merge branch 'market-sort-filters/sort-filter' of github.com:strapi/strapi into market-sort-filters/api-filter-requests
This commit is contained in:
		
						commit
						f96a07dae2
					
				@ -1,4 +1,5 @@
 | 
			
		||||
import React, { useState, useRef } from 'react';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import { useIntl } from 'react-intl';
 | 
			
		||||
import { Box } from '@strapi/design-system/Box';
 | 
			
		||||
@ -18,6 +19,10 @@ const FilterTag = ({ name, handleRemove }) => {
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ButtonToggle = styled(Button)`
 | 
			
		||||
  height: ${({ theme }) => theme.sizes.input.S};
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const NpmPackagesFilters = ({
 | 
			
		||||
  possibleCollections,
 | 
			
		||||
  possibleCategories,
 | 
			
		||||
@ -35,7 +40,7 @@ const NpmPackagesFilters = ({
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <Box paddingTop={1} paddingBottom={1}>
 | 
			
		||||
        <Button
 | 
			
		||||
        <ButtonToggle
 | 
			
		||||
          variant="tertiary"
 | 
			
		||||
          ref={buttonRef}
 | 
			
		||||
          startIcon={<Filter />}
 | 
			
		||||
@ -43,7 +48,7 @@ const NpmPackagesFilters = ({
 | 
			
		||||
          size="S"
 | 
			
		||||
        >
 | 
			
		||||
          {formatMessage({ id: 'app.utils.filters', defaultMessage: 'Filters' })}
 | 
			
		||||
        </Button>
 | 
			
		||||
        </ButtonToggle>
 | 
			
		||||
        {isVisible && (
 | 
			
		||||
          <FiltersPopover
 | 
			
		||||
            onToggle={handleToggle}
 | 
			
		||||
 | 
			
		||||
@ -1,137 +1,75 @@
 | 
			
		||||
import React, { useRef, useState } from 'react';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
import { Select, Option } from '@strapi/design-system/Select';
 | 
			
		||||
import { Box } from '@strapi/design-system/Box';
 | 
			
		||||
import { useIntl } from 'react-intl';
 | 
			
		||||
import PropTypes from 'prop-types';
 | 
			
		||||
import { Option } from '@strapi/design-system/Select';
 | 
			
		||||
import { Popover } from '@strapi/design-system/Popover';
 | 
			
		||||
import { Button } from '@strapi/design-system/Button';
 | 
			
		||||
import { FocusTrap } from '@strapi/design-system/FocusTrap';
 | 
			
		||||
import CarretDown from '@strapi/icons/CarretDown';
 | 
			
		||||
import styled from 'styled-components';
 | 
			
		||||
 | 
			
		||||
const SortOption = styled(Option)`
 | 
			
		||||
  list-style-type: none;
 | 
			
		||||
`;
 | 
			
		||||
const SelectWrapper = styled(Box)`
 | 
			
		||||
  font-weight: ${({ theme }) => theme.fontWeights.semiBold};
 | 
			
		||||
 | 
			
		||||
const SortToggleButton = styled(Button)`
 | 
			
		||||
  svg {
 | 
			
		||||
    width: ${({ theme }) => theme.spaces[2]};
 | 
			
		||||
    height: ${({ theme }) => theme.spaces[1]};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  svg > path {
 | 
			
		||||
    fill: ${({ theme }) => theme.colors.neutral500};
 | 
			
		||||
  span {
 | 
			
		||||
    font-size: ${({ theme }) => theme.fontSizes[1]};
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
const SortSelect = ({ sortQuery, setQuery, setTabQuery, npmPackageType }) => {
 | 
			
		||||
  const { formatMessage } = useIntl();
 | 
			
		||||
  const buttonRef = useRef();
 | 
			
		||||
  const [isVisible, setIsVisible] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const sortTypes = {
 | 
			
		||||
    'name:asc': {
 | 
			
		||||
      id: 'admin.pages.MarketPlacePage.sort.alphabetical',
 | 
			
		||||
      defaultMessage: 'Alphabetical order',
 | 
			
		||||
      selected: {
 | 
			
		||||
        id: 'admin.pages.MarketPlacePage.sort.alphabetical.selected',
 | 
			
		||||
        defaultMessage: 'Sort by alphabetical order',
 | 
			
		||||
      },
 | 
			
		||||
      option: {
 | 
			
		||||
        id: 'admin.pages.MarketPlacePage.sort.alphabetical',
 | 
			
		||||
        defaultMessage: 'Alphabetical order',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    'submissionDate:desc': {
 | 
			
		||||
      id: 'admin.pages.MarketPlacePage.sort.newest',
 | 
			
		||||
      defaultMessage: 'Newest',
 | 
			
		||||
      selected: {
 | 
			
		||||
        id: 'admin.pages.MarketPlacePage.sort.newest.selected',
 | 
			
		||||
        defaultMessage: 'Sort by newest',
 | 
			
		||||
      },
 | 
			
		||||
      option: {
 | 
			
		||||
        id: 'admin.pages.MarketPlacePage.sort.newest',
 | 
			
		||||
        defaultMessage: 'Newest',
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleToggle = () => setIsVisible((prev) => !prev);
 | 
			
		||||
 | 
			
		||||
  const handleBlur = (e) => {
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
 | 
			
		||||
    if (!e.currentTarget.contains(e.relatedTarget)) {
 | 
			
		||||
      setIsVisible(false);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleSortClick = (sortName) => {
 | 
			
		||||
    setQuery({ sort: sortName });
 | 
			
		||||
    setTabQuery((prev) => ({
 | 
			
		||||
      ...prev,
 | 
			
		||||
      [npmPackageType]: { ...prev[npmPackageType], sort: sortName, npmPackageType },
 | 
			
		||||
    }));
 | 
			
		||||
    handleToggle();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const computeSortMessage = (sortName) => {
 | 
			
		||||
    const defaultSortLabel = formatMessage({
 | 
			
		||||
      id: 'admin.pages.MarketPlacePage.sort.sortBy',
 | 
			
		||||
      defaultMessage: 'Sort by',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (sortName) {
 | 
			
		||||
      const sortInfo = sortTypes[sortName];
 | 
			
		||||
      const message = formatMessage({
 | 
			
		||||
        id: sortInfo.id,
 | 
			
		||||
        defaultMessage: sortInfo.defaultMessage,
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return `${defaultSortLabel}: ${message}`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return defaultSortLabel;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      <SortToggleButton
 | 
			
		||||
        aria-label={computeSortMessage(sortQuery)}
 | 
			
		||||
        aria-controls="sort-by-values"
 | 
			
		||||
        aria-haspopup="dialog"
 | 
			
		||||
        aria-expanded={isVisible}
 | 
			
		||||
        aria-disabled={false}
 | 
			
		||||
        onClick={handleToggle}
 | 
			
		||||
        variant="tertiary"
 | 
			
		||||
        ref={buttonRef}
 | 
			
		||||
        endIcon={<CarretDown aria-hidden />}
 | 
			
		||||
    <SelectWrapper>
 | 
			
		||||
      <Select
 | 
			
		||||
        size="S"
 | 
			
		||||
        id="sort-by-select"
 | 
			
		||||
        value={sortQuery}
 | 
			
		||||
        customizeContent={() => formatMessage(sortTypes[sortQuery].selected)}
 | 
			
		||||
        onChange={(sortName) => {
 | 
			
		||||
          setQuery({ sort: sortName });
 | 
			
		||||
          setTabQuery((prev) => ({
 | 
			
		||||
            ...prev,
 | 
			
		||||
            [npmPackageType]: { ...prev[npmPackageType], sort: sortName, npmPackageType },
 | 
			
		||||
          }));
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        {computeSortMessage(sortQuery)}
 | 
			
		||||
      </SortToggleButton>
 | 
			
		||||
      {isVisible && (
 | 
			
		||||
        <Popover
 | 
			
		||||
          role="dialog"
 | 
			
		||||
          id="sort-by-values"
 | 
			
		||||
          onBlur={handleBlur}
 | 
			
		||||
          source={buttonRef}
 | 
			
		||||
          spacing={4}
 | 
			
		||||
        >
 | 
			
		||||
          <FocusTrap onEscape={handleToggle}>
 | 
			
		||||
            {Object.entries(sortTypes).map(([sortName, sortInfo], index) => {
 | 
			
		||||
              const { id, defaultMessage } = sortInfo;
 | 
			
		||||
 | 
			
		||||
              return (
 | 
			
		||||
                <SortOption
 | 
			
		||||
                  key={sortName}
 | 
			
		||||
                  value={sortName}
 | 
			
		||||
                  selected={sortQuery === sortName}
 | 
			
		||||
                  onClick={() => handleSortClick(sortName)}
 | 
			
		||||
                  tabIndex={index}
 | 
			
		||||
                >
 | 
			
		||||
                  {formatMessage({ id, defaultMessage })}
 | 
			
		||||
                </SortOption>
 | 
			
		||||
              );
 | 
			
		||||
            })}
 | 
			
		||||
          </FocusTrap>
 | 
			
		||||
        </Popover>
 | 
			
		||||
      )}
 | 
			
		||||
    </>
 | 
			
		||||
        {Object.entries(sortTypes).map(([sortName, messages]) => {
 | 
			
		||||
          return (
 | 
			
		||||
            <Option key={sortName} value={sortName}>
 | 
			
		||||
              {formatMessage(messages.option)}
 | 
			
		||||
            </Option>
 | 
			
		||||
          );
 | 
			
		||||
        })}
 | 
			
		||||
      </Select>
 | 
			
		||||
    </SelectWrapper>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
SortSelect.defaultProps = {
 | 
			
		||||
  sortQuery: undefined,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
SortSelect.propTypes = {
 | 
			
		||||
  sortQuery: PropTypes.string.isRequired,
 | 
			
		||||
  setQuery: PropTypes.func.isRequired,
 | 
			
		||||
  setTabQuery: PropTypes.func.isRequired,
 | 
			
		||||
  sortQuery: PropTypes.string,
 | 
			
		||||
  npmPackageType: PropTypes.string.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -183,7 +183,7 @@ const MarketPlacePage = () => {
 | 
			
		||||
    } else {
 | 
			
		||||
      setQuery(
 | 
			
		||||
        {
 | 
			
		||||
          npmPackageType: null,
 | 
			
		||||
          npmPackageType: selectedTab,
 | 
			
		||||
          // Clear filters
 | 
			
		||||
          collections: [],
 | 
			
		||||
          categories: [],
 | 
			
		||||
@ -194,7 +194,6 @@ const MarketPlacePage = () => {
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  console.log(tabQuery);
 | 
			
		||||
  // Check if plugins and providers are installed already
 | 
			
		||||
  const installedPackageNames = Object.keys(dependencies);
 | 
			
		||||
 | 
			
		||||
@ -266,7 +265,7 @@ const MarketPlacePage = () => {
 | 
			
		||||
            </Box>
 | 
			
		||||
            <Flex paddingBottom={4} gap={2}>
 | 
			
		||||
              <SortSelect
 | 
			
		||||
                sortQuery={query?.sort}
 | 
			
		||||
                sortQuery={query?.sort || 'name:asc'}
 | 
			
		||||
                setQuery={setQuery}
 | 
			
		||||
                setTabQuery={setTabQuery}
 | 
			
		||||
                npmPackageType={npmPackageType}
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -287,7 +287,7 @@ describe('Marketplace page', () => {
 | 
			
		||||
  it('shows the correct options on sort select', () => {
 | 
			
		||||
    render(App);
 | 
			
		||||
    const sortButton = screen.getByRole('button', { name: /Sort by/i });
 | 
			
		||||
    fireEvent.click(sortButton);
 | 
			
		||||
    fireEvent.mouseDown(sortButton);
 | 
			
		||||
 | 
			
		||||
    const alphabeticalOption = screen.getByRole('option', { name: 'Alphabetical order' });
 | 
			
		||||
    const newestOption = screen.getByRole('option', { name: 'Newest' });
 | 
			
		||||
@ -296,10 +296,11 @@ describe('Marketplace page', () => {
 | 
			
		||||
    expect(newestOption).toBeVisible();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('sort by newest changes the url on click', () => {
 | 
			
		||||
  it('changes the url on sort option select', () => {
 | 
			
		||||
    render(App);
 | 
			
		||||
    const sortButton = screen.getByRole('button', { name: /Sort by/i });
 | 
			
		||||
    fireEvent.click(sortButton);
 | 
			
		||||
    fireEvent.mouseDown(sortButton);
 | 
			
		||||
 | 
			
		||||
    const newestOption = screen.getByRole('option', { name: 'Newest' });
 | 
			
		||||
    fireEvent.click(newestOption);
 | 
			
		||||
    expect(history.location.search).toEqual('?npmPackageType=provider&sort=submissionDate:desc');
 | 
			
		||||
 | 
			
		||||
@ -280,9 +280,10 @@
 | 
			
		||||
  "admin.pages.MarketPlacePage.tab-group.label": "Plugins and Providers for Strapi",
 | 
			
		||||
  "admin.pages.MarketPlacePage.missingPlugin.title": "Missing a plugin?",
 | 
			
		||||
  "admin.pages.MarketPlacePage.missingPlugin.description": "Tell us what plugin you are looking for and we'll let our community plugin developers know in case they are in search for inspiration!",
 | 
			
		||||
  "admin.pages.MarketPlacePage.sort.sortBy": "Sort by",
 | 
			
		||||
  "admin.pages.MarketPlacePage.sort.alphabetical": "Alphabetical order",
 | 
			
		||||
  "admin.pages.MarketPlacePage.sort.newest": "Newest",
 | 
			
		||||
  "admin.pages.MarketPlacePage.sort.alphabetical.selected": "Sort by alphabetical order",
 | 
			
		||||
  "admin.pages.MarketPlacePage.sort.newest.selected": "Sort by newest",
 | 
			
		||||
  "admin.pages.MarketPlacePage.filters.collections": "Collections",
 | 
			
		||||
  "admin.pages.MarketPlacePage.filters.collectionsSelected": "{count, plural, =0 {No collections} one {# collection} other {# collections}} selected",
 | 
			
		||||
  "admin.pages.MarketPlacePage.filters.categories": "Categories",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user