common Dropdown component implemented

This commit is contained in:
NikolaBorislavovHristov 2019-09-13 10:47:01 +03:00
parent 44713856eb
commit f67480919d
9 changed files with 142 additions and 105 deletions

View file

@ -0,0 +1,75 @@
const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const Icon = require('stremio-icons/dom');
const Button = require('stremio/common/Button');
const Popup = require('stremio/common/Popup');
const useBinaryState = require('stremio/common/useBinaryState');
const styles = require('./styles');
const Dropdown = ({ className, name, selected, options, onSelect }) => {
const [menuOpen, openMenu, closeMenu, toggleMenu] = useBinaryState(false);
const optionOnClick = React.useCallback((event) => {
if (typeof onSelect === 'function') {
onSelect(event);
}
if (!event.nativeEvent.closeMenuPrevented) {
closeMenu();
}
}, [onSelect]);
return (
<Popup
open={menuOpen}
menuMatchLabelWidth={true}
onCloseRequest={closeMenu}
renderLabel={(ref) => (
<Button ref={ref} className={classnames(className, styles['dropdown-label-container'], { 'active': menuOpen })} title={name} onClick={toggleMenu}>
<div className={styles['dropdown-label']}>
{
Array.isArray(selected) && selected.length > 0 ?
options.reduce((result, { label, value }) => {
if (selected.includes(value)) {
result.push(label);
}
return result;
}, []).join(', ')
:
name
}
</div>
<Icon className={styles['icon']} icon={'ic_arrow_down'} />
</Button>
)}
renderMenu={() => (
<div className={styles['dropdown-menu-container']}>
{
Array.isArray(options) && options.length > 0 ?
options.map(({ label, value }) => (
<Button key={value} className={classnames(styles['dropdown-option-container'], { 'selected': Array.isArray(selected) && selected.includes(value) })} title={label} data-name={name} data-value={value} onClick={optionOnClick}>
<div className={styles['dropdown-option-label']}>{label}</div>
<Icon className={styles['icon']} icon={'ic_check'} />
</Button>
))
:
null
}
</div>
)}
/>
);
};
Dropdown.propTypes = {
className: PropTypes.string,
name: PropTypes.string,
selected: PropTypes.arrayOf(PropTypes.string),
options: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired
})),
onSelect: PropTypes.func
};
module.exports = Dropdown;

View file

@ -0,0 +1,3 @@
const Dropdown = require('./Dropdown');
module.exports = Dropdown;

View file

@ -1,8 +1,8 @@
.picker-label-container {
.dropdown-label-container {
display: flex;
flex-direction: row;
align-items: center;
padding: 0 0.8rem;
padding: 0 1rem;
background-color: var(--color-backgroundlighter);
&:hover {
@ -12,7 +12,7 @@
&:global(.active) {
background-color: var(--color-surfacelight);
.picker-label {
.dropdown-label {
color: var(--color-backgrounddarker);
}
@ -21,9 +21,8 @@
}
}
.picker-label {
.dropdown-label {
flex: 1;
font-size: 1.1rem;
max-height: 2.4em;
color: var(--color-surfacelighter);
}
@ -32,24 +31,45 @@
flex: none;
width: 1rem;
height: 1rem;
margin-left: 0.8rem;
margin-left: 1rem;
fill: var(--color-surfacelighter);
}
}
.picker-menu-container {
.dropdown-menu-container {
background-color: var(--color-backgroundlighter);
.picker-option-container {
padding: 0.8rem;
.dropdown-option-container {
display: flex;
flex-direction: row;
align-items: center;
padding: 1rem;
&:global(.selected) {
background-color: var(--color-surfacedarker40);
.icon {
display: block;
}
}
&:hover {
background-color: var(--color-surfacedarker60);
}
.picker-option-label {
color: var(--color-surfacelighter);
.dropdown-option-label {
flex: 1;
max-height: 4.8em;
color: var(--color-surfacelighter);
}
.icon {
flex: none;
display: none;
width: 1rem;
height: 1rem;
margin-left: 1rem;
fill: var(--color-surfacelighter);
}
}
}

View file

@ -1,6 +1,7 @@
const Button = require('./Button');
const Checkbox = require('./Checkbox');
const ColorPicker = require('./ColorPicker');
const Dropdown = require('./Dropdown');
const MainNavBar = require('./MainNavBar');
const MetaItem = require('./MetaItem');
const MetaPreview = require('./MetaPreview');
@ -23,6 +24,7 @@ module.exports = {
Button,
Checkbox,
ColorPicker,
Dropdown,
MainNavBar,
MetaItem,
MetaPreview,

View file

@ -1,12 +1,11 @@
const React = require('react');
const classnames = require('classnames');
const { MainNavBar, MetaItem, MetaPreview } = require('stremio/common');
const PickerMenu = require('./PickerMenu');
const { Dropdown, MainNavBar, MetaItem, MetaPreview } = require('stremio/common');
const useCatalog = require('./useCatalog');
const styles = require('./styles');
const Discover = ({ urlParams, queryParams }) => {
const [pickers, metaItems] = useCatalog(urlParams, queryParams);
const [dropdowns, metaItems] = useCatalog(urlParams, queryParams);
const [selectedItem, setSelectedItem] = React.useState(null);
const metaItemsOnMouseDown = React.useCallback((event) => {
event.nativeEvent.blurPrevented = true;
@ -27,9 +26,9 @@ const Discover = ({ urlParams, queryParams }) => {
<div className={styles['discover-container']}>
<MainNavBar className={styles['nav-bar']} />
<div className={styles['discover-content']}>
<div className={styles['pickers-container']}>
{pickers.map((picker) => (
<PickerMenu {...picker} key={picker.name} className={styles['picker']} />
<div className={styles['dropdowns-container']}>
{dropdowns.map((dropdown) => (
<Dropdown {...dropdown} key={dropdown.name} className={styles['dropdown']} />
))}
</div>
<div className={styles['meta-items-container']} onFocusCapture={metaItemsOnFocus} onMouseDownCapture={metaItemsOnMouseDown}>

View file

@ -1,59 +0,0 @@
const React = require('react');
const PropTypes = require('prop-types');
const classnames = require('classnames');
const Icon = require('stremio-icons/dom');
const { Button, Popup, useBinaryState } = require('stremio/common');
const styles = require('./styles');
const PickerMenu = ({ className, name, value, options, onSelect }) => {
const [menuOpen, openMenu, closeMenu, toggleMenu] = useBinaryState(false);
const optionOnClick = React.useCallback((event) => {
if (typeof onSelect === 'function') {
onSelect(event);
}
if (!event.nativeEvent.closeMenuPrevented) {
closeMenu();
}
}, [onSelect]);
return (
<Popup
open={menuOpen}
menuMatchLabelWidth={true}
onCloseRequest={closeMenu}
renderLabel={(ref) => (
<Button ref={ref} className={classnames(className, styles['picker-label-container'], { 'active': menuOpen })} title={name} onClick={toggleMenu}>
<div className={styles['picker-label']}>{typeof value === 'string' && value.length > 0 ? value : name}</div>
<Icon className={styles['icon']} icon={'ic_arrow_down'} />
</Button>
)}
renderMenu={() => (
<div className={styles['picker-menu-container']}>
{
Array.isArray(options) && options.length > 0 ?
options.map(({ label, value }) => (
<Button key={value} className={styles['picker-option-container']} title={typeof label === 'string' && label.length > 0 ? label : value} data-name={name} data-value={value} onClick={optionOnClick}>
<div className={styles['picker-option-label']}>{typeof label === 'string' && label.length > 0 ? label : value}</div>
</Button>
))
:
null
}
</div>
)}
/>
);
};
PickerMenu.propTypes = {
className: PropTypes.string,
name: PropTypes.string,
value: PropTypes.string,
options: PropTypes.arrayOf(PropTypes.shape({
label: PropTypes.string,
value: PropTypes.string.isRequired
})),
onSelect: PropTypes.func
};
module.exports = PickerMenu;

View file

@ -1,3 +0,0 @@
const PickerMenu = require('./PickerMenu');
module.exports = PickerMenu;

View file

@ -19,18 +19,18 @@
grid-template-columns: 1fr 26rem;
grid-template-rows: fit-content(15rem) 1fr;
grid-template-areas:
"pickers-area meta-preview-area"
"dropdowns-area meta-preview-area"
"meta-items-area meta-preview-area";
.pickers-container {
grid-area: pickers-area;
.dropdowns-container {
grid-area: dropdowns-area;
display: grid;
grid-gap: 1rem;
margin: 2rem 0;
padding: 0 2rem;
overflow-y: auto;
.picker {
.dropdown {
height: 3rem;
}
}
@ -62,7 +62,7 @@
@media only screen and (min-width: @xxlarge) {
.discover-container {
.discover-content {
.pickers-container {
.dropdowns-container {
grid-template-columns: repeat(auto-fill, 17rem);
}
@ -76,7 +76,7 @@
@media only screen and (max-width: @xxlarge) {
.discover-container {
.discover-content {
.pickers-container {
.dropdowns-container {
grid-template-columns: repeat(6, 1fr);
}
@ -90,7 +90,7 @@
@media only screen and (max-width: @normal) {
.discover-container {
.discover-content {
.pickers-container {
.dropdowns-container {
grid-template-columns: repeat(5, 1fr);
}
@ -104,7 +104,7 @@
@media only screen and (max-width: @medium) {
.discover-container {
.discover-content {
.pickers-container {
.dropdowns-container {
grid-template-columns: repeat(4, 1fr);
}
@ -118,7 +118,7 @@
@media only screen and (max-width: @small) {
.discover-container {
.discover-content {
.pickers-container {
.dropdowns-container {
grid-template-columns: repeat(3, 1fr);
}
@ -135,10 +135,10 @@
grid-template-columns: 1fr;
grid-template-rows: fit-content(19rem) 1fr;
grid-template-areas:
"pickers-area"
"dropdowns-area"
"meta-items-area";
.pickers-container {
.dropdowns-container {
grid-template-columns: repeat(4, 1fr);
}
@ -157,7 +157,7 @@
@media only screen and (max-width: @minimum) {
.discover-container {
.discover-content {
.pickers-container {
.dropdowns-container {
grid-template-columns: repeat(3, 1fr);
}
}

View file

@ -32,7 +32,7 @@ const useCatalog = (urlParams, queryParams) => {
};
return [addon, catalog];
}, [urlParams.type, urlParams.catalog]);
const pickers = React.useMemo(() => {
const dropdowns = React.useMemo(() => {
const onTypeChange = (event) => {
const { value } = event.currentTarget.dataset;
const query = new URLSearchParams(queryParams);
@ -48,45 +48,45 @@ const useCatalog = (urlParams, queryParams) => {
const query = new URLSearchParams({ ...queryParams, [name]: value });
window.location = `#/discover/${catalog.type}/${addon.id}:${catalog.id}?${query}`;
};
const requiredPickers = [
const requiredDropdowns = [
{
name: 'type',
value: catalog.type,
selected: [catalog.type],
options: [
{ value: 'movie', label: 'movie' },
{ value: 'series', label: 'series' },
{ value: 'channels', label: 'channels' },
{ value: 'games', label: 'games' }
],
onChange: onTypeChange
onSelect: onTypeChange
},
{
name: 'catalog',
value: catalog.name,
selected: [`${addon.id}:${catalog.id}`],
options: [
{ value: 'com.linvo.cinemeta:top', label: 'Top' },
{ value: 'com.linvo.cinemeta:year', label: 'By year' }
],
onChange: onCatalogChange
onSelect: onCatalogChange
}
];
const extraPickers = catalog.extra
const extraDropdowns = catalog.extra
.filter((extra) => {
return extra.name !== 'skip' && extra.name !== 'search';
})
.map((extra) => ({
...extra,
onChange: onQueryParamChange,
onSelect: onQueryParamChange,
options: extra.options.map((option) => ({ value: option, label: option })),
value: extra.options.includes(queryParams[extra.name]) ?
queryParams[extra.name]
selected: extra.options.includes(queryParams[extra.name]) ?
[queryParams[extra.name]]
:
extra.isRequired ?
extra.options[0]
[extra.options[0]]
:
null
[]
}));
return requiredPickers.concat(extraPickers);
return requiredDropdowns.concat(extraDropdowns);
}, [addon, catalog, queryString]);
const items = React.useMemo(() => {
return Array(100).fill(null).map((_, index) => ({
@ -98,7 +98,7 @@ const useCatalog = (urlParams, queryParams) => {
posterShape: 'poster'
}));
}, []);
return [pickers, items];
return [dropdowns, items];
};
module.exports = useCatalog;