Easier button styles; Keyboard handling; Better demo

This commit is contained in:
Vladimir Borisov 2019-10-31 16:34:30 +02:00
parent 893598d450
commit 4f944c2a68
No known key found for this signature in database
GPG key ID: F9A584BE4FCB6603
4 changed files with 146 additions and 98 deletions

View file

@ -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
};

View file

@ -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;
}
}

View file

@ -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>
);
});

View file

@ -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;
}