mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-19 05:32:09 +00:00
common Dropdown component implemented
This commit is contained in:
parent
44713856eb
commit
f67480919d
9 changed files with 142 additions and 105 deletions
75
src/common/Dropdown/Dropdown.js
Normal file
75
src/common/Dropdown/Dropdown.js
Normal 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;
|
||||
3
src/common/Dropdown/index.js
Normal file
3
src/common/Dropdown/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
const Dropdown = require('./Dropdown');
|
||||
|
||||
module.exports = Dropdown;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
const PickerMenu = require('./PickerMenu');
|
||||
|
||||
module.exports = PickerMenu;
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue