mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-10 18:27:25 +03:00
Created common Dropdown component for style consistency
This commit is contained in:
parent
81d24432a9
commit
6be3a1223f
7 changed files with 92 additions and 116 deletions
|
@ -1,31 +1,12 @@
|
|||
@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:hover,
|
||||
.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:hover {
|
||||
border-color: #ced4da;
|
||||
}
|
||||
|
||||
.domains-dropdown__toggle-btn.domains-dropdown__toggle-btn::after {
|
||||
right: .75rem;
|
||||
|
||||
@include vertical-align();
|
||||
}
|
||||
|
||||
.domains-dropdown__menu {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -1,20 +1,10 @@
|
|||
import { useEffect } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputGroupAddon,
|
||||
UncontrolledTooltip,
|
||||
} from 'reactstrap';
|
||||
import { Button, DropdownItem, Input, InputGroup, InputGroupAddon, UncontrolledTooltip } from 'reactstrap';
|
||||
import { InputProps } from 'reactstrap/lib/Input';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faUndo } from '@fortawesome/free-solid-svg-icons';
|
||||
import { isEmpty, pipe } from 'ramda';
|
||||
import classNames from 'classnames';
|
||||
import { Dropdown } from '../utils/Dropdown';
|
||||
import { useToggle } from '../utils/helpers/hooks';
|
||||
import { DomainsList } from './reducers/domainsList';
|
||||
import './DomainSelector.scss';
|
||||
|
@ -31,7 +21,6 @@ interface DomainSelectorConnectProps extends DomainSelectorProps {
|
|||
|
||||
export const DomainSelector = ({ listDomains, value, domainsList, onChange }: DomainSelectorConnectProps) => {
|
||||
const [ inputDisplayed,, showInput, hideInput ] = useToggle();
|
||||
const [ isDropdownOpen, toggleDropdown ] = useToggle();
|
||||
const { domains } = domainsList;
|
||||
const valueIsEmpty = isEmpty(value);
|
||||
const unselectDomain = () => onChange('');
|
||||
|
@ -63,33 +52,24 @@ export const DomainSelector = ({ listDomains, value, domainsList, onChange }: Do
|
|||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
) : (
|
||||
<Dropdown isOpen={isDropdownOpen} toggle={toggleDropdown}>
|
||||
<DropdownToggle
|
||||
caret
|
||||
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 }) => (
|
||||
<DropdownItem
|
||||
key={domain}
|
||||
active={value === domain || isDefault && valueIsEmpty}
|
||||
onClick={() => onChange(domain)}
|
||||
>
|
||||
{domain}
|
||||
{isDefault && <span className="float-right text-muted">default</span>}
|
||||
</DropdownItem>
|
||||
))}
|
||||
<DropdownItem divider />
|
||||
<DropdownItem onClick={pipe(unselectDomain, showInput)}>
|
||||
<i>New domain</i>
|
||||
<Dropdown
|
||||
text={valueIsEmpty ? 'Domain' : `Domain: ${value}`}
|
||||
className={!valueIsEmpty ? 'domains-dropdown__toggle-btn--active' : ''}
|
||||
>
|
||||
{domains.map(({ domain, isDefault }) => (
|
||||
<DropdownItem
|
||||
key={domain}
|
||||
active={value === domain || isDefault && valueIsEmpty}
|
||||
onClick={() => onChange(domain)}
|
||||
>
|
||||
{domain}
|
||||
{isDefault && <span className="float-right text-muted">default</span>}
|
||||
</DropdownItem>
|
||||
</DropdownMenu>
|
||||
))}
|
||||
<DropdownItem divider />
|
||||
<DropdownItem onClick={pipe(unselectDomain, showInput)}>
|
||||
<i>New domain</i>
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
|
19
src/utils/Dropdown.scss
Normal file
19
src/utils/Dropdown.scss
Normal 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
21
src/utils/Dropdown.tsx
Normal 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>
|
||||
);
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { useState } from 'react';
|
||||
import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
|
||||
import { useToggle } from '../helpers/hooks';
|
||||
import { DropdownItem } from 'reactstrap';
|
||||
import { Dropdown } from '../Dropdown';
|
||||
import {
|
||||
DateInterval,
|
||||
DateRange,
|
||||
|
@ -10,7 +10,6 @@ import {
|
|||
rangeIsInterval,
|
||||
} from './types';
|
||||
import DateRangeRow from './DateRangeRow';
|
||||
import './DateRangeSelector.scss';
|
||||
|
||||
export interface DateRangeSelectorProps {
|
||||
initialDateRange?: DateInterval | DateRange;
|
||||
|
@ -20,9 +19,8 @@ export interface DateRangeSelectorProps {
|
|||
}
|
||||
|
||||
export const DateRangeSelector = (
|
||||
{ onDatesChange, initialDateRange, defaultText, disabled = false }: DateRangeSelectorProps,
|
||||
{ onDatesChange, initialDateRange, defaultText, disabled }: DateRangeSelectorProps,
|
||||
) => {
|
||||
const [ isOpen, toggle ] = useToggle();
|
||||
const [ activeInterval, setActiveInterval ] = useState(
|
||||
rangeIsInterval(initialDateRange) ? initialDateRange : undefined,
|
||||
);
|
||||
|
@ -41,35 +39,30 @@ export const DateRangeSelector = (
|
|||
};
|
||||
|
||||
return (
|
||||
<Dropdown isOpen={isOpen} toggle={toggle} disabled={disabled}>
|
||||
<DropdownToggle caret className="date-range-selector__btn btn-block" color="primary">
|
||||
{rangeOrIntervalToString(activeInterval ?? activeDateRange) ?? defaultText}
|
||||
</DropdownToggle>
|
||||
<DropdownMenu className="w-100">
|
||||
<DropdownItem
|
||||
active={activeInterval === undefined && dateRangeIsEmpty(activeDateRange)}
|
||||
onClick={updateInterval(undefined)}
|
||||
>
|
||||
{defaultText}
|
||||
</DropdownItem>
|
||||
<DropdownItem divider />
|
||||
{([ 'today', 'yesterday', 'last7Days', 'last30Days', 'last90Days', 'last180days', 'last365Days' ] as DateInterval[]).map(
|
||||
(interval) => (
|
||||
<DropdownItem key={interval} active={activeInterval === interval} onClick={updateInterval(interval)}>
|
||||
{rangeOrIntervalToString(interval)}
|
||||
</DropdownItem>
|
||||
),
|
||||
)}
|
||||
<DropdownItem divider />
|
||||
<DropdownItem header>Custom:</DropdownItem>
|
||||
<DropdownItem text>
|
||||
<DateRangeRow
|
||||
{...activeDateRange}
|
||||
onStartDateChange={(startDate) => updateDateRange({ ...activeDateRange, startDate })}
|
||||
onEndDateChange={(endDate) => updateDateRange({ ...activeDateRange, endDate })}
|
||||
/>
|
||||
</DropdownItem>
|
||||
</DropdownMenu>
|
||||
<Dropdown disabled={disabled} text={rangeOrIntervalToString(activeInterval ?? activeDateRange) ?? defaultText}>
|
||||
<DropdownItem
|
||||
active={activeInterval === undefined && dateRangeIsEmpty(activeDateRange)}
|
||||
onClick={updateInterval(undefined)}
|
||||
>
|
||||
{defaultText}
|
||||
</DropdownItem>
|
||||
<DropdownItem divider />
|
||||
{([ 'today', 'yesterday', 'last7Days', 'last30Days', 'last90Days', 'last180days', 'last365Days' ] as DateInterval[]).map(
|
||||
(interval) => (
|
||||
<DropdownItem key={interval} active={activeInterval === interval} onClick={updateInterval(interval)}>
|
||||
{rangeOrIntervalToString(interval)}
|
||||
</DropdownItem>
|
||||
),
|
||||
)}
|
||||
<DropdownItem divider />
|
||||
<DropdownItem header>Custom:</DropdownItem>
|
||||
<DropdownItem text>
|
||||
<DateRangeRow
|
||||
{...activeDateRange}
|
||||
onStartDateChange={(startDate) => updateDateRange({ ...activeDateRange, startDate })}
|
||||
onEndDateChange={(endDate) => updateDateRange({ ...activeDateRange, endDate })}
|
||||
/>
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import { shallow, ShallowWrapper } from 'enzyme';
|
||||
import { Mock } from 'ts-mockery';
|
||||
import { DropdownItem, DropdownMenu, InputGroup } from 'reactstrap';
|
||||
import { DropdownItem, InputGroup } from 'reactstrap';
|
||||
import { DomainSelector } from '../../src/domains/DomainSelector';
|
||||
import { DomainsList } from '../../src/domains/reducers/domainsList';
|
||||
import { ShlinkDomain } from '../../src/api/types';
|
||||
import { Dropdown } from '../../src/utils/Dropdown';
|
||||
|
||||
describe('<DomainSelector />', () => {
|
||||
let wrapper: ShallowWrapper;
|
||||
|
@ -23,7 +24,7 @@ describe('<DomainSelector />', () => {
|
|||
|
||||
it('shows dropdown by default', () => {
|
||||
const input = wrapper.find(InputGroup);
|
||||
const dropdown = wrapper.find(DropdownMenu);
|
||||
const dropdown = wrapper.find(Dropdown);
|
||||
|
||||
expect(input).toHaveLength(0);
|
||||
expect(dropdown).toHaveLength(1);
|
||||
|
@ -33,10 +34,10 @@ describe('<DomainSelector />', () => {
|
|||
it('allows to toggle between dropdown and input', () => {
|
||||
wrapper.find(DropdownItem).last().simulate('click');
|
||||
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');
|
||||
expect(wrapper.find(InputGroup)).toHaveLength(0);
|
||||
expect(wrapper.find(DropdownMenu)).toHaveLength(1);
|
||||
expect(wrapper.find(Dropdown)).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue