Skip to content
Snippets Groups Projects
Unverified Commit 44918b4d authored by Tiago Evangelista Pinto's avatar Tiago Evangelista Pinto Committed by GitHub
Browse files

Chore: Add Livechat repo into Monorepo packages (#25312)

parent 573e6d46
No related branches found
No related tags found
No related merge requests found
Showing
with 1156 additions and 0 deletions
This diff is collapsed.
import ascii, { asciiRegexp } from './ascii';
import emojis from './emojis';
const shortnamePattern = new RegExp(/:[-+_a-z0-9]+:/, 'gi');
const replaceShortNameWithUnicode = (shortname) => emojis[shortname] || shortname;
const regAscii = new RegExp(`((\\s|^)${ asciiRegexp }(?=\\s|$|[!,.?]))`, 'gi');
const unescapeHTML = (string) => {
const unescaped = {
'&': '&',
'&': '&',
'&': '&',
'&lt;': '<',
'&#60;': '<',
'&#x3C;': '<',
'&gt;': '>',
'&#62;': '>',
'&#x3E;': '>',
'&quot;': '"',
'&#34;': '"',
'&#x22;': '"',
'&apos;': '\'',
'&#39;': '\'',
'&#x27;': '\'',
};
return string.replace(/&(?:amp|#38|#x26|lt|#60|#x3C|gt|#62|#x3E|apos|#39|#x27|quot|#34|#x22);/ig, (match) => unescaped[match]);
};
const shortnameToUnicode = (stringMessage) => {
stringMessage = stringMessage.replace(shortnamePattern, replaceShortNameWithUnicode);
stringMessage = stringMessage.replace(regAscii, (entire, m1, m2, m3) => {
if (!m3 || !(unescapeHTML(m3) in ascii)) {
return entire;
}
m3 = unescapeHTML(m3);
return ascii[m3];
});
return stringMessage;
};
export default shortnameToUnicode;
import { Component } from 'preact';
import { createClassName } from '../helpers';
import styles from './styles.scss';
const escapeForRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
export class FilesDropTarget extends Component {
state = {
dragLevel: 0,
}
handleInputRef = (ref) => {
this.input = ref;
}
handleDragOver = (event) => {
event.preventDefault();
}
handleDragEnter = (event) => {
event.preventDefault();
this.setState({ dragLevel: this.state.dragLevel + 1 });
}
handleDragLeave = (event) => {
event.preventDefault();
this.setState({ dragLevel: this.state.dragLevel - 1 });
}
handleDrop = (event) => {
event.preventDefault();
let { dragLevel } = this.state;
if (dragLevel === 0) {
return;
}
dragLevel = 0;
this.setState({ dragLevel });
this.handleUpload(event.dataTransfer.files);
}
handleInputChange = (event) => {
this.handleUpload(event.currentTarget.files);
}
handleUpload = (files) => {
const { accept, multiple, onUpload } = this.props;
if (!onUpload) {
return;
}
let filteredFiles = Array.from(files);
if (accept) {
const acceptMatchers = accept.split(',')
.map((acceptString) => {
if (acceptString.charAt(0) === '.') {
return ({ name }) => new RegExp(`${ escapeForRegExp(acceptString) }$`, 'i').test(name);
}
const matchTypeOnly = /^(.+)\/\*$/i.exec(acceptString);
if (matchTypeOnly) {
return ({ type }) => new RegExp(`^${ escapeForRegExp(matchTypeOnly[1]) }/.*$`, 'i').test(type);
}
return ({ type }) => new RegExp(`^s${ escapeForRegExp(acceptString) }$`, 'i').test(type);
});
filteredFiles = filteredFiles.filter((file) => acceptMatchers.some((acceptMatcher) => acceptMatcher(file)));
}
if (!multiple) {
filteredFiles = filteredFiles.slice(0, 1);
}
filteredFiles.length && onUpload(filteredFiles);
}
browse = () => {
this.input.click();
}
render = ({
overlayed,
overlayText,
accept,
multiple,
className,
style = {},
children,
}, { dragLevel }) => (
<div
data-overlay-text={overlayText}
onDragOver={this.handleDragOver}
onDragEnter={this.handleDragEnter}
onDragLeave={this.handleDragLeave}
onDrop={this.handleDrop}
className={createClassName(styles, 'drop', { overlayed, dragover: dragLevel > 0 }, [className])}
style={style}
>
<input
ref={this.handleInputRef}
type='file'
accept={accept}
multiple={multiple}
onChange={this.handleInputChange}
className={createClassName(styles, 'drop__input')}
/>
{children}
</div>
)
}
import { action } from '@storybook/addon-actions';
import { withKnobs, boolean, button, text } from '@storybook/addon-knobs';
import { storiesOf } from '@storybook/react';
import { FilesDropTarget } from '.';
import { centered } from '../../helpers.stories';
const DummyContent = () => (
<div
style={{
display: 'flex',
width: '100vw',
height: '100vh',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
}}
>
Drop files here
<span style={{ padding: '1rem' }}>Or into this span</span>
</div>
);
storiesOf('Components/FilesDropTarget', module)
.addDecorator(centered)
.addDecorator(withKnobs)
.add('default', () => (
<FilesDropTarget
overlayed={boolean('overlayed', false)}
overlayText={text('overlayText', '')}
accept={text('accept', '')}
multiple={boolean('multiple', false)}
onUpload={action('upload')}
>
<DummyContent />
</FilesDropTarget>
))
.add('overlayed', () => (
<FilesDropTarget
overlayed={boolean('overlayed', true)}
overlayText={text('overlayText', '')}
accept={text('accept', '')}
multiple={boolean('multiple', false)}
onUpload={action('upload')}
>
<DummyContent />
</FilesDropTarget>
))
.add('overlayed with text', () => (
<FilesDropTarget
overlayed={boolean('overlayed', true)}
overlayText={text('overlayText', 'You can release your files now')}
accept={text('accept', '')}
multiple={boolean('multiple', false)}
onUpload={action('upload')}
>
<DummyContent />
</FilesDropTarget>
))
.add('accepting only images', () => (
<FilesDropTarget
overlayed={boolean('overlayed', false)}
overlayText={text('overlayText', '')}
accept={text('accept', 'image/*')}
multiple={boolean('multiple', false)}
onUpload={action('upload')}
>
<DummyContent />
</FilesDropTarget>
))
.add('accepting multiple', () => (
<FilesDropTarget
overlayed={boolean('overlayed', false)}
overlayText={text('overlayText', '')}
accept={text('accept', '')}
multiple={boolean('multiple', true)}
onUpload={action('upload')}
>
<DummyContent />
</FilesDropTarget>
))
.add('triggering browse action', () => {
let filesDropTarget;
function handleRef(ref) {
filesDropTarget = ref;
}
button('Browse for files', () => {
filesDropTarget.browse();
});
return (
<FilesDropTarget
ref={handleRef}
overlayed={boolean('overlayed', false)}
overlayText={text('overlayText', '')}
accept={text('accept', '')}
multiple={boolean('multiple', false)}
onUpload={action('upload')}
/>
);
});
@import '../../styles/colors';
@import '../../styles/variables';
$drop-overlay-animation-time: $default-time-animation;
$drop-overlay-background-color: rgba($bg-color-white, 0.9);
$drop-overlay-gap: $default-padding;
$drop-overlay-border-width: 4px;
$drop-overlay-border-style: dashed;
$drop-overlay-text-font-size: 1.375rem;
.drop {
position: relative;
display: flex;
overflow: hidden;
flex-direction: column;
flex: 1 1 auto;
&.drop--overlayed.drop--dragover {
&::before {
position: absolute;
z-index: 10;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: "";
animation: fadein $drop-overlay-animation-time;
pointer-events: none;
background-color: $drop-overlay-background-color;
}
&::after {
position: absolute;
z-index: 10;
top: $drop-overlay-gap;
right: $drop-overlay-gap;
bottom: $drop-overlay-gap;
left: $drop-overlay-gap;
display: flex;
box-sizing: border-box;
padding: $drop-overlay-gap;
content: attr(data-overlay-text) "";
animation: fadein $drop-overlay-animation-time;
text-align: center;
pointer-events: none;
color: var(--color, $color-blue);
border: $drop-overlay-border-width var(--color, $color-blue) $drop-overlay-border-style;
font-size: $drop-overlay-text-font-size;
font-weight: 500;
align-items: center;
justify-content: center;
}
}
&__input {
display: none;
}
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
import { withTranslation } from 'react-i18next';
import { PopoverMenu } from '../Menu';
import { createClassName } from '../helpers';
import Logo from './logo.svg';
import styles from './styles.scss';
export const Footer = ({ children, className, ...props }) => (
<footer className={createClassName(styles, 'footer', {}, [className])} {...props}>
{children}
</footer>
);
export const FooterContent = ({ children, className, ...props }) => (
<div className={createClassName(styles, 'footer__content', {}, [className])} {...props}>
{children}
</div>
);
export const PoweredBy = withTranslation()(({ className, t, ...props }) => (
<h3 className={createClassName(styles, 'powered-by', {}, [className])} {...props}>
{t('powered_by_rocket_chat').split('Rocket.Chat')[0]}
<a href='https://rocket.chat' target='_blank' rel='noopener noreferrer'>
<Logo className={createClassName(styles, 'powered-by__logo')} width={60} height={60 * 272 / 1500} role='img' aria-label='Rocket.Chat' />
</a>
{t('powered_by_rocket_chat').split('Rocket.Chat')[1]}
</h3>
));
const handleMouseUp = ({ target }) => target.blur();
const OptionsTrigger = withTranslation()(({ pop, t }) => (
<button className={createClassName(styles, 'footer__options')} onClick={pop} onMouseUp={handleMouseUp}>
{t('options')}
</button>
));
export const FooterOptions = ({ children }) => (
<PopoverMenu trigger={OptionsTrigger} overlayed>
{children}
</PopoverMenu>
);
export const CharCounter = ({ className, style = {}, textLength, limitTextLength }) => (
<span
className={createClassName(styles, 'footer__remainder', { highlight: textLength === limitTextLength }, [className])}
style={style}
>
{textLength} / {limitTextLength}
</span>
);
<?xml version="1.0" encoding="UTF-8"?>
<svg version="1.1" viewBox="0 0 1500 272" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<g transform="translate(367.73 44.373)" fill-rule="nonzero">
<path class="text" d="m93.858 88.273c0 15.237-5.6869 25.243-16.607 30.016l15.699 59.582c0.68116 2.7339-0.68116 4.092-3.1858 4.092h-23.663c-2.2741 0-3.4093-1.1347-3.867-3.1842l-15.244-57.759h-15.699v57.309c0 2.2765-1.3623 3.6346-3.6399 3.6346h-23.663c-2.2741 0-3.6399-1.3652-3.6399-3.6346v-174.65c0-2.2729 1.3659-3.6381 3.6399-3.6381h57.107c21.385 0 32.763 11.372 32.763 32.746v55.49zm-40.043 2.7268c5.914 0 9.0998-3.1842 9.0998-9.0952v-42.753c0-5.911-3.1858-9.0917-9.0998-9.0917h-22.524v60.943l22.524-0.0035459z"/>
<path class="text" d="m112.05 32.783c0-21.375 11.374-32.746 32.763-32.746h25.483c21.385 0 32.763 11.372 32.763 32.746v116.43c0 21.371-11.377 32.743-32.763 32.743h-25.483c-21.389 0-32.763-11.372-32.763-32.743v-116.43zm52.555 119.84c5.914 0 9.0998-2.9573 9.0998-9.0952v-105.06c0-5.911-3.1858-9.0952-9.0998-9.0952h-13.194c-5.914 0-9.0998 3.1842-9.0998 9.0952v105.06c0 6.1344 3.1858 9.0917 9.0998 9.0917h13.194z"/>
<path class="text" d="m317.03 57.342c0 2.2765-1.3659 3.6381-3.6364 3.6381h-22.751c-2.5047 0-3.6399-1.3616-3.6399-3.6381v-18.19c0-5.911-3.1823-9.0917-9.0963-9.0917h-11.832c-6.141 0-9.0998 3.1807-9.0998 9.0917v103.7c0 6.138 3.1823 9.0881 9.0998 9.0881h11.832c5.914 0 9.0963-2.9537 9.0963-9.0881v-18.198c0-2.2765 1.1353-3.6381 3.6399-3.6381h22.751c2.2812 0 3.6364 1.3616 3.6364 3.6381v24.562c0 21.371-11.604 32.743-32.759 32.743h-25.483c-21.385 0-32.99-11.372-32.99-32.743v-116.44c0-21.375 11.604-32.746 32.99-32.746h25.483c21.158 0 32.759 11.372 32.759 32.746v24.559z"/>
<path class="text" d="m413.03 181.96c-2.7353 0-4.3211-1.1347-5.2364-3.4076l-28.662-67.542-8.4222 16.148v50.253c0 2.9573-1.5894 4.5494-4.5481 4.5494h-21.843c-2.9588 0-4.5517-1.5921-4.5517-4.5494v-172.83c0-2.9537 1.5929-4.5494 4.5517-4.5494h21.843c2.9552 0 4.5481 1.5921 4.5481 4.5494v70.496l35.037-71.634c1.1388-2.2729 2.7353-3.4112 5.2364-3.4112h23.887c3.4129 0 4.7787 2.2765 3.1823 5.4571l-38.673 79.364 41.174 91.874c1.5929 2.9573 0.22705 5.2302-3.4093 5.2302h-24.114z"/>
<path class="text" d="m547.94 26.418c0 2.2729-0.91176 3.865-3.6399 3.865h-56.88v45.48h43.456c2.2812 0 3.6399 1.3652 3.6399 3.865v22.513c0 2.5034-1.3659 3.8686-3.6399 3.8686h-43.456v45.934h56.88c2.7353 0 3.6399 1.1382 3.6399 3.6381v22.743c0 2.2729-0.91176 3.631-3.6399 3.631h-83.725c-2.0506 0-3.4164-1.3652-3.4164-3.631v-174.65c0-2.2729 1.3659-3.6381 3.4164-3.6381h83.725c2.7353 0 3.6399 1.3652 3.6399 3.6381v22.743z"/>
<path class="text" d="m653.5 0.036637c2.5011 0 3.6399 1.3652 3.6399 3.6381v22.743c0 2.2729-1.1388 3.6381-3.6399 3.6381h-26.391v148.27c0 2.4999-1.1353 3.631-3.6364 3.631h-23.663c-2.2812 0-3.6399-1.1311-3.6399-3.631v-148.27h-26.388c-2.2812 0-3.6399-1.3652-3.6399-3.6381v-22.743c0-2.2729 1.3659-3.6381 3.6399-3.6381h83.718z"/>
<path class="text" d="m654.88 156.53c0-2.9573 1.5929-4.5458 4.5517-4.5458h20.704c2.9588 0 4.5481 1.5886 4.5481 4.5458v20.917c0 2.9608-1.5894 4.5494-4.5481 4.5494h-20.704c-2.9588 0-4.5517-1.5886-4.5517-4.5494v-20.917z"/>
<path class="text" d="m798.63 57.342c0 2.2765-1.3694 3.6381-3.6399 3.6381h-22.751c-2.5011 0-3.6364-1.3616-3.6364-3.6381v-18.19c0-5.911-3.1823-9.0917-9.0963-9.0917h-11.832c-6.1446 0-9.0998 3.1807-9.0998 9.0917v103.7c0 6.138 3.1823 9.0881 9.0998 9.0881h11.832c5.914 0 9.0963-2.9537 9.0963-9.0881v-18.198c0-2.2765 1.1353-3.6381 3.6364-3.6381h22.751c2.2812 0 3.6399 1.3616 3.6399 3.6381v24.562c0 21.371-11.604 32.743-32.763 32.743h-25.483c-21.385 0-32.99-11.372-32.99-32.743v-116.44c0-21.375 11.604-32.746 32.99-32.746h25.483c21.162 0 32.763 11.372 32.763 32.746v24.559z"/>
<path class="text" d="m881.44 3.6747c0-2.2729 1.3623-3.6381 3.6364-3.6381h23.432c2.7317 0 3.8634 1.3652 3.8634 3.6381v174.65c0 2.2729-1.1353 3.631-3.8634 3.631h-23.432c-2.2812 0-3.6364-1.3652-3.6364-3.631v-72.315h-29.123v72.319c0 2.2765-1.3659 3.6346-3.6399 3.6346h-23.429c-2.7353 0-3.8705-1.3652-3.8705-3.6346v-174.65c0-2.2729 1.1353-3.6381 3.8705-3.6381h23.429c2.2812 0 3.6399 1.3652 3.6399 3.6381v72.315h29.123v-72.315z"/>
<path class="text" d="m1016.1 181.96c-2.047 0-3.1823-1.1347-3.6399-3.1842l-6.3681-33.197h-40.5l-6.1375 33.197c-0.45765 2.0495-1.5929 3.1842-3.6399 3.1842h-24.341c-2.5011 0-3.6399-1.3652-2.9623-3.865l37.769-174.88c0.45765-2.2729 1.82-3.1842 3.867-3.1842h31.628c2.047 0 3.4129 0.9113 3.867 3.1842l37.769 174.88c0.45765 2.4999-0.45411 3.865-3.1823 3.865h-24.128zm-30.262-142.13l-14.56 79.364h29.123l-14.563-79.364z"/>
<path class="text" d="m1128.5 0.036637c2.5011 0 3.6399 1.3652 3.6399 3.6381v22.743c0 2.2729-1.1388 3.6381-3.6399 3.6381h-26.388v148.27c0 2.4999-1.1388 3.631-3.6399 3.631h-23.663c-2.2741 0-3.6364-1.1311-3.6364-3.631v-148.27h-26.388c-2.2776 0-3.6364-1.3652-3.6364-3.6381v-22.743c0-2.2729 1.3659-3.6381 3.6364-3.6381h83.715z"/>
</g>
<path class="rocket" d="m270.5 105.32l0.004003 0.006248c-6.67e-4 -0.001041-0.001334-0.002083-0.002002-0.003124-6.67e-4 -0.001041-0.001334-0.002083-0.002001-0.003124zm-177.56-93.85c9.5081 5.2808 18.496 11.962 26.171 19.388 12.373-2.2409 25.13-3.3709 38.072-3.3709 38.744 0 75.477 10.164 103.42 28.613 14.473 9.5589 25.977 20.9 34.189 33.712 9.1447 14.276 13.78 29.629 13.78 46.079 0 16.008-4.6355 31.366-13.78 45.64-8.2114 12.818-19.715 24.157-34.189 33.716-27.948 18.449-64.678 28.607-103.42 28.607-12.942 0-25.697-1.13-38.072-3.3677-7.6786 7.423-16.663 14.108-26.171 19.388-50.802 25.443-92.931 0.59843-92.931 0.59843s39.169-33.254 32.799-62.405c-17.525-17.962-27.021-39.627-27.021-62.612 0-22.552 9.4989-44.217 27.021-62.182 6.3685-29.143-32.786-62.391-32.799-62.402 0.012528-0.0073926 42.136-24.844 92.931 0.59842z" fill="#DB2323" fill-rule="nonzero" />
<path d="m62.545 186.04c-17.412-13.722-27.863-31.281-27.863-50.419 0-43.916 55.032-79.517 122.92-79.517 67.885 0 122.92 35.601 122.92 79.517 0 43.916-55.032 79.517-122.92 79.517-16.731 0-32.681-2.1625-47.219-6.079l-10.629 10.251c-5.7752 5.5698-12.544 10.61-19.6 14.582-9.3523 4.5928-18.588 7.0986-27.725 7.8632 0.51543-0.93708 0.99005-1.8864 1.4996-2.8249 10.65-19.618 13.523-37.248 8.6189-52.89z" fill="#fff"/>
<path class="rocket" d="m98.656 154.54c-9.9802 0-18.071-8.2205-18.071-18.361 0-10.141 8.0905-18.361 18.071-18.361 9.9802 0 18.071 8.2205 18.071 18.361 0 10.141-8.0905 18.361-18.071 18.361zm58.179 0c-9.9802 0-18.071-8.2205-18.071-18.361 0-10.141 8.0905-18.361 18.071-18.361s18.071 8.2205 18.071 18.361c0 10.141-8.0905 18.361-18.071 18.361zm58.179 0c-9.9802 0-18.071-8.2205-18.071-18.361 0-10.141 8.0905-18.361 18.071-18.361 9.9802 0 18.071 8.2205 18.071 18.361 0 10.141-8.0905 18.361-18.071 18.361z" fill="#DB2323" fill-rule="nonzero"/>
</g>
</svg>
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import '../../i18next';
import { Footer, FooterContent, FooterOptions, PoweredBy } from '.';
import ChangeIcon from '../../icons/change.svg';
import FinishIcon from '../../icons/finish.svg';
import RemoveIcon from '../../icons/remove.svg';
import { Composer } from '../Composer';
import Menu from '../Menu';
import { PopoverContainer } from '../Popover';
const bottomWithPopoverContainer = (storyFn) => (
<div style={{ display: 'flex', width: '100vw', height: '100vh' }}>
<PopoverContainer>
<div style={{ flex: '1' }} />
{storyFn()}
</PopoverContainer>
</div>
);
storiesOf('Components/Footer', module)
.addDecorator(bottomWithPopoverContainer)
.add('simple', () => (
<Footer>
<FooterContent>
<PoweredBy />
</FooterContent>
</Footer>
))
.add('with Composer and options', () => (
<Footer>
<FooterContent>
<Composer placeholder='Insert your text here' />
</FooterContent>
<FooterContent>
<FooterOptions>
<Menu.Group>
<Menu.Item onClick={action('change-department')} icon={ChangeIcon}>Change department</Menu.Item>
<Menu.Item onClick={action('remove-user-data')} icon={RemoveIcon}>Forget/Remove my personal data</Menu.Item>
<Menu.Item danger onClick={action('finish-chat')} icon={FinishIcon}>Finish this chat</Menu.Item>
</Menu.Group>
</FooterOptions>
<PoweredBy />
</FooterContent>
</Footer>
));
@import '../../styles/colors';
@import '../../styles/helpers';
@import '../../styles/variables';
.footer {
display: flex;
flex-direction: column;
flex: 0 0 auto;
width: 100%;
padding: 4px 8px;
color: $color-text-grey;
font-size: 0.625rem;
align-items: stretch;
justify-content: space-between;
&__content {
display: flex;
padding: 4px 8px;
}
&__options {
padding: 0;
cursor: pointer;
user-select: none;
transition: trasform $default-time-animation;
text-align: left;
letter-spacing: 0.2px;
color: $color-text-grey;
border: none;
background: none;
font-size: 0.625rem;
font-weight: bold;
line-height: 1;
&:hover,
&:focus {
color: black;
}
@include pressable-button(2px);
}
&__remainder {
min-width: 100px;
margin-left: 10px;
font-weight: bold;
&--highlight {
transition: color 0.2s;
color: $color-red;
}
}
}
.powered-by {
width: 100%;
margin: 0;
user-select: none;
text-align: end;
font-size: 10px;
font-weight: 500;
align-self: flex-end;
.powered-by__logo {
margin: 0 5px;
vertical-align: middle;
:global(.text) {
fill: #{$color-text-grey};
}
&:hover :global(.text) {
fill: #2f343d;
}
:global(.rocket) {
fill: #{$color-text-grey};
}
&:hover :global(.rocket) {
fill: #db2323;
}
}
}
import { createClassName, memo } from '../../helpers';
import styles from './styles.scss';
const DateInput = ({
name,
value,
placeholder,
disabled,
small,
error,
onChange,
onInput,
className,
style = {},
}) => (
<input
type='date'
name={name}
value={value}
placeholder={placeholder}
disabled={disabled}
onChange={onChange}
onInput={onInput}
className={createClassName(styles, 'date-input', { disabled, error, small }, [className])}
style={style}
/>
);
export default memo(DateInput);
import { action } from '@storybook/addon-actions';
import { withKnobs, boolean, text } from '@storybook/addon-knobs';
import { storiesOf } from '@storybook/react';
import DateInput from '.';
import { Form, FormField } from '..';
storiesOf('Forms/DateInput', module)
.addDecorator(withKnobs)
.addParameters({
layout: 'centered',
})
.add('default', () => (
<Form>
<FormField>
<DateInput
value={text('value', '')}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', false)}
small={boolean('small', false)}
error={boolean('error', false)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
))
.add('filled', () => (
<Form>
<FormField>
<DateInput
value={text('value', 'Value')}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', false)}
small={boolean('small', false)}
error={boolean('error', false)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
))
.add('disabled', () => (
<Form>
<FormField>
<DateInput
value={text('value', 'Value')}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', true)}
small={boolean('small', false)}
error={boolean('error', false)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
))
.add('small', () => (
<Form>
<FormField>
<DateInput
value={text('value', 'Value')}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', false)}
small={boolean('small', true)}
error={boolean('error', false)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
))
.add('with error', () => (
<Form>
<FormField>
<DateInput
value={text('value', 'Value')}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', false)}
small={boolean('small', false)}
error={boolean('error', true)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
));
@import '../mixins';
.date-input {
@include form__input-box;
}
import { cloneElement } from 'preact';
import { createClassName } from '../../helpers';
import styles from './styles.scss';
export const FormField = ({
required,
label,
description,
error,
className,
style = {},
children,
}) => (
<div
className={createClassName(styles, 'form-field', { required, error: !!error }, [className])}
style={style}
>
<label className={createClassName(styles, 'form-field__label-wrapper')}>
{label
? <span className={createClassName(styles, 'form-field__label')}>{label}</span>
: null}
<span className={createClassName(styles, 'form-field__input')}>
{error
? (Array.isArray(children) ? children : [children])
.map((child) => cloneElement(child, { error: !!error }))
: children}
</span>
</label>
<small className={createClassName(styles, 'form-field__description')}>
{error || description}
</small>
</div>
);
import { withKnobs, boolean, text } from '@storybook/addon-knobs';
import { storiesOf } from '@storybook/react';
import { FormField } from '.';
import { Form, TextInput } from '..';
import { loremIpsum, centered } from '../../../helpers.stories';
storiesOf('Forms/FormField', module)
.addDecorator(centered)
.addDecorator(withKnobs)
.add('normal', () => (
<Form>
<FormField
required={boolean('required', false)}
label={text('label', 'Label')}
description={text('description', 'Description')}
error={text('error', '')}
>
<TextInput value={loremIpsum({ count: 3, units: 'words' })} />
</FormField>
</Form>
))
.add('required', () => (
<Form>
<FormField
required={boolean('required', true)}
label={text('label', 'Label')}
description={text('description', 'Description')}
error={text('error', '')}
>
<TextInput value={loremIpsum({ count: 3, units: 'words' })} />
</FormField>
</Form>
))
.add('with error', () => (
<Form>
<FormField
required={boolean('required', false)}
label={text('label', 'Label')}
description={text('description', 'Description')}
error={text('error', 'Error')}
>
<TextInput value={loremIpsum({ count: 3, units: 'words' })} />
</FormField>
</Form>
));
@import '../../../styles/colors';
@import '../../../styles/variables';
$form-field-label-color: $color-text-dark;
$form-field-label-error-color: $color-red;
$form-field-label-font-size: 0.75rem;
$form-field-label-font-weight: 600;
$form-field-label-line-height: 1rem;
$form-field-description-color: $color-text-grey;
$form-field-description-font-size: 0.75rem;
$form-field-description-font-weight: 500;
$form-field-description-line-height: 1rem;
$form-field-error-color: $color-red;
$form-field-error-border-color: $color-red;
.form-field {
display: flex;
width: 100%;
margin: 5px 0;
flex-flow: column nowrap;
&__label-wrapper {
display: flex;
flex: 1 0 auto;
flex-flow: column nowrap;
}
&__label,
&__input,
&__description {
margin: 3px 0;
}
&__label {
flex: 0 0 auto;
transition: color $default-time-animation;
text-align: left;
white-space: nowrap;
letter-spacing: 0;
text-overflow: ellipsis;
color: $form-field-label-color;
font-size: $form-field-label-font-size;
font-weight: $form-field-label-font-weight;
line-height: $form-field-label-line-height;
}
&__input {
display: flex;
flex: 1 0 auto;
}
&__description {
flex: 0 0 auto;
min-height: $form-field-description-line-height;
transition: color $default-time-animation;
color: $form-field-description-color;
font-size: $form-field-description-font-size;
font-weight: $form-field-description-font-weight;
line-height: $form-field-description-line-height;
}
&--error {
.form-field__label,
.form-field__input,
.form-field__description {
color: $form-field-error-color;
}
}
&--required {
.form-field__label::after {
content: " *";
}
}
}
import { createClassName, memo } from '../../helpers';
import styles from './styles.scss';
export const PasswordInput = memo(({
name,
value,
placeholder,
disabled,
small,
error,
onChange,
onInput,
className,
style = {},
}) => (
<input
type='password'
name={name}
value={value}
placeholder={placeholder}
disabled={disabled}
onChange={onChange}
onInput={onInput}
className={createClassName(styles, 'password-input', { disabled, error, small }, [className])}
style={style}
/>
));
import { action } from '@storybook/addon-actions';
import { withKnobs, boolean, text } from '@storybook/addon-knobs';
import { storiesOf } from '@storybook/react';
import { PasswordInput } from '.';
import { Form, FormField } from '..';
import { centered } from '../../../helpers.stories';
storiesOf('Forms/PasswordInput', module)
.addDecorator(centered)
.addDecorator(withKnobs)
.add('default', () => (
<Form>
<FormField>
<PasswordInput
value={text('value', '')}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', false)}
small={boolean('small', false)}
error={boolean('error', false)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
))
.add('filled', () => (
<Form>
<FormField>
<PasswordInput
value={text('value', 'Value')}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', false)}
small={boolean('small', false)}
error={boolean('error', false)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
))
.add('disabled', () => (
<Form>
<FormField>
<PasswordInput
value={text('value', 'Value')}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', true)}
small={boolean('small', false)}
error={boolean('error', false)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
))
.add('small', () => (
<Form>
<FormField>
<PasswordInput
value={text('value', 'Value')}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', false)}
small={boolean('small', true)}
error={boolean('error', false)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
))
.add('with error', () => (
<Form>
<FormField>
<PasswordInput
value={text('value', 'Value')}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', false)}
small={boolean('small', false)}
error={boolean('error', true)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
));
@import '../mixins';
.password-input {
@include form__input-box;
}
import { Component } from 'preact';
import ArrowIcon from '../../../icons/arrowDown.svg';
import { createClassName } from '../../helpers';
import styles from './styles.scss';
export class SelectInput extends Component {
static getDerivedStateFromProps(props, state) {
if (props.value !== state.value) {
return { value: props.value };
}
return null;
}
state = {
value: this.props.value,
}
handleChange = (event) => {
const { onChange } = this.props;
onChange && onChange(event);
if (event.defaultPrevented) {
return;
}
this.setState({ value: event.target.value });
}
render = ({
name,
placeholder,
options = [],
disabled,
small,
error,
onInput,
className,
style = {},
...props
}) => (
<div
className={createClassName(styles, 'select-input', {}, [className])}
style={style}
>
<select
name={name}
value={this.state.value}
disabled={disabled}
onChange={this.handleChange}
onInput={onInput}
className={createClassName(styles, 'select-input__select', {
disabled,
error,
small,
placeholder: !this.state.value,
})}
{...props}
>
<option value='' disabled hidden>{placeholder}</option>
{Array.from(options).map(({ value, label }, key) => (
<option key={key} value={value} className={createClassName(styles, 'select-input__option')}>{label}</option>
))}
</select>
<ArrowIcon className={createClassName(styles, 'select-input__arrow')} />
</div>
)
}
import { action } from '@storybook/addon-actions';
import { withKnobs, boolean, object, text } from '@storybook/addon-knobs';
import { storiesOf } from '@storybook/react';
import { SelectInput } from '.';
import { Form, FormField } from '..';
import { centered } from '../../../helpers.stories';
storiesOf('Forms/SelectInput', module)
.addDecorator(centered)
.addDecorator(withKnobs)
.add('empty', () => (
<Form>
<FormField>
<SelectInput
value={text('value', '')}
options={object('options', [
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
])}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', false)}
small={boolean('small', false)}
error={boolean('error', false)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
))
.add('selected', () => (
<Form>
<FormField>
<SelectInput
value={text('value', '2')}
options={object('options', [
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
])}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', false)}
small={boolean('small', false)}
error={boolean('error', false)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
))
.add('disabled', () => (
<Form>
<FormField>
<SelectInput
value={text('value', '2')}
options={object('options', [
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
])}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', true)}
small={boolean('small', false)}
error={boolean('error', false)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
))
.add('small', () => (
<Form>
<FormField>
<SelectInput
value={text('value', '2')}
options={object('options', [
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
])}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', false)}
small={boolean('small', true)}
error={boolean('error', false)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
))
.add('with error', () => (
<Form>
<FormField>
<SelectInput
value={text('value', '2')}
options={object('options', [
{ value: '1', label: 'Option 1' },
{ value: '2', label: 'Option 2' },
{ value: '3', label: 'Option 3' },
])}
placeholder={text('placeholder', 'Placeholder')}
disabled={boolean('disabled', false)}
small={boolean('small', false)}
error={boolean('error', true)}
onChange={action('change')}
onInput={action('input')}
/>
</FormField>
</Form>
));
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment