Commit 84eec9f3 authored by Tasso Evangelista's avatar Tasso Evangelista Committed by Renato Becker

[IMPROVE] Technical debts of components (#202)

* Update dependencies

* Update Alert component

* Update Avatar component

* Split ButtonGroup from Button

* Add support for icon and badge on Button component

* Embed ChatButton into Screen component

* Update FilesDropTarget component

* Don't export Sound component as default

* Don't export Composer component as default

* Don't export Footer component as default

* Don't export Form component as default

* Update Composer component

* Recover submit event from some forms

* Update TextInput, PasswordInput, and SelectInput components

* Remove FileUploadInput and move its behavior to FilesDropTarget

* Fix memo() helper

* Add white background to Storybook

* Add memedIpsum() helper for stories

* Expose MemoizedComponent helper

* Update Form components

* Update history module initialization

* Remove Storybook's console addon

* Update Messages components

* Fix MessageList scroll positioning

* Update Message attachment handling
parent bb189ca8
import '@storybook/addon-a11y/register';
import '@storybook/addon-actions/register';
import '@storybook/addon-backgrounds/register';
import '@storybook/addon-knobs/register';
import '@storybook/addon-options/register';
import '@storybook/addon-viewport/register';
import { addDecorator, configure, addParameters } from '@storybook/react';
import { withA11y } from '@storybook/addon-a11y';
import { setConsoleOptions } from '@storybook/addon-console';
import { withOptions } from '@storybook/addon-options';
import { create } from '@storybook/theming';
......@@ -16,14 +15,17 @@ addParameters({
hierarchySeparator: /\//,
hierarchyRootSeparator: /\|/,
},
backgrounds: [
{
name: 'white',
value: 'white',
default: true,
},
],
});
addDecorator(withA11y);
setConsoleOptions({
panelExclude: [],
});
function loadStories() {
require('../src/styles/index.scss');
const req = require.context('../src', true, /(stories|story)\.js$/);
......
......@@ -3,36 +3,47 @@ import { createClassName } from '../helpers';
import CloseIcon from '../../icons/close.svg';
import styles from './styles';
export const Alert = ({ children, success, warning, error, color, onDismiss, ...props }) => (
<div className={createClassName(styles, 'alert', { success, warning, error })} style={color && { backgroundColor: color }} {...props}>
<div className={createClassName(styles, 'alert__content')}>
{children}
</div>
<button
onClick={onDismiss}
className={createClassName(styles, 'alert__close')}
aria-label={I18n.t('Dismiss this alert')}
>
<CloseIcon width={20} />
</button>
</div>
);
class AlertContainer extends Component {
export class Alert extends Component {
static defaultProps = {
timeout: 3000,
}
handleDismiss = () => this.props.onDismiss && this.props.onDismiss(this.props.id)
handleDismiss = () => {
const { onDismiss, id } = this.props;
onDismiss && onDismiss(id);
}
componentDidMount() {
const { timeout } = this.props;
if (Number.isFinite(timeout) && timeout > 0) {
setTimeout(() => this.handleDismiss(), timeout);
this.dismissTimeout = setTimeout(this.handleDismiss, timeout);
}
}
render = ({ children, ...props }) => <Alert {...props} onDismiss={this.handleDismiss}>{children}</Alert>
}
componentWillUnmount() {
clearTimeout(this.dismissTimeout);
}
export default AlertContainer;
render = ({ success, warning, error, color, className, style = {}, children }) => (
<div
role="alert"
className={createClassName(styles, 'alert', { success, warning, error }, [className])}
style={{
...style,
...(color && { backgroundColor: color }),
}}
>
<div className={createClassName(styles, 'alert__content')}>
{children}
</div>
<button
onClick={this.handleDismiss}
className={createClassName(styles, 'alert__close')}
aria-label={I18n.t('Dismiss this alert')}
>
<CloseIcon width={20} />
</button>
</div>
)
}
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs, boolean, text, color } from '@storybook/addon-knobs';
import { withKnobs, boolean, color, text } from '@storybook/addon-knobs';
import { storiesOf } from '@storybook/react';
import { screenCentered, memedIpsum } from '../../helpers.stories';
import { Alert } from '.';
const alertText = 'A simple alert';
storiesOf('Components|Alert', module)
.addDecorator(withKnobs)
.addDecorator(screenCentered)
.add('default', () => (
<Alert
success={boolean('success', false)}
warning={boolean('warning', false)}
error={boolean('error', false)}
onDismiss={action('dismiss')}
>
{text('text', memedIpsum({ count: 3, units: 'words' }))}
</Alert>
))
.add('success', () => (
<Alert
success={boolean('success', true)}
warning={boolean('warning', false)}
error={boolean('error', false)}
onDismiss={action('clicked')}
onDismiss={action('dismiss')}
>
{text('text', alertText)}
{text('text', memedIpsum({ count: 3, units: 'words' }))}
</Alert>
))
.add('warning', () => (
......@@ -22,9 +33,9 @@ storiesOf('Components|Alert', module)
success={boolean('success', false)}
warning={boolean('warning', true)}
error={boolean('error', false)}
onDismiss={action('clicked')}
onDismiss={action('dismiss')}
>
{text('text', alertText)}
{text('text', memedIpsum({ count: 3, units: 'words' }))}
</Alert>
))
.add('error', () => (
......@@ -32,9 +43,9 @@ storiesOf('Components|Alert', module)
success={boolean('success', false)}
warning={boolean('warning', false)}
error={boolean('error', true)}
onDismiss={action('clicked')}
onDismiss={action('dismiss')}
>
{text('text', alertText)}
{text('text', memedIpsum({ count: 3, units: 'words' }))}
</Alert>
))
.add('custom color', () => (
......@@ -43,9 +54,9 @@ storiesOf('Components|Alert', module)
warning={boolean('warning', false)}
error={boolean('error', false)}
color={color('color', '#175CC4')}
onDismiss={action('clicked')}
onDismiss={action('dismiss')}
>
{text('text', alertText)}
{text('text', memedIpsum({ count: 3, units: 'words' }))}
</Alert>
))
.add('with long text content', () => (
......@@ -53,8 +64,20 @@ storiesOf('Components|Alert', module)
success={boolean('success', false)}
warning={boolean('warning', false)}
error={boolean('error', false)}
onDismiss={action('clicked')}
onDismiss={action('dismiss')}
>
{text('text', alertText.repeat(100))}
{text('text', memedIpsum({ count: 30, units: 'words' }))}
</Alert>
));
))
.add('without timeout', () => (
<Alert
success={boolean('success', false)}
warning={boolean('warning', false)}
error={boolean('error', false)}
timeout={0}
onDismiss={action('dismiss')}
>
{text('text', memedIpsum({ count: 3, units: 'words' }))}
</Alert>
))
;
......@@ -5,6 +5,7 @@
$alert-color: $color-text-lighter;
$alert-font-family: $font-family;
$alert-background-color: $bg-color-darker;
$alert-success-background-color: $color-green;
$alert-warning-background-color: $color-yellow;
$alert-error-background-color: $color-red;
......@@ -20,7 +21,7 @@ $alert-error-background-color: $color-red;
overflow: hidden;
letter-spacing: 0;
line-height: 16px;
background-color: $alert-success-background-color;
background-color: $alert-background-color;
display: flex;
align-items: center;
justify-content: space-between;
......@@ -31,18 +32,6 @@ $alert-error-background-color: $color-red;
text-overflow: ellipsis;
}
&--success {
background-color: $alert-success-background-color;
}
&--warning {
background-color: $alert-warning-background-color;
}
&--error {
background-color: $alert-error-background-color;
}
&__close {
padding: 0;
background: none;
......@@ -55,4 +44,15 @@ $alert-error-background-color: $color-red;
@include pressable-button(1px);
}
&--success {
background-color: $alert-success-background-color;
}
&--warning {
background-color: $alert-warning-background-color;
}
&--error {
background-color: $alert-error-background-color;
}
}
import { Component } from 'preact';
import styles from './styles';
import { createClassName } from '../helpers';
import StatusIndicator from '../StatusIndicator';
import styles from './styles';
export class Avatar extends Component {
......@@ -13,17 +12,18 @@ export class Avatar extends Component {
this.setState({ errored: true });
}
componentWillReceiveProps(nextProps) {
if (nextProps.src !== this.props.src) {
componentWillReceiveProps({ src: nextSrc }) {
const { src } = this.props;
if (nextSrc !== src) {
this.setState({ errored: false });
}
}
render = ({ small, large, src, description, status, className, ...args }, { errored }) => (
render = ({ small, large, src, description, status, className, style }, { errored }) => (
<div
aria-label="User picture"
className={createClassName(styles, 'avatar', { small, large, nobg: src && !errored }, [className])}
{...args}
style={style}
>
{(src && !errored) && (
<img
......@@ -35,12 +35,8 @@ export class Avatar extends Component {
)}
{status && (
<div className={createClassName(styles, 'avatar__status', { small, large })}>
<StatusIndicator status={status} small={small} large={large} bordered />
</div>
<span className={createClassName(styles, 'avatar__status', { small, large, status })} />
)}
</div>
)
}
export default Avatar;
import centered from '@storybook/addon-centered/react';
import { withKnobs, boolean, text, select } from '@storybook/addon-knobs';
import { storiesOf } from '@storybook/react';
import Avatar from '.';
import bertieBartonAvatar from './bertieBarton.png';
import { avatarResolver } from '../../helpers.stories';
import { Avatar } from '.';
const avatarDescription = 'user description';
const avatarStatuses = {
None: null,
OffLine: 'offline',
Away: 'away',
Busy: 'busy',
OnLine: 'online',
};
const defaultSrc = avatarResolver('guilherme.gazzo');
const defaultDescription = 'user description';
const statuses = [null, 'offline', 'away', 'busy', 'online'];
storiesOf('Components|Avatar', module)
.addDecorator(centered)
.addDecorator(withKnobs)
.add('large', () => (
.add('default', () => (
<Avatar
src={text('src', bertieBartonAvatar)}
src={text('src', defaultSrc)}
small={boolean('small', false)}
large={boolean('large', true)}
description={text('description', avatarDescription)}
status={select('status', avatarStatuses, null)}
large={boolean('large', false)}
description={text('description', defaultDescription)}
status={select('status', statuses, null)}
/>
))
.add('medium', () => (
.add('large', () => (
<Avatar
src={text('src', bertieBartonAvatar)}
src={text('src', defaultSrc)}
small={boolean('small', false)}
large={boolean('large', false)}
description={text('description', avatarDescription)}
status={select('status', avatarStatuses, null)}
large={boolean('large', true)}
description={text('description', defaultDescription)}
status={select('status', statuses, null)}
/>
))
.add('small', () => (
<Avatar
src={text('src', bertieBartonAvatar)}
src={text('src', defaultSrc)}
small={boolean('small', true)}
large={boolean('large', false)}
description={text('description', avatarDescription)}
status={select('status', avatarStatuses, null)}
description={text('description', defaultDescription)}
status={select('status', statuses, null)}
/>
))
.add('as placeholder', () => (
......@@ -49,54 +44,51 @@ storiesOf('Components|Avatar', module)
<Avatar
src={text('src', '')}
large
description={text('description', avatarDescription)}
status={select('status', avatarStatuses, null)}
description={text('description', defaultDescription)}
status={select('status', statuses, null)}
style={{ margin: '0.5rem' }}
/>
<Avatar
src={text('src', '')}
description={text('description', avatarDescription)}
status={select('status', avatarStatuses, null)}
description={text('description', defaultDescription)}
status={select('status', statuses, null)}
style={{ margin: '0.5rem' }}
/>
<Avatar
src={text('src', '')}
small
description={text('description', avatarDescription)}
status={select('status', avatarStatuses, null)}
description={text('description', defaultDescription)}
status={select('status', statuses, null)}
style={{ margin: '0.5rem' }}
/>
</div>
))
.add('with status indicator', () => (
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-around' }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-around' }}>
<Avatar
src={text('src', bertieBartonAvatar)}
large
description={text('description', avatarDescription)}
src={text('src', defaultSrc)}
description={text('description', defaultDescription)}
status={'offline'}
style={{ margin: '0.5rem' }}
/>
<Avatar
src={text('src', bertieBartonAvatar)}
large
description={text('description', avatarDescription)}
src={text('src', defaultSrc)}
description={text('description', defaultDescription)}
status={'away'}
style={{ margin: '0.5rem' }}
/>
<Avatar
src={text('src', bertieBartonAvatar)}
large
description={text('description', avatarDescription)}
src={text('src', defaultSrc)}
description={text('description', defaultDescription)}
status={'busy'}
style={{ margin: '0.5rem' }}
/>
<Avatar
src={text('src', bertieBartonAvatar)}
large
description={text('description', avatarDescription)}
src={text('src', defaultSrc)}
description={text('description', defaultDescription)}
status={'online'}
style={{ margin: '0.5rem' }}
/>
</div>
));
))
;
......@@ -3,12 +3,15 @@
$avatar-size-small: 20px;
$avatar-status-indicator-size-small: 10px;
$avatar-size-medium: 32px;
$avatar-status-indicator-size-medium: 12px;
$avatar-size-large: 46px;
$avatar-status-indicator-size-large: 14px;
.avatar {
flex: 0 0 auto;
background-color: $bg-color-grey;
background-color: #000000;
background-image: url(./profile.png);
background-position: right;
background-repeat: no-repeat;
......@@ -18,21 +21,7 @@ $avatar-size-large: 46px;
width: $avatar-size-medium;
height: $avatar-size-medium;
&--nobg {
background: none;
}
&--small {
width: $avatar-size-small;
height: $avatar-size-small;
}
&--large {
width: $avatar-size-large;
height: $avatar-size-large;
}
& &__image {
&__image {
width: 100%;
height: 100%;
object-fit: cover;
......@@ -45,16 +34,52 @@ $avatar-size-large: 46px;
bottom: -3px;
right: -2px;
overflow: hidden;
display: flex;
height: $avatar-status-indicator-size-medium;
width: $avatar-status-indicator-size-medium;
border-radius: 50%;
background-color: $bg-color-grey;
border: 2px solid var(--color, transparent);
&--small {
bottom: -2px;
right: -2px;
height: $avatar-status-indicator-size-small;
width: $avatar-status-indicator-size-small;
}
&--large {
bottom: -4px;
right: -2px;
height: $avatar-status-indicator-size-large;
width: $avatar-status-indicator-size-large;
}
&--status {
&-online {
background-color: $color-green;
}
&-away {
background-color: $color-yellow;
}
&-busy {
background-color: $color-red;
}
}
}
&--nobg {
background: none;
}
&--small {
width: $avatar-size-small;
height: $avatar-size-small;
}
&--large {
width: $avatar-size-large;
height: $avatar-size-large;
}
}
import { createClassName } from '../helpers';
import { createClassName, memo } from '../helpers';
import styles from './styles';
const handleMouseUp = ({ target }) => target.blur();
export const Button = ({
children,
export const Button = memo(({
submit,
disabled,
outline,
nude,
......@@ -14,13 +14,19 @@ export const Button = ({
stack,
small,
loading,
badge,
icon,
onClick,
className,
...props
style = {},
children,
}) => (
<button
{...props}
type={secondary ? 'button' : 'submit'}
type={submit ? 'submit' : 'button'}
disabled={disabled}
onClick={onClick}
onMouseUp={handleMouseUp}
aria-label={icon ? children[0] : null}
className={createClassName(styles, 'button', {
disabled,
outline,
......@@ -30,20 +36,11 @@ export const Button = ({
stack,
small,
loading,
icon: !!icon,
}, [className])}
onMouseUp={handleMouseUp}
style={style}
>
{children}
{badge ? (<span className={createClassName(styles, 'button__badge')}>{badge}</span>) : null}
{icon || children}
</button>
);
export const Group = ({ children }) => (
<div className={createClassName(styles, 'group')}>{children}</div>
);
Button.Group = Group;
export default Button;
));
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withKnobs, boolean, text } from '@storybook/addon-knobs';
import centered from '@storybook/addon-centered/react';
import { storiesOf } from '@storybook/react';
import ChatIcon from '../../icons/chat.svg';
import { Button } from '.';
import Button, { Group } from '.';
const buttonText = 'Powered by Rocket.Chat';
const defaultText = 'Powered by Rocket.Chat';
const defaultBadge = 'badged';
storiesOf('Components|Button', module)
.addDecorator(centered)
......@@ -19,9 +21,11 @@ storiesOf('Components|Button', module)
secondary={boolean('secondary', false)}
stack={boolean('stack', false)}
small={boolean('small', false)}
loading={boolean('loading', false)}
badge={text('badge', '')}
onClick={action('clicked')}
>
{text('text', buttonText)}
{text('text', defaultText)}
</Button>
))
.add('disabled', () => (
......@@ -33,9 +37,11 @@ storiesOf('Components|Button', module)
secondary={boolean('secondary', false)}
stack={boolean('stack', false)}
small={boolean('small', false)}
loading={boolean('loading', false)}
badge={text('badge', '')}
onClick={action('clicked')}
>
{text('text', buttonText)}
{text('text', defaultText)}
</Button>
))
.add('outline', () => (
......@@ -47,9 +53,11 @@ storiesOf('Components|Button', module)
secondary={boolean('secondary', false)}
stack={boolean(<