Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DS-497] - Create dropdown input component 🚀 #586

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
65 changes: 65 additions & 0 deletions packages/doc/content/components/components/dropdowninput/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
---
title: 'Dropdown Input'
metaTitle: 'Dropdown Input'
metaDescription: 'Dropdown Input Component'
---

import { FlagsIcons } from '@gympass/yoga-icons';

# Dropdown Input

Allows the user to choose a country and add secondary information. (Company name for example)

### Usage

```javascript state

render(() => {
const [value, setValue] = useState('');
const [selectedCountry, setSelectedCountry] = useState({ icon: FlagsIcons.FlagBrazil, name: 'Brazil', id: 'BR' });

function onClear() {
setValue('');
}

function onChange(event) {
const { value } = event.target;
setValue(value);
}

function onChangeSelectedCountry(selected) {
setValue('');
setSelectedCountry(selected);
}

return (
<DropdownInput
placeholder="Company Name"
value={value}
onChange={onChange}
onClear={onClear}
selectedCountry={selectedCountry}
onChangeSelectedCountry={onChangeSelectedCountry}
countries={[
{ icon: FlagsIcons.FlagBrazil, name: 'Brazil', id: 'BR' },
{ icon: FlagsIcons.FlagUS, name: 'United States', id: 'US' },
{ icon: FlagsIcons.FlagGermany, name: 'Germany', id: 'DE' },
{ icon: FlagsIcons.FlagMexico, name: 'Mexico', id: 'MX' },
{ icon: FlagsIcons.FlagItaly, name: 'Italy', id: 'IT' },
{ icon: FlagsIcons.FlagUK, name: 'United Kingdom', id: 'UK' },
{ icon: FlagsIcons.FlagSpain, name: 'Spain', id: 'ES' },
{ icon: FlagsIcons.FlagArgentina, name: 'Argentina', id: 'AR' },
{ icon: FlagsIcons.FlagChile, name: 'Chile', id: 'CL' },
{ icon: FlagsIcons.FlagNetherlands, name: 'Netherlands', id: 'NL' },
{ icon: FlagsIcons.FlagPortugal, name: 'Portugal', id: 'PT' },
{ icon: FlagsIcons.FlagIreland, name: 'Ireland', id: 'IE' },
{ icon: FlagsIcons.FlagUruguay, name: 'Uruguay', id: 'UY' },
]}
/>
);
});
```

### Props

<PropsTable component="DropdownInput" platform="web" />
3 changes: 3 additions & 0 deletions packages/yoga/src/DropdownInput/DropdownInput.theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const DropdownInput = () => ({});

export default DropdownInput;
3 changes: 3 additions & 0 deletions packages/yoga/src/DropdownInput/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import DropdownInput from './web';

export default DropdownInput;
262 changes: 262 additions & 0 deletions packages/yoga/src/DropdownInput/web/DropdownInput.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
import React, { useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { theme, Box, Icon, Text } from '@gympass/yoga';
import styled, { css } from 'styled-components';
import { Check, ChevronUp, ChevronDown, Close } from '@gympass/yoga-icons';
import { useOnClickOutside } from '../../hooks';

const Wrapper = styled.div`
position: relative;
`;

const Container = styled.div`
display: flex;
border-radius: ${theme.radii.small}px;
justify-content: flex-start;
align-items: center;
background: #fff;
border: ${theme.borders.small}px solid #ccc;
box-sizing: border-box;
background: #fff;
height: 52px;
margin: ${theme.spacing.xxsmall}px 0 0;
transition: all 0.1s ease-in-out;
width: 320px;
${({ showDropDown }) =>
showDropDown &&
css`
border-top: ${theme.borders.small}px solid #ccc;
border-left: ${theme.borders.small}px solid #ccc;
border-right: ${theme.borders.small}px solid #ccc;
border-radius: ${theme.radii.small}px ${theme.radii.small}px 0px 0px;
border-bottom: 0;
`}
`;

const Clear = styled.button`
background: none;
border: none;
cursor: pointer;
display: none;
outline: none;
padding: ${theme.spacing.medium}px;
`;

const Input = styled.input`
background: none;
border: none;
color: ${theme.colors.text.primary};
flex: 1;
font-family: ${({ theme: { yoga } }) => yoga.baseFont.family};
font-size: ${theme.fontSizes.small}px;
font-style: normal;
matheushdsbr marked this conversation as resolved.
Show resolved Hide resolved
font-weight: ${theme.fontWeights.regular};
height: 100%;
letter-spacing: 0px;
matheushdsbr marked this conversation as resolved.
Show resolved Hide resolved
line-height: ${theme.lineHeights.small}px;
outline: none;
text-align: left;
padding-left: ${theme.spacing.small}px;

&::placeholder {
color: ${theme.colors.text.secondary};
}

${({ value }) =>
value &&
css`
+ ${Clear} {
display: block;
}
`}
`;

const Divisor = styled.div`
matheushdsbr marked this conversation as resolved.
Show resolved Hide resolved
width: 1px;
height: 28px;
background: ${theme.colors.elements.lineAndBorders};
`;

const ContainerDropDown = styled.ul`
margin: 0;
height: 272px;
width: ${props => `${props.width}px`};
overflow-y: scroll;
border-left: 1px solid #ccc;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
list-style: none;
padding: 0 16px 0 16px;
matheushdsbr marked this conversation as resolved.
Show resolved Hide resolved
transition: all 0.1s ease-in-out;
border-radius: 0px 0px ${theme.radii.small}px ${theme.radii.small}px;
background: #fff;
position: absolute;
top: ${props => `${props.position}px`};
z-index: 9999;
`;

const ButtonDropDown = styled.button`
display: flex;
align-items: center;
width: auto;
background: none;
border: none;
outline: none;
cursor: pointer;
padding: 0;
height: 100%;
`;

const ItemDropDown = styled.li`
display: flex;
align-items: center;
justify-content: space-between;
align-content: center;
width: 100%;
height: 72px;
matheushdsbr marked this conversation as resolved.
Show resolved Hide resolved
cursor: pointer;
font-size: 16px;
matheushdsbr marked this conversation as resolved.
Show resolved Hide resolved
font-weight: 500;

div {
display: flex;
align-items: center;
}

&:last-child {
border: none;
}
`;

const ContainerName = styled.div``;
matheushdsbr marked this conversation as resolved.
Show resolved Hide resolved

const DropdownInput = ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of this component is too the generic, considering we have this one:

https://gympass.github.io/yoga/components/dropdown

So, WDYT of?

Suggested change
const DropdownInput = ({
const CountryDropdownInput = ({

placeholder,
onClear,
value,
selectedCountry,
onChangeSelectedCountry,
countries,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you choose the path of specifying this components in only to work with countries this list can be static instead of a prop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way, whoever uses the component will not be able to specify the countries that should be displayed.
What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently gympass only supports the countries we have flags for. If you want to keep this property, then you should make it optional and have a default list...

...other
}) => {
const [showDropDown, setShowDropDown] = useState(false);
const inputRef = useRef(null);
const DropDownRef = useRef(null);
matheushdsbr marked this conversation as resolved.
Show resolved Hide resolved
const containerRef = useRef(null);

const toggleShowDropDown = () => {
setShowDropDown(!showDropDown);
};

useOnClickOutside(DropDownRef, e => {
if (e.target.id !== 'button-drop-down') {
toggleShowDropDown();
}
});

const changeSelectedCountry = selected => () => {
onChangeSelectedCountry(selected);
toggleShowDropDown();
inputRef.current.focus();
};

const renderListCountries = () => {
return countries.map(country => {
const isCountrySelected = selectedCountry.id === country.id;

return (
<ItemDropDown key={country.id} onClick={changeSelectedCountry(country)}>
<ContainerName>
<Box>
<Icon
as={country.icon}
width="medium"
height="medium"
marginRight={4}
/>
</Box>
<Text.Small
fw={isCountrySelected ? 'medium' : 'regular'}
color={isCountrySelected ? 'primary' : 'secondary'}
>
{country.name}
</Text.Small>
</ContainerName>
{isCountrySelected && (
<Icon as={Check} width="medium" height="medium" fill="vibin" />
)}
</ItemDropDown>
);
});
};

return (
<Wrapper>
<Container ref={containerRef} showDropDown={showDropDown}>
<ButtonDropDown
id="button-drop-down"
type="button"
onClick={toggleShowDropDown}
>
<Box>
<Icon
marginRight={2}
marginLeft={4}
as={selectedCountry.icon}
width="medium"
height="medium"
/>
</Box>
<Icon
as={showDropDown ? ChevronUp : ChevronDown}
size="small"
cursor="pointer"
mr="xsmall"
stroke="deep"
/>
<Divisor />
</ButtonDropDown>

<Input
data-testid="input"
ref={inputRef}
value={value}
placeholder={placeholder}
{...other}
/>

<Clear onClick={onClear} type="button">
<Icon as={Close} size="small" fill="medium" />
</Clear>
</Container>

{showDropDown && (
<ContainerDropDown
ref={DropDownRef}
position={containerRef.current.offsetTop + 50}
width={containerRef.current.offsetWidth}
data-testid="dropdown"
>
{renderListCountries()}
</ContainerDropDown>
)}
</Wrapper>
);
};

DropdownInput.propTypes = {
placeholder: PropTypes.string.isRequired,
countries: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
icon: PropTypes.elementType.isRequired,
}),
).isRequired,
onClear: PropTypes.func.isRequired,
value: PropTypes.string.isRequired,
selectedCountry: PropTypes.string.isRequired,
onChangeSelectedCountry: PropTypes.func.isRequired,
};

export default DropdownInput;
23 changes: 23 additions & 0 deletions packages/yoga/src/DropdownInput/web/DropdownInput.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { render } from '@testing-library/react';

import { FlagsIcons } from '@gympass/yoga-icons';
import { ThemeProvider, DropdownInput } from '../..';

describe('<DropdownInput />', () => {
it.only('should match snapshot', () => {
const { container } = render(
<DropdownInput
placeholder="Company Name"
selectedCountry={{
icon: FlagsIcons.FlagBrazil,
name: 'Brazil',
id: 'BR',
}}
/>,
{ wrapper: ThemeProvider },
);

expect(container).toMatchSnapshot();
});
});
Loading