mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-04-19 01:22:11 +00:00
Easier button styles; Keyboard handling; Better demo
This commit is contained in:
parent
893598d450
commit
4f944c2a68
4 changed files with 146 additions and 98 deletions
|
|
@ -6,14 +6,27 @@ const Icon = require('stremio-icons/dom');
|
|||
const { Modal } = require('stremio-router');
|
||||
const styles = require('./styles');
|
||||
|
||||
const ModalDialog = ({ className, children, title, buttons, visible, onClose }) => {
|
||||
const ModalDialog = ({ className, children, title, buttons, onClose }) => {
|
||||
// Close with the Escape key
|
||||
// TODO: Maybe we should consider a global actions mapping so the 'close' key can be globbaly changed
|
||||
React.useEffect(() => {
|
||||
const onKeyUp = (event) => {
|
||||
if (event.key === 'Escape' && typeof onClose === 'function') {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
window.addEventListener('keyup', onKeyUp);
|
||||
return () => {
|
||||
window.removeEventListener('keyup', onKeyUp);
|
||||
};
|
||||
}, [onClose]);
|
||||
const onModalContainerClick = React.useCallback(event => {
|
||||
if(event.target === event.currentTarget) {
|
||||
if (event.target === event.currentTarget && typeof onClose === 'function') {
|
||||
onClose(event);
|
||||
}
|
||||
});
|
||||
}, [onClose]);
|
||||
return (
|
||||
<Modal className={classnames(styles['modal-container'], { [styles['shown']]: visible })} onMouseDown={onModalContainerClick}>
|
||||
<Modal className={styles['modal-container']} onMouseDown={onModalContainerClick}>
|
||||
<div className={classnames(styles['modal-dialog-container'], className)}>
|
||||
<Button className={styles['close-button']} onClick={onClose}>
|
||||
<Icon className={styles['x-icon']} icon={'ic_x'} />
|
||||
|
|
@ -24,7 +37,7 @@ const ModalDialog = ({ className, children, title, buttons, visible, onClose })
|
|||
</div>
|
||||
<div className={styles['modal-dialog-buttons']}>
|
||||
{Array.isArray(buttons) && buttons.length ? buttons.map((button, key) => (
|
||||
<Button key={key} className={styles['button']} {...button.props}>
|
||||
<Button key={key} className={classnames(styles['action-button'], button.className)} {...button.props}>
|
||||
{
|
||||
button.icon
|
||||
?
|
||||
|
|
@ -47,9 +60,9 @@ ModalDialog.propTypes = {
|
|||
buttons: PropTypes.arrayOf(PropTypes.shape({
|
||||
label: PropTypes.node,
|
||||
icon: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
props: PropTypes.object // Button.propTypes unfortunately these are not defined
|
||||
})),
|
||||
visible: PropTypes.boolean,
|
||||
onClose: PropTypes.func
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
.modal-container {
|
||||
display: none;
|
||||
&.shown {
|
||||
display: flex;
|
||||
}
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
background-color: var(--color-backgrounddark40);
|
||||
|
||||
.modal-dialog-container {
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
|
|
@ -14,11 +14,21 @@
|
|||
}
|
||||
|
||||
.close-button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
top: .2rem;
|
||||
right: .2rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
|
||||
.x-icon {
|
||||
flex: 1 0 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
fill: var(--color-surfacedark);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-surfacelight);
|
||||
|
|
@ -27,15 +37,12 @@
|
|||
fill: var(--color-signal2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.x-icon {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
fill: var(--color-surfacedark);
|
||||
&:focus {
|
||||
.x-icon {
|
||||
fill: var(--color-signal2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
|
@ -53,32 +60,39 @@
|
|||
flex-flow: row;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1 0 0;
|
||||
text-align: center;
|
||||
color: var(--color-surfacelighter);
|
||||
background-color: var(--color-signal5);
|
||||
padding: 1rem;
|
||||
margin: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-signal580);
|
||||
}
|
||||
.action-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1 0 0;
|
||||
text-align: center;
|
||||
color: var(--color-surfacelighter);
|
||||
background-color: var(--color-signal5);
|
||||
padding: 1rem;
|
||||
margin: .5rem;
|
||||
|
||||
&:global(.disabled) {
|
||||
color: var(--color-surfacedarker);
|
||||
background-color: var(--color-surfacedark);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-signal580);
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: var(--color-surfacelighter);
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
}
|
||||
&:focus {
|
||||
outline: 3px solid var(--color-surfacelighter);
|
||||
outline-offset: -4px;
|
||||
|
||||
}
|
||||
|
||||
&:global(.disabled) {
|
||||
color: var(--color-surfacedarker);
|
||||
background-color: var(--color-surfacedark);
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: var(--color-surfacelighter);
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,16 +2,14 @@
|
|||
const React = require('react');
|
||||
const { storiesOf } = require('@storybook/react');
|
||||
const { action } = require('@storybook/addon-actions');
|
||||
const classnames = require('classnames');
|
||||
const Icon = require('stremio-icons/dom');
|
||||
const { Modal } = require('stremio-router');
|
||||
const { ModalDialog } = require('stremio/common');
|
||||
const styles = require('./styles');
|
||||
const useBinaryState = require('stremio/common/useBinaryState');
|
||||
|
||||
storiesOf('ModalDialog', module).add('ModalDialog', () => {
|
||||
const [modalVisible, showModal, hideModal, toggleModal] = useBinaryState(false);
|
||||
const [modalBVisible, showModalB, hideModalB, toggleModalB] = useBinaryState(false);
|
||||
const [modalVisible, showModal, hideModal] = useBinaryState(false);
|
||||
const [modalBVisible, showModalB, hideModalB] = useBinaryState(true);
|
||||
|
||||
const modalDummyContents = (
|
||||
<div className={styles['content-container']}>
|
||||
|
|
@ -26,22 +24,38 @@ storiesOf('ModalDialog', module).add('ModalDialog', () => {
|
|||
</div>
|
||||
);
|
||||
|
||||
/*
|
||||
|
||||
Every Button has the following properties:
|
||||
|
||||
label (String/React component) - the contents of the button.
|
||||
icon (String) - icon class name. It will be shown to the left of the button's text
|
||||
className (String) - Custom className applied along side the default one. Used for custom styles
|
||||
props (Object) - the properties applied to the button itself. If a className is supplied here it will override all other class names for this Button
|
||||
|
||||
*/
|
||||
|
||||
const oneButton = [
|
||||
{
|
||||
label: 'Show many buttons', icon: 'ic_ellipsis', props: {
|
||||
onClick: React.useCallback(() => setButtons(manyButtons), [])
|
||||
onClick: React.useCallback(() => setButtons(manyButtons), [setButtons])
|
||||
}
|
||||
},
|
||||
]
|
||||
const manyButtons = [
|
||||
{
|
||||
label: 'One', icon: 'ic_back_ios', props: {
|
||||
onClick: React.useCallback(() => setButtons(oneButton), [])
|
||||
label: 'One',
|
||||
icon: 'ic_back_ios',
|
||||
props: {
|
||||
onClick: React.useCallback(() => setButtons(oneButton), [setButtons])
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'A disabled button with a long name', props: {
|
||||
label: 'A disabled button with a long name',
|
||||
props: {
|
||||
disabled: true,
|
||||
tabIndex: -1, // We don't need keyboard focus on disabled elements
|
||||
onClick: action('The onClick on disabled buttons should not be called!')
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
@ -50,8 +64,9 @@ storiesOf('ModalDialog', module).add('ModalDialog', () => {
|
|||
<Icon className={styles['icon']} icon={'ic_actor'} />
|
||||
{'A button with a long name, icon and custom class'}
|
||||
</React.Fragment>
|
||||
), props: {
|
||||
className: styles['custom-button'],
|
||||
),
|
||||
className: styles['custom-button'],
|
||||
props: {
|
||||
onClick: action('A button with a long name and icon clicked')
|
||||
}
|
||||
},
|
||||
|
|
@ -60,17 +75,28 @@ storiesOf('ModalDialog', module).add('ModalDialog', () => {
|
|||
const [buttons, setButtons] = React.useState(oneButton);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<button className={styles['button']} onClick={toggleModal}>Toggle dialog without buttons</button>
|
||||
<div>
|
||||
<button className={styles['show-modal-button']} onClick={showModal}>Show dialog without buttons</button>
|
||||
{
|
||||
modalVisible
|
||||
?
|
||||
<ModalDialog className={styles['modal-dialog']} title={'Test dialog without buttons'} visible={modalVisible} onClose={hideModal}>
|
||||
{modalDummyContents}
|
||||
</ModalDialog>
|
||||
:
|
||||
null
|
||||
}
|
||||
|
||||
<button className={styles['button']} onClick={toggleModalB}>Toggle dialog with buttons</button>
|
||||
|
||||
<ModalDialog className={styles['modal-dialog']} title={'Test dialog without buttons'} visible={modalVisible} onClose={hideModal}>
|
||||
{modalDummyContents}
|
||||
</ModalDialog>
|
||||
<ModalDialog className={styles['modal-dialog']} title={'Test dialog with buttons'} buttons={buttons} visible={modalBVisible} onClose={hideModalB}>
|
||||
{modalDummyContents}
|
||||
</ModalDialog>
|
||||
</React.Fragment>
|
||||
<button className={styles['show-modal-button']} onClick={showModalB}>Show dialog with buttons</button>
|
||||
{
|
||||
modalBVisible
|
||||
?
|
||||
<ModalDialog className={styles['modal-dialog']} title={'Test dialog with buttons'} buttons={buttons} visible={modalBVisible} onClose={hideModalB}>
|
||||
{modalDummyContents}
|
||||
</ModalDialog>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,39 +1,8 @@
|
|||
.button {
|
||||
display: inline-block;
|
||||
background-color: var(--color-signal3);
|
||||
width: 15rem;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
margin: .5rem;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
width: 45rem;
|
||||
.content-container {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
margin: 0 -0.5rem;
|
||||
|
||||
.content-column {
|
||||
flex: 1 0 0;
|
||||
padding: 0 0.5rem;
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.custom-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1 0 0;
|
||||
text-align: center;
|
||||
color: var(--color-surfacelighter);
|
||||
background-color: var(--color-signal3);
|
||||
padding: 1rem;
|
||||
margin: 0.5rem;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-signal380);
|
||||
|
|
@ -51,4 +20,30 @@
|
|||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
// This is only for the content. Not relevant for the demo
|
||||
.content-container {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
margin: 0 -0.5rem;
|
||||
|
||||
.content-column {
|
||||
flex: 1 0 0;
|
||||
padding: 0 0.5rem;
|
||||
|
||||
p {
|
||||
text-align: justify;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is only for the buttons that show the modals. Not relevant for the demo
|
||||
.show-modal-button {
|
||||
display: inline-block;
|
||||
background-color: var(--color-signal4);
|
||||
width: 15rem;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
margin: .5rem;
|
||||
}
|
||||
Loading…
Reference in a new issue