convert dropdown list component

This commit is contained in:
Ramón Souza 2021-11-09 18:09:37 +00:00
parent 9170acb662
commit 11897ad788
10 changed files with 197 additions and 597 deletions

View File

@ -1,8 +1,7 @@
import React, { Component, Children, cloneElement } from 'react'; import React, { Component, Children, cloneElement } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cx from 'classnames';
import KEY_CODES from '/imports/utils/keyCodes'; import KEY_CODES from '/imports/utils/keyCodes';
import { styles } from './styles'; import Styled from './styles';
import ListItem from './item/component'; import ListItem from './item/component';
import ListSeparator from './separator/component'; import ListSeparator from './separator/component';
import ListTitle from './title/component'; import ListTitle from './title/component';
@ -14,8 +13,8 @@ const propTypes = {
if (propValue[key].type !== ListItem if (propValue[key].type !== ListItem
&& propValue[key].type !== ListSeparator && propValue[key].type !== ListSeparator
&& propValue[key].type !== ListTitle) { && propValue[key].type !== ListTitle) {
return new Error(`Invalid prop \`${propFullName}\` supplied to` + return new Error(`Invalid prop \`${propFullName}\` supplied to`
` \`${componentName}\`. Validation failed.`); + ` \`${componentName}\`. Validation failed.`);
} }
return true; return true;
}).isRequired, }).isRequired,
@ -153,7 +152,6 @@ export default class DropdownList extends Component {
const { const {
children, children,
style, style,
className,
horizontal, horizontal,
} = this.props; } = this.props;
@ -186,11 +184,12 @@ export default class DropdownList extends Component {
}, },
); );
const listDirection = horizontal ? styles.horizontalList : styles.verticalList; const listDirection = horizontal ? 'horizontal' : 'vertical';
return ( return (
<ul <Styled.List
style={style} style={style}
className={cx(listDirection, className)} direction={listDirection}
role="menu" role="menu"
ref={(menu) => { ref={(menu) => {
this._menu = menu; this._menu = menu;
@ -198,7 +197,7 @@ export default class DropdownList extends Component {
}} }}
> >
{boundChildren} {boundChildren}
</ul> </Styled.List>
); );
} }
} }

View File

@ -2,9 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import _ from 'lodash'; import _ from 'lodash';
import cx from 'classnames'; import Styled from './styles';
import Icon from '/imports/ui/components/icon/component';
import { styles } from '../styles';
const propTypes = { const propTypes = {
icon: PropTypes.string, icon: PropTypes.string,
@ -41,13 +39,13 @@ class DropdownListItem extends Component {
} = this.props; } = this.props;
return [ return [
(icon ? <Icon iconName={icon} key="icon" className={styles.itemIcon} /> : null), (icon ? <Styled.ItemIcon iconName={icon} key="icon" /> : null),
( (
<span className={styles.itemLabel} key="label" accessKey={accessKey}> <Styled.ItemLabel key="label" accessKey={accessKey}>
{label} {label}
</span> </Styled.ItemLabel>
), ),
(iconRight ? <Icon iconName={iconRight} key="iconRight" className={styles.iconRight} /> : null), (iconRight ? <Styled.IconRight iconName={iconRight} key="iconRight" /> : null),
]; ];
} }
@ -70,7 +68,7 @@ class DropdownListItem extends Component {
const isSelected = className && className.includes('emojiSelected'); const isSelected = className && className.includes('emojiSelected');
const _label = isSelected ? `${label} (${intl.formatMessage(messages.activeAriaLabel)})` : label; const _label = isSelected ? `${label} (${intl.formatMessage(messages.activeAriaLabel)})` : label;
return ( return (
<li <Styled.Item
id={id} id={id}
ref={injectRef} ref={injectRef}
onClick={onClick} onClick={onClick}
@ -78,7 +76,6 @@ class DropdownListItem extends Component {
tabIndex={tabIndex} tabIndex={tabIndex}
aria-labelledby={this.labelID} aria-labelledby={this.labelID}
aria-describedby={this.descID} aria-describedby={this.descID}
className={cx(styles.item, className)}
style={style} style={style}
role="menuitem" role="menuitem"
data-test={dataTest} data-test={dataTest}
@ -92,7 +89,7 @@ class DropdownListItem extends Component {
: null : null
} }
<span id={this.descID} key="describedby" hidden>{description}</span> <span id={this.descID} key="describedby" hidden>{description}</span>
</li> </Styled.Item>
); );
} }
} }

View File

@ -0,0 +1,99 @@
import styled from 'styled-components';
import Icon from '/imports/ui/components/icon/component';
import { lineHeightComputed } from '/imports/ui/stylesheets/styled-components/typography';
import {
colorGrayDark,
colorPrimary,
colorWhite,
colorText,
} from '/imports/ui/stylesheets/styled-components/palette';
import { borderSize } from '/imports/ui/stylesheets/styled-components/general';
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
const ItemIcon = styled(Icon)`
margin: 0 calc(${lineHeightComputed} / 2) 0 0;
color: ${colorText};
flex: 0 0;
[dir="rtl"] & {
margin: 0 0 0 calc(${lineHeightComputed} / 2);
}
`;
const ItemLabel = styled.span`
color: ${colorGrayDark};
font-size: 90%;
flex: 1;
`;
const IconRight = styled(ItemIcon)`
margin-right: 0;
margin-left: 1rem;
font-size: 12px;
line-height: 16px;
[dir="rtl"] & {
margin-left: 0;
margin-right: 1rem;
-webkit-transform: scale(-1, 1);
-moz-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
-o-transform: scale(-1, 1);
transform: scale(-1, 1);
}
`;
const Item = styled.li`
display: flex;
flex: 1 1 100%;
align-items: center;
padding: calc(${lineHeightComputed} / 3) 0;
&:hover,
&:focus {
outline: transparent;
outline-style: dotted;
outline-width: ${borderSize};
cursor: pointer;
background-color: ${colorPrimary};
color: ${colorWhite};
& > span {
color: ${colorWhite} !important;
}
margin-left: -.25rem;
margin-right: -.25rem;
padding-left: .25rem;
padding-right: .25rem;
[dir="rtl"] & {
margin-right: -.25rem;
margin-left: -.25rem;
padding-right: .25rem;
padding-left: .25rem;
}
@media ${smallOnly} {
border-radius: 0.2rem;
}
& i {
color: inherit;
}
}
&:focus {
box-shadow: 0 0 0 2px ${colorWhite}, 0 0 2px 4px rgba(${colorPrimary}, .4);
outline-style: solid;
}
`;
export default {
ItemIcon,
ItemLabel,
IconRight,
Item,
};

View File

@ -1,20 +1,17 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import cx from 'classnames'; import Styled from './styles';
import { styles } from '../styles';
const DropdownListSeparator = ({ style, className }) => ( const DropdownListSeparator = ({ style }) => (
<li style={style} className={cx(styles.separator, className)} /> <Styled.Separator style={style} />
); );
DropdownListSeparator.propTypes = { DropdownListSeparator.propTypes = {
style: PropTypes.shape({}), style: PropTypes.shape({}),
className: PropTypes.string,
}; };
DropdownListSeparator.defaultProps = { DropdownListSeparator.defaultProps = {
style: null, style: null,
className: null,
}; };
export default DropdownListSeparator; export default DropdownListSeparator;

View File

@ -0,0 +1,18 @@
import styled from 'styled-components';
import { colorGrayLighter } from '/imports/ui/stylesheets/styled-components/palette';
import { lineHeightComputed } from '/imports/ui/stylesheets/styled-components/typography';
const Separator = styled.li`
display: flex;
flex: 1 1 100%;
height: 1px;
min-height: 1px;
background-color: ${colorGrayLighter};
padding: 0;
margin-top: calc(${lineHeightComputed} * .5);
margin-bottom: calc(${lineHeightComputed} * .5);
`;
export default {
Separator,
};

View File

@ -0,0 +1,47 @@
import styled from 'styled-components';
import { fontSizeBase, fontSizeLarge, lineHeightComputed } from '/imports/ui/stylesheets/styled-components/typography';
import { colorGrayDark } from '/imports/ui/stylesheets/styled-components/palette';
import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints';
const List = styled.ul`
list-style: none;
font-size: ${fontSizeBase};
margin: 0;
padding: 0;
text-align: left;
color: ${colorGrayDark};
display: flex;
overflow-wrap: break-word;
white-space: pre-line;
[dir="rtl"] & {
text-align: right;
}
@media ${smallOnly} {
font-size: calc(${fontSizeLarge} * 1.1);
padding: ${lineHeightComputed};
}
${({ direction }) => direction === 'horizontal' && `
padding: 0;
flex-direction: row;
@media ${smallOnly} {
flex-direction: column;
padding: calc(${lineHeightComputed} / 1.5) 0;
}
padding: 0 calc(${lineHeightComputed} / 3);
`}
${({ direction }) => direction === 'vertical' && `
flex-direction: column;
width: 100%;
`}
`;
export default {
List,
};

View File

@ -1,166 +0,0 @@
@import "/imports/ui/stylesheets/variables/breakpoints";
@import '/imports/ui/stylesheets/mixins/_indicators';
@import "/imports/ui/stylesheets/variables/placeholders";
%list {
list-style: none;
font-size: var(--font-size-base);
margin: 0;
padding: 0;
text-align: left;
color: var(--color-gray-dark);
display: flex;
overflow-wrap: break-word;
white-space: pre-line;
[dir="rtl"] & {
text-align: right;
}
@include mq($small-only) {
font-size: calc(var(--font-size-large) * 1.1);
padding: var(--line-height-computed);
}
}
.verticalList {
@extend %list;
flex-direction: column;
width: 100%;
}
.horizontalList {
@extend %list;
padding: 0;
flex-direction: row;
@include mq($small-only) {
flex-direction: column;
padding: var(--line-height-computed);
}
}
.title {
color: var(--color-gray);
font-weight: 600;
width: 100%;
}
.separator {
display: flex;
flex: 1 1 100%;
height: 1px;
min-height: 1px;
background-color: var(--color-gray-lighter);
padding: 0;
margin-top: calc(var(--line-height-computed) * .5);
margin-bottom: calc(var(--line-height-computed) * .5);
}
.item {
display: flex;
flex: 1 1 100%;
align-items: center;
.verticalList & {
padding: calc(var(--line-height-computed) / 3) 0;
@include mq($small-only) {
padding: calc(var(--line-height-computed) / 1.5) 0;
}
}
.horizontalList & {
padding: 0 calc(var(--line-height-computed) / 3);
@include mq($small-only) {
padding: calc(var(--line-height-computed) / 1.5) 0;
}
}
&:hover,
&:focus {
@extend %highContrastOutline;
cursor: pointer;
background-color: var(--color-primary);
color: var(--color-white);
.verticalList & {
margin-left: -.25rem;
margin-right: -.25rem;
padding-left: .25rem;
padding-right: .25rem;
[dir="rtl"] & {
margin-right: -.25rem;
margin-left: -.25rem;
padding-right: .25rem;
padding-left: .25rem;
}
}
.horizontalList & {
margin-top: calc((var(--line-height-computed) / 2) * -1);
margin-bottom: calc((var(--line-height-computed) / 2) * -1);
padding-top: calc(var(--line-height-computed) / 2);
padding-bottom: calc(var(--line-height-computed) / 2);
@include mq($small-only) {
margin: 0;
padding: calc(var(--line-height-computed) / 1.5) 0;
margin-left: calc((var(--line-height-computed) / 2) * -1);
margin-right: calc((var(--line-height-computed) / 2) * -1);
padding-left: calc(var(--line-height-computed) / 2);
padding-right: calc(var(--line-height-computed) / 2);
}
}
@include mq($small-only) {
border-radius: 0.2rem;
}
.iconRight,
.itemIcon,
.itemLabel {
color: inherit;
}
}
&:focus {
box-shadow: 0 0 0 2px var(--color-white), 0 0 2px 4px rgba(var(--color-primary), .4);
outline-style: solid;
}
}
.iconRight,
.itemIcon {
margin: 0 calc(var(--line-height-computed) / 2) 0 0;
color: var(--color-text);
flex: 0 0;
[dir="rtl"] & {
margin: 0 0 0 calc(var(--line-height-computed) / 2);
}
}
.iconRight {
margin-right: 0;
margin-left: 1rem;
font-size: 12px;
line-height: 16px;
[dir="rtl"] & {
margin-left: 0;
margin-right: 1rem;
-webkit-transform: scale(-1, 1);
-moz-transform: scale(-1, 1);
-ms-transform: scale(-1, 1);
-o-transform: scale(-1, 1);
transform: scale(-1, 1);
}
}
.itemLabel {
color: var(--color-gray-dark);
font-size: 90%;
flex: 1;
}

View File

@ -1,7 +1,6 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import cx from 'classnames';
import _ from 'lodash'; import _ from 'lodash';
import { styles } from '../styles'; import Styled from './styles';
export default class DropdownListTitle extends Component { export default class DropdownListTitle extends Component {
constructor(props) { constructor(props) {
@ -11,14 +10,13 @@ export default class DropdownListTitle extends Component {
render() { render() {
const { const {
className,
children, children,
} = this.props; } = this.props;
return ( return (
<li className={cx(styles.title, className)} aria-hidden> <Styled.Title aria-hidden>
{children} {children}
</li> </Styled.Title>
); );
} }
} }

View File

@ -0,0 +1,12 @@
import styled from 'styled-components';
import { colorGray } from '/imports/ui/stylesheets/styled-components/palette';
const Title = styled.li`
color: ${colorGray};
font-weight: 600;
width: 100%;
`;
export default {
Title,
};

View File

@ -1,401 +0,0 @@
@import "/imports/ui/stylesheets/variables/breakpoints";
@import "/imports/ui/stylesheets/mixins/_scrollable";
@import "/imports/ui/stylesheets/mixins/_indicators";
@import "/imports/ui/stylesheets/variables/placeholders";
:root {
--dropdown-bg: var(--color-white);
--dropdown-color: var(--color-text);
--caret-shadow-color: var(--color-gray);
--dropdown-caret-width: 12px;
--dropdown-caret-height: 8px;
--rtl-caret-offset: -0.4375rem;
--rtl-content-offset: 10.75rem;
}
.dropdown {
position: relative;
z-index: 3;
&:focus {
outline: none;
}
}
.content {
@extend %highContrastOutline;
outline-style: solid;
z-index: 9999;
position: absolute;
background: var(--dropdown-bg);
border-radius: var(--border-radius);
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
border: 0;
padding: calc(var(--line-height-computed) / 2);
[dir="rtl"] & {
right: var(--rtl-content-offset);
}
&:after,
&:before {
content: '';
position: absolute;
width: 0;
height: 0;
}
&[aria-expanded="false"] {
display: none;
}
&[aria-expanded="true"] {
display: block;
}
@include mq($small-only) {
z-index: 1015;
border-radius: 0;
background-color: #fff;
box-shadow: none;
position: fixed;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
border: 0 !important;
padding: 0 !important;
margin: 0 0 calc(var(--line-height-computed) * 5.25) 0 !important;
transform: translateX(0) translateY(0) !important;
&:after,
&:before {
display: none !important;
}
}
}
.scrollable {
@include mq($small-only) {
@include scrollbox-vertical();
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
}
.trigger {
}
.close {
display: none;
position: fixed;
bottom: 0.8rem;
border-radius: 0;
z-index: 1011;
font-size: calc(var(--font-size-large) * 1.1);
width: calc(100% - (var(--line-height-computed) * 2));
left: var(--line-height-computed);
box-shadow: 0 0 0 2rem var(--color-white) !important;
border: var(--color-white) !important;
@include mq($small-only) {
display: block;
}
}
// removes transform on tethered dropdown for small screen sizes.
// prevents the user-list-item dropdown breaking on mobile.
:global(.tether-out-of-bounds) {
@include mq($small-only) {
transform: none !important;
}
}
/* Placements
* ==========
*/
%down-caret {
bottom: 100%;
left: 50%;
transform: translateX(-50%);
margin-bottom: calc(var(--dropdown-caret-height) * 1.25);
&:before,
&:after {
border-left: var(--dropdown-caret-width) solid transparent;
border-right: var(--dropdown-caret-width) solid transparent;
border-top: var(--dropdown-caret-height) solid var(--dropdown-bg);
bottom: 0;
margin-bottom: calc(var(--dropdown-caret-height) * -1);
}
&:before {
border-top: var(--dropdown-caret-height) solid var(--caret-shadow-color);
}
}
%up-caret {
top: 100%;
left: 50%;
transform: translateX(-50%);
margin-top: calc(var(--dropdown-caret-height) * 1.25);
&:before,
&:after {
border-left: var(--dropdown-caret-width) solid transparent;
border-right: var(--dropdown-caret-width) solid transparent;
border-bottom: var(--dropdown-caret-height) solid var(--dropdown-bg);
margin-top: calc(var(--dropdown-caret-height) * -1);
top: 0;
[dir="rtl"] & {
right: 50%;
transform: translateX(-150%);
}
}
&:before {
border-bottom: var(--dropdown-caret-height) solid var(--caret-shadow-color);
}
}
%right-caret {
top: 50%;
transform: translateX(-100%) translateY(-50%);
left: calc(var(--dropdown-caret-height) * -1.25);
&:before,
&:after {
border-top: var(--dropdown-caret-width) solid transparent;
border-bottom: var(--dropdown-caret-width) solid transparent;
border-left: var(--dropdown-caret-height) solid var(--dropdown-bg);
margin-right: calc(var(--dropdown-caret-height) * -1);
top: 50%;
right: 0;
[dir="rtl"] & {
left: 0;
border-left: 0;
border-right: var(--dropdown-caret-height) solid var(--dropdown-bg);
margin-left: calc(var(--dropdown-caret-height) * -1);
}
}
&:before {
border-left: var(--dropdown-caret-height) solid var(--caret-shadow-color);
border-right: 0;
[dir="rtl"] & {
transform: rotate(180deg);
border-left: 0;
border-right: var(--dropdown-caret-height) solid var(--caret-shadow-color);
}
}
}
%left-caret {
top: 50%;
transform: translateX(100%) translateY(-50%);
right: calc(var(--dropdown-caret-height) * -1.25);
&:before,
&:after {
border-top: var(--dropdown-caret-width) solid transparent;
border-bottom: var(--dropdown-caret-width) solid transparent;
border-right: var(--dropdown-caret-height) solid var(--dropdown-bg);
margin-left: calc(var(--dropdown-caret-height) * -1);
top: 50%;
left: 0;
[dir="rtl"] & {
transform: rotate(180deg);
margin-right: calc(var(--dropdown-caret-height) * -1);
right: 1px;
}
}
&:before {
border-right: var(--dropdown-caret-height) solid var(--caret-shadow-color);
border-left: 0;
[dir="rtl"] & {
right: var(--rtl-caret-offset);
border: none;
margin: 0;
}
}
}
%horz-center-caret {
&:after,
&:before {
left: 50%;
margin-left: calc(var(--dropdown-caret-width) * -1);
[dir="rtl"] & {
margin-right: calc(var(--dropdown-caret-width) * -1);
}
}
}
%horz-left-caret {
transform: translateX(-100%);
left: 100%;
[dir="rtl"] & {
right: 100%;
}
&:after,
&:before {
right: calc(var(--dropdown-caret-width) / 2);
}
}
%horz-right-caret {
transform: translateX(100%);
right: 100%;
left: auto;
&:after,
&:before {
left: var(--dropdown-caret-width);
}
}
%vert-center-caret {
&:after,
&:before {
margin-top: calc(var(--dropdown-caret-width) * -1);
}
}
%vert-top-caret {
top: 0;
&:after,
&:before {
top: 0;
margin-top: calc(var(--dropdown-caret-width) / 2);
}
}
%vert-bottom-caret {
top: auto;
bottom: 0;
&:after,
&:before {
top: auto;
bottom: calc(var(--dropdown-caret-width) / 2);
[dir="rtl"] & {
top: calc(var(--dropdown-caret-width) / 2);
bottom: auto;
}
}
}
.top {
@extend %down-caret;
@extend %horz-center-caret;
}
.top-left, [dir="rtl"] .top-right {
@extend %down-caret;
@extend %horz-right-caret;
min-width: 18rem;
@include mq($small-only) {
width: auto;
}
}
.top-right {
@extend %down-caret;
@extend %horz-left-caret;
}
[dir="rtl"] .top-left {
@extend %down-caret;
@extend %horz-left-caret;
transform: translateX(25%);
}
.bottom {
@extend %up-caret;
@extend %horz-center-caret;
}
.bottom-left, [dir="rtl"] .bottom-right {
@extend %up-caret;
@extend %horz-right-caret;
}
.bottom-right, [dir="rtl"] .bottom-left {
@extend %up-caret;
@extend %horz-left-caret;
min-width: 10rem;
@include mq($small-only) {
width: auto;
}
}
.left, [dir="rtl"] .right {
@extend %right-caret;
@extend %vert-center-caret;
}
.left-top, [dir="rtl"] .right-top {
@extend %right-caret;
@extend %vert-top-caret;
transform: translateX(-100%) translateY(0);
}
.left-bottom {
@extend %right-caret;
@extend %vert-bottom-caret;
transform: translateX(-100%) translateY(0);
}
[dir="rtl"] .right-bottom {
@extend %right-caret;
@extend %vert-bottom-caret;
transform: translateX(-110%) translateY(0);
}
.right, [dir="rtl"] .left {
@extend %left-caret;
@extend %vert-center-caret;
}
.right-top {
@extend %left-caret;
@extend %vert-top-caret;
transform: translateX(100%) translateY(0);
}
[dir="rtl"] .right-top {
transform: translateX(-25%) translateY(0%);
}
[dir="rtl"] .left-top {
@extend %left-caret;
@extend %vert-top-caret;
transform: translateX(125%) translateY(-25%);
}
.right-bottom, [dir="rtl"] .left-bottom {
@extend %left-caret;
@extend %vert-bottom-caret;
transform: translateX(100%) translateY(0);
}