Created common Dropdown component for style consistency

This commit is contained in:
Alejandro Celaya
2020-12-25 10:29:25 +01:00
parent 81d24432a9
commit 6be3a1223f
7 changed files with 92 additions and 116 deletions

View File

@@ -1,31 +1,12 @@
@import '../utils/mixins/vertical-align'; @import '../utils/mixins/vertical-align';
.domains-dropdown__toggle-btn.domains-dropdown__toggle-btn,
.domains-dropdown__toggle-btn.domains-dropdown__toggle-btn:hover,
.domains-dropdown__toggle-btn.domains-dropdown__toggle-btn:active {
text-align: left;
color: #6c757d;
border-color: #ced4da;
background-color: white;
}
.domains-dropdown__toggle-btn--active.domains-dropdown__toggle-btn--active, .domains-dropdown__toggle-btn--active.domains-dropdown__toggle-btn--active,
.domains-dropdown__toggle-btn--active.domains-dropdown__toggle-btn--active:hover, .domains-dropdown__toggle-btn--active.domains-dropdown__toggle-btn--active:hover,
.domains-dropdown__toggle-btn--active.domains-dropdown__toggle-btn--active:active { .domains-dropdown__toggle-btn--active.domains-dropdown__toggle-btn--active:active {
color: #495057; color: #495057 !important;
} }
.domains-dropdown__back-btn.domains-dropdown__back-btn, .domains-dropdown__back-btn.domains-dropdown__back-btn,
.domains-dropdown__back-btn.domains-dropdown__back-btn:hover { .domains-dropdown__back-btn.domains-dropdown__back-btn:hover {
border-color: #ced4da; border-color: #ced4da;
} }
.domains-dropdown__toggle-btn.domains-dropdown__toggle-btn::after {
right: .75rem;
@include vertical-align();
}
.domains-dropdown__menu {
width: 100%;
}

View File

@@ -1,20 +1,10 @@
import { useEffect } from 'react'; import { useEffect } from 'react';
import { import { Button, DropdownItem, Input, InputGroup, InputGroupAddon, UncontrolledTooltip } from 'reactstrap';
Button,
Dropdown,
DropdownItem,
DropdownMenu,
DropdownToggle,
Input,
InputGroup,
InputGroupAddon,
UncontrolledTooltip,
} from 'reactstrap';
import { InputProps } from 'reactstrap/lib/Input'; import { InputProps } from 'reactstrap/lib/Input';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUndo } from '@fortawesome/free-solid-svg-icons'; import { faUndo } from '@fortawesome/free-solid-svg-icons';
import { isEmpty, pipe } from 'ramda'; import { isEmpty, pipe } from 'ramda';
import classNames from 'classnames'; import { Dropdown } from '../utils/Dropdown';
import { useToggle } from '../utils/helpers/hooks'; import { useToggle } from '../utils/helpers/hooks';
import { DomainsList } from './reducers/domainsList'; import { DomainsList } from './reducers/domainsList';
import './DomainSelector.scss'; import './DomainSelector.scss';
@@ -31,7 +21,6 @@ interface DomainSelectorConnectProps extends DomainSelectorProps {
export const DomainSelector = ({ listDomains, value, domainsList, onChange }: DomainSelectorConnectProps) => { export const DomainSelector = ({ listDomains, value, domainsList, onChange }: DomainSelectorConnectProps) => {
const [ inputDisplayed,, showInput, hideInput ] = useToggle(); const [ inputDisplayed,, showInput, hideInput ] = useToggle();
const [ isDropdownOpen, toggleDropdown ] = useToggle();
const { domains } = domainsList; const { domains } = domainsList;
const valueIsEmpty = isEmpty(value); const valueIsEmpty = isEmpty(value);
const unselectDomain = () => onChange(''); const unselectDomain = () => onChange('');
@@ -63,18 +52,10 @@ export const DomainSelector = ({ listDomains, value, domainsList, onChange }: Do
</InputGroupAddon> </InputGroupAddon>
</InputGroup> </InputGroup>
) : ( ) : (
<Dropdown isOpen={isDropdownOpen} toggle={toggleDropdown}> <Dropdown
<DropdownToggle text={valueIsEmpty ? 'Domain' : `Domain: ${value}`}
caret className={!valueIsEmpty ? 'domains-dropdown__toggle-btn--active' : ''}
className={classNames(
'domains-dropdown__toggle-btn btn-block',
{ 'domains-dropdown__toggle-btn--active': !valueIsEmpty },
)}
> >
{valueIsEmpty && <>Domain</>}
{!valueIsEmpty && <>Domain: {value}</>}
</DropdownToggle>
<DropdownMenu className="domains-dropdown__menu">
{domains.map(({ domain, isDefault }) => ( {domains.map(({ domain, isDefault }) => (
<DropdownItem <DropdownItem
key={domain} key={domain}
@@ -89,7 +70,6 @@ export const DomainSelector = ({ listDomains, value, domainsList, onChange }: Do
<DropdownItem onClick={pipe(unselectDomain, showInput)}> <DropdownItem onClick={pipe(unselectDomain, showInput)}>
<i>New domain</i> <i>New domain</i>
</DropdownItem> </DropdownItem>
</DropdownMenu>
</Dropdown> </Dropdown>
); );
}; };

19
src/utils/Dropdown.scss Normal file
View File

@@ -0,0 +1,19 @@
@import '../utils/mixins/vertical-align';
.dropdown__btn.dropdown__btn,
.dropdown__btn.dropdown__btn:not(:disabled):not(.disabled).active,
.dropdown__btn.dropdown__btn:not(:disabled):not(.disabled):active,
.dropdown__btn.dropdown__btn:not(:disabled):not(.disabled):focus,
.dropdown__btn.dropdown__btn:not(:disabled):not(.disabled):hover,
.show > .dropdown__btn.dropdown__btn.dropdown-toggle {
color: #6c757d;
background-color: white;
text-align: left;
border-color: rgba(0, 0, 0, .125);
}
.dropdown__btn.dropdown__btn:after {
@include vertical-align();
right: .75rem;
}

21
src/utils/Dropdown.tsx Normal file
View File

@@ -0,0 +1,21 @@
import { FC } from 'react';
import { Dropdown as BsDropdown, DropdownMenu, DropdownToggle } from 'reactstrap';
import { useToggle } from './helpers/hooks';
import './Dropdown.scss';
interface DropdownProps {
text: string;
disabled?: boolean;
className?: string;
}
export const Dropdown: FC<DropdownProps> = ({ text, disabled = false, className, children }) => {
const [ isOpen, toggle ] = useToggle();
return (
<BsDropdown isOpen={isOpen} toggle={toggle} disabled={disabled}>
<DropdownToggle caret className={`dropdown__btn btn-block ${className}`} color="primary">{text}</DropdownToggle>
<DropdownMenu className="w-100">{children}</DropdownMenu>
</BsDropdown>
);
};

View File

@@ -1,19 +0,0 @@
@import '../../utils/mixins/vertical-align';
.date-range-selector__btn.date-range-selector__btn,
.date-range-selector__btn.date-range-selector__btn:not(:disabled):not(.disabled).active,
.date-range-selector__btn.date-range-selector__btn:not(:disabled):not(.disabled):active,
.date-range-selector__btn.date-range-selector__btn:not(:disabled):not(.disabled):focus,
.date-range-selector__btn.date-range-selector__btn:not(:disabled):not(.disabled):hover,
.show > .date-range-selector__btn.date-range-selector__btn.dropdown-toggle {
color: #6c757d;
background-color: white;
text-align: left;
border-color: rgba(0, 0, 0, .125);
}
.date-range-selector__btn.date-range-selector__btn:after {
@include vertical-align();
right: .75rem;
}

View File

@@ -1,6 +1,6 @@
import { useState } from 'react'; import { useState } from 'react';
import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap'; import { DropdownItem } from 'reactstrap';
import { useToggle } from '../helpers/hooks'; import { Dropdown } from '../Dropdown';
import { import {
DateInterval, DateInterval,
DateRange, DateRange,
@@ -10,7 +10,6 @@ import {
rangeIsInterval, rangeIsInterval,
} from './types'; } from './types';
import DateRangeRow from './DateRangeRow'; import DateRangeRow from './DateRangeRow';
import './DateRangeSelector.scss';
export interface DateRangeSelectorProps { export interface DateRangeSelectorProps {
initialDateRange?: DateInterval | DateRange; initialDateRange?: DateInterval | DateRange;
@@ -20,9 +19,8 @@ export interface DateRangeSelectorProps {
} }
export const DateRangeSelector = ( export const DateRangeSelector = (
{ onDatesChange, initialDateRange, defaultText, disabled = false }: DateRangeSelectorProps, { onDatesChange, initialDateRange, defaultText, disabled }: DateRangeSelectorProps,
) => { ) => {
const [ isOpen, toggle ] = useToggle();
const [ activeInterval, setActiveInterval ] = useState( const [ activeInterval, setActiveInterval ] = useState(
rangeIsInterval(initialDateRange) ? initialDateRange : undefined, rangeIsInterval(initialDateRange) ? initialDateRange : undefined,
); );
@@ -41,11 +39,7 @@ export const DateRangeSelector = (
}; };
return ( return (
<Dropdown isOpen={isOpen} toggle={toggle} disabled={disabled}> <Dropdown disabled={disabled} text={rangeOrIntervalToString(activeInterval ?? activeDateRange) ?? defaultText}>
<DropdownToggle caret className="date-range-selector__btn btn-block" color="primary">
{rangeOrIntervalToString(activeInterval ?? activeDateRange) ?? defaultText}
</DropdownToggle>
<DropdownMenu className="w-100">
<DropdownItem <DropdownItem
active={activeInterval === undefined && dateRangeIsEmpty(activeDateRange)} active={activeInterval === undefined && dateRangeIsEmpty(activeDateRange)}
onClick={updateInterval(undefined)} onClick={updateInterval(undefined)}
@@ -69,7 +63,6 @@ export const DateRangeSelector = (
onEndDateChange={(endDate) => updateDateRange({ ...activeDateRange, endDate })} onEndDateChange={(endDate) => updateDateRange({ ...activeDateRange, endDate })}
/> />
</DropdownItem> </DropdownItem>
</DropdownMenu>
</Dropdown> </Dropdown>
); );
}; };

View File

@@ -1,9 +1,10 @@
import { shallow, ShallowWrapper } from 'enzyme'; import { shallow, ShallowWrapper } from 'enzyme';
import { Mock } from 'ts-mockery'; import { Mock } from 'ts-mockery';
import { DropdownItem, DropdownMenu, InputGroup } from 'reactstrap'; import { DropdownItem, InputGroup } from 'reactstrap';
import { DomainSelector } from '../../src/domains/DomainSelector'; import { DomainSelector } from '../../src/domains/DomainSelector';
import { DomainsList } from '../../src/domains/reducers/domainsList'; import { DomainsList } from '../../src/domains/reducers/domainsList';
import { ShlinkDomain } from '../../src/api/types'; import { ShlinkDomain } from '../../src/api/types';
import { Dropdown } from '../../src/utils/Dropdown';
describe('<DomainSelector />', () => { describe('<DomainSelector />', () => {
let wrapper: ShallowWrapper; let wrapper: ShallowWrapper;
@@ -23,7 +24,7 @@ describe('<DomainSelector />', () => {
it('shows dropdown by default', () => { it('shows dropdown by default', () => {
const input = wrapper.find(InputGroup); const input = wrapper.find(InputGroup);
const dropdown = wrapper.find(DropdownMenu); const dropdown = wrapper.find(Dropdown);
expect(input).toHaveLength(0); expect(input).toHaveLength(0);
expect(dropdown).toHaveLength(1); expect(dropdown).toHaveLength(1);
@@ -33,10 +34,10 @@ describe('<DomainSelector />', () => {
it('allows to toggle between dropdown and input', () => { it('allows to toggle between dropdown and input', () => {
wrapper.find(DropdownItem).last().simulate('click'); wrapper.find(DropdownItem).last().simulate('click');
expect(wrapper.find(InputGroup)).toHaveLength(1); expect(wrapper.find(InputGroup)).toHaveLength(1);
expect(wrapper.find(DropdownMenu)).toHaveLength(0); expect(wrapper.find(Dropdown)).toHaveLength(0);
wrapper.find('.domains-dropdown__back-btn').simulate('click'); wrapper.find('.domains-dropdown__back-btn').simulate('click');
expect(wrapper.find(InputGroup)).toHaveLength(0); expect(wrapper.find(InputGroup)).toHaveLength(0);
expect(wrapper.find(DropdownMenu)).toHaveLength(1); expect(wrapper.find(Dropdown)).toHaveLength(1);
}); });
}); });