From 71a3ec91a2726091baff083efec3311be6e322e6 Mon Sep 17 00:00:00 2001 From: Ilana Barbosa Date: Wed, 13 Mar 2024 10:26:54 -0300 Subject: [PATCH] feat(multi-select): add suffix and selection limit option (#482) * feat(multi-select): add suffix * feat(multi-select): add selection limit option --- .../MultiSelect/MultiSelect.stories.tsx | 26 ++++++++++++++ .../MultiSelect/MultiSelect.test.tsx | 34 +++++++++++++++++- src/components/MultiSelect/MultiSelect.tsx | 5 ++- .../MultiSelect/MultiSelectFetchable.tsx | 15 ++++++-- .../MultiSelect/MultiSelectStatic.tsx | 36 ++++++++++++------- 5 files changed, 100 insertions(+), 16 deletions(-) diff --git a/src/components/MultiSelect/MultiSelect.stories.tsx b/src/components/MultiSelect/MultiSelect.stories.tsx index d3fe5074..e76287a8 100644 --- a/src/components/MultiSelect/MultiSelect.stories.tsx +++ b/src/components/MultiSelect/MultiSelect.stories.tsx @@ -6,12 +6,15 @@ import { MultiSelect } from './MultiSelect' import { createPageExport } from '../../utils/storybook' +import { IoIosArrowDown } from 'react-icons/io' + const aiqProps = [ 'maxWidth', 'filters', 'onChange', 'value', 'items', + 'selectedItemsLimit', 'isLoading', 'isFetchable', 'placeholder', @@ -31,6 +34,7 @@ export default createPageExport(MultiSelect, 'MultiSelect', aiqProps, { filters: { control: 'object' }, value: { control: 'object' }, items: { control: 'object' }, + selectedItemsLimit: { control: 'number' }, isLoading: { control: 'boolean' }, isFetchable: { control: 'number' }, placeholder: { control: 'text' }, @@ -160,3 +164,25 @@ export const DisabledWithoutElements = (args): ReactElement => { DisabledWithoutElements.args = { disabled: true } + +export const WithSuffix = (args): ReactElement => { + const [value, setValue] = useState([items[0]]) + + function handleChangeMultiSelect({ selectedItems }) { + setValue(selectedItems) + } + + return ( + + } + errorForm={value.length === 0} + {...args} + /> + + ) +} diff --git a/src/components/MultiSelect/MultiSelect.test.tsx b/src/components/MultiSelect/MultiSelect.test.tsx index 0eb6ceaf..88925c56 100644 --- a/src/components/MultiSelect/MultiSelect.test.tsx +++ b/src/components/MultiSelect/MultiSelect.test.tsx @@ -1,8 +1,9 @@ -import React, { useState } from 'react' +import React from 'react' import { fireEvent } from '@testing-library/react' import { MultiSelect } from '../MultiSelect' import { render } from '../utils/test/render' +import { IoIosArrowDown } from 'react-icons/io' const greenColor = '#6EC531' @@ -149,4 +150,35 @@ describe('MultiSelect', () => { const BadgeItem = getByTestId('select-selected-item') expect(BadgeItem).toHaveStyle({ backgroundColor: greenColor }) }) + + it('should show suffix when prop is provided', () => { + const { container } = render( + } + /> + ) + + const suffix = container.querySelector('svg') + + expect(suffix).toBeInTheDocument() + }) + + it('should show limit message when the selected items limit is reached', () => { + const { container } = render( + } + selectedItemsLimit={2} + /> + ) + + const list = container.querySelectorAll('li') + const firstItemText = list[0].textContent + + expect(list.length).toBe(1) + expect(firstItemText).toContain('quantidade máxima atingida') + }) }) diff --git a/src/components/MultiSelect/MultiSelect.tsx b/src/components/MultiSelect/MultiSelect.tsx index 865f5b4f..12742943 100644 --- a/src/components/MultiSelect/MultiSelect.tsx +++ b/src/components/MultiSelect/MultiSelect.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { ReactNode } from 'react' import { MultiSelectFetchable } from './MultiSelectFetchable' import { MultiSelectStatic } from './MultiSelectStatic' @@ -21,7 +21,10 @@ export interface Props { onChange?: any value?: Item[] items: Item[] + selectedItemsLimit?: number + limitMessage?: string isLoading?: boolean + suffix?: ReactNode isFetchable?: boolean placeholder?: string loadingMessage?: string diff --git a/src/components/MultiSelect/MultiSelectFetchable.tsx b/src/components/MultiSelect/MultiSelectFetchable.tsx index 47dde7f5..3aaedeac 100644 --- a/src/components/MultiSelect/MultiSelectFetchable.tsx +++ b/src/components/MultiSelect/MultiSelectFetchable.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react' +import React, { ReactNode, useEffect, useRef, useState } from 'react' import styled from 'styled-components' import { MdClose } from 'react-icons/md' @@ -32,7 +32,10 @@ export interface Props { onChange?: any value?: Item[] items: Item[] + selectedItemsLimit?: number + limitMessage?: string isLoading?: boolean + suffix?: ReactNode placeholder?: string loadingMessage?: string emptyMessage?: string @@ -144,11 +147,14 @@ const SelectedItem = styled(Text)` export const MultiSelectFetchable: React.FC = ({ items, + selectedItemsLimit, + limitMessage = 'quantidade máxima atingida', maxWidth, filters = [], onChange, value = [], isLoading = false, + suffix, placeholder, loadingMessage = 'carregando...', emptyMessage = 'item não encontrado ou já adicionado', @@ -289,6 +295,8 @@ export const MultiSelectFetchable: React.FC = ({ } } + const hasReachedLimit = selectedItemsLimit === selectedItems?.length + return ( = ({ autoComplete='disabled' /> - {isLoading && } + {isLoading ? : suffix} = ({ !isDependent && !isLoading && !disabled && + !hasReachedLimit && getFilteredItems().map((item, index) => (
  • = ({ !isLoading && !isDependent && getFilteredItems().length === 0 &&
  • {emptyMessage}
  • } + + {hasReachedLimit &&
  • {limitMessage}
  • }
    diff --git a/src/components/MultiSelect/MultiSelectStatic.tsx b/src/components/MultiSelect/MultiSelectStatic.tsx index 7f61166b..680c5737 100644 --- a/src/components/MultiSelect/MultiSelectStatic.tsx +++ b/src/components/MultiSelect/MultiSelectStatic.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef, useEffect } from 'react' +import React, { useState, useRef, useEffect, ReactNode } from 'react' import styled from 'styled-components' import { MdClose } from 'react-icons/md' @@ -31,7 +31,9 @@ export interface Props { onChange?: any value?: Item[] items: Item[] - isLoading?: boolean + selectedItemsLimit?: number + limitMessage?: string + suffix?: ReactNode isFetchable?: boolean placeholder?: string errorMessage?: string @@ -169,11 +171,14 @@ const CustomText = styled(Text)` export const MultiSelectStatic: React.FC = ({ items, + selectedItemsLimit, + limitMessage = 'quantidade máxima atingida', maxWidth, filters = [], onChange, value = [], placeholder, + suffix, errorForm, errorMessage, emptyMessage = 'item não encontrado ou já adicionado', @@ -315,6 +320,17 @@ export const MultiSelectStatic: React.FC = ({ } } + const handleSelect = ({ e, item, index }) => { + if (selectedItems.indexOf(item) > -1 && removable) + onChange({ + selectedItems: selectedItems.filter(e => e.id !== item.id) + }) + else getItemProps({ item, index }).onClick(e) + setItemLimit(undefined) + } + + const hasReachedLimit = selectedItemsLimit === selectedItems?.length + return ( = ({ )} autoComplete='disabled' /> + + {suffix} = ({ {isOpen && !isDependent && !disabled && + !hasReachedLimit && getFilteredItems().map((item, index) => (
  • { - if (selectedItems.indexOf(item) > -1 && removable) - onChange({ - selectedItems: selectedItems.filter( - e => e.id !== item.id - ) - }) - else getItemProps({ item, index }).onClick(e) - setItemLimit(undefined) - }} + onClick={e => handleSelect({ e, item, index })} > {item.name}
  • @@ -515,6 +525,8 @@ export const MultiSelectStatic: React.FC = ({ {isOpen && !isDependent && getFilteredItems().length === 0 && (
  • {emptyMessage}
  • )} + + {hasReachedLimit &&
  • {limitMessage}
  • }