Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discrete unit input #353

Closed
wants to merge 16 commits into from
Closed
41,665 changes: 21,440 additions & 20,225 deletions docs/bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/bundle.js.map

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src-docs/src/components/guide_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,4 @@ $guideZLevelHighest: $euiZLevel9 + 1000;
@import "guide_sandbox/index";
@import "guide_section/index";
@import "guide_rule/index";
@import "twemoji/index";
4 changes: 4 additions & 0 deletions src-docs/src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ export {
GuideSection,
GuideSectionTypes,
} from './guide_section';

export {
Twemoji
} from './twemoji';
1 change: 1 addition & 0 deletions src-docs/src/components/twemoji/_index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import 'twemoji';
6 changes: 6 additions & 0 deletions src-docs/src/components/twemoji/_twemoji.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.twemoji {
height: 1em;
width: 1em;
margin: 0 .05em 0 .1em;
vertical-align: -0.1em;
}
3 changes: 3 additions & 0 deletions src-docs/src/components/twemoji/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export {
Twemoji,
} from './twemoji';
44 changes: 44 additions & 0 deletions src-docs/src/components/twemoji/twemoji.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { isEqual } from 'lodash';

import React, {
Component,
} from 'react';

import {
findDOMNode,
} from 'react-dom';

export class Twemoji extends Component {
componentWillMount() {
const script = document.createElement('script');

// a nice to have for the examples, but not something we want to bundle
script.src = 'https://twemoji.maxcdn.com/2/twemoji.min.js?2.4';
script.async = true;
script.onload = () => this.parseTwemoji();

document.body.appendChild(script);
}

componentDidMount() {
this.parseTwemoji();
}

componentDidUpdate(prevProps) {
if (!isEqual(this.props, prevProps)) {
this.parseTwemoji();
}
}

render() {
return <span {...this.props} />;
}

parseTwemoji() {
const node = findDOMNode(this);

if (window.twemoji) {
window.twemoji.parse(node, { className: 'twemoji' });
}
}
}
5 changes: 5 additions & 0 deletions src-docs/src/views/badge/badge.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export default () => (
<EuiBadge color="danger">
Danger
</EuiBadge>
&nbsp;&nbsp;

<EuiBadge color="ghost">
Ghost
</EuiBadge>
</div>

);
230 changes: 230 additions & 0 deletions src-docs/src/views/form/field_badges.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import React, {
Component,
} from 'react';

import {
EuiBadge,
EuiFormRow,
EuiFlexGroup,
EuiFlexItem,
EuiFieldBadges,
EuiCode,
} from '../../../../src/components';

import { Twemoji } from '../../components';

import makeId from '../../../../src/components/form/form_row/make_id';

export default class extends Component {
state = {
simple: ['a', 'b', 'c'],
parsing: ['a', 'b', 'c'],
transform: ['a', 'b', 'c'],
validation: ['🤯', '😒']
}

render() {
return (
<div>
{ this.renderSimpleExample() }
{ this.renderParseExample() }
{ this.renderTransformExample() }
{ this.renderValidationExample() }
</div>
);
}

renderSimpleExample() {
const { simple } = this.state;
const onInsert = value => this.setState({ simple: [...simple, value] });
const onRemove = value => this.setState({ simple: simple.filter(source => source !== value) });

return (
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label="Field with discrete values"
helpText="
Once the user presses Enter, the value is inserted to the list.
They can then click on items on that list to remove them.
"
>
<EuiFieldBadges
values={simple}
onInsert={onInsert}
onRemove={onRemove}
placeholder="Type and press Enter …"
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label="State"
helpText="These are the values realized as state."
>
<div>
{
simple.map(value => (
<span key={makeId()}>
<EuiBadge>{ value }</EuiBadge>
{ ` ` }
</span>
))
}
</div>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
);
}

renderParseExample() {
const { parsing } = this.state;
const onInsert = value => this.setState({ parsing: [...parsing, value] });
const onRemove = value => this.setState({ parsing: parsing.filter(source => source !== value) });
const parse = value => value.toUpperCase();

return (
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label="Field with custom parsing"
helpText={
<span>
Values can be parsed from the user input string by using the
<EuiCode>parse</EuiCode> prop into any plain JavaScript value or objects we need.
</span>
}
>
<EuiFieldBadges
values={parsing}
onInsert={onInsert}
onRemove={onRemove}
placeholder="Type and press Enter …"
parse={parse}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label="State"
helpText="These are the values realized as state."
>
<div>
{
parsing.map(value => (
<span key={makeId()}>
<EuiBadge>{ value }</EuiBadge>
{ ` ` }
</span>
))
}
</div>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
);
}

renderTransformExample() {
const { transform } = this.state;
const onInsert = value => this.setState({ transform: [...transform, value] });
const onRemove = value => this.setState({ transform: transform.filter(source => source !== value) });
const renderContent = value => <strong>{value.toUpperCase()}</strong>;

return (
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label="Field with custom renderContent"
helpText={
<span>
Values can be transformed into any display content with the
<EuiCode>renderContent</EuiCode> prop, while preserving the underlying data
</span>
}
>
<EuiFieldBadges
values={transform}
onInsert={onInsert}
onRemove={onRemove}
placeholder="Type and press Enter …"
renderContent={renderContent}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label="State"
helpText="These are the values realized as state."
>
<div>
{
transform.map(value => (
<span key={makeId()}>
<EuiBadge>{ value }</EuiBadge>
{ ` ` }
</span>
))
}
</div>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
);
}

renderValidationExample() {
const { validation } = this.state;
const onInsert = value => this.setState({ validation: [...validation, value] });
const onRemove = value => this.setState({ validation: validation.filter(source => source !== value) });
const validate = value => /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|[\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|[\ud83c[\ude32-\ude3a]|[\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g.test(value); // eslint-disable-line max-len

const renderContent = value => <Twemoji>{ value }</Twemoji>;

return (
<EuiFlexGroup>
<EuiFlexItem>
<EuiFormRow
label="Field with validation logic"
helpText={
<span>
User input can be validated before parsing occurs via the <EuiCode>isValid</EuiCode> prop.
Only valid input will be parsed via <EuiCode>parse</EuiCode> and raised
to <EuiCode>onInsert</EuiCode>. This prevents bad data from making its way onto your state.
</span>
}
>
<div>
<EuiFieldBadges
values={validation}
onInsert={onInsert}
onRemove={onRemove}
placeholder="Only emoji here …"
isValid={validate}
renderContent={renderContent}
/>
</div>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow
label="State"
helpText="These are the values realized as state."
>
<div>
{
validation.map(value => (
<span key={makeId()}>
<EuiBadge>{ value }</EuiBadge>
{ ` ` }
</span>
))
}
</div>
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
);
}
}
25 changes: 25 additions & 0 deletions src-docs/src/views/form/form_controls.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { without } from 'lodash';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're removing our dependency upon lodash. Could you inline these?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds great, what's the reasoning behind this? Maybe instead of making lodash a module non grato, we could switch to importing from the smaller bundles like lodash/without and so on?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We hit a problem where we were using a version of lodash incompatible with the one Kibana was using (addressed by #359). I think the best way to avoid problems like that in the future is by removing the dependency entirely... which seems viable/reasonable to me because we're mostly using some really simple lodash utilities, which should be simple to internalize and maintain. This way, lodash will no longer be a critical dependency for using EUI, which should simplify consumption.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of this approach? Are there modules in lodash you feel we really need, which we would have trouble maintaining?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a non-solution to that problem. We've hit so many problems due to the way we distribute EUI (build headaches, svg icons, now lodash, etc). As long as we refuse to bundle EUI into a drop-in thing that compiles whatever code it needs into a nicely self-contained bundle, we'll keep hitting these issues.

Sure, not using lodash wouldn't be such a huge deal, but it doesn't fix the underlying problem that just keeps spitting out issues for us to deal with.

In any case, there's enough stuff using lodash today that I'd defer the removal of lodash calls to a new PR unless it's already being actively worked on

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there modules in lodash you feel we really need, which we would have trouble maintaining?

Well there's a ton of tiny utilities in it, obviously. Some are trivial to reimplement, some aren't that trivial. None are a huge concern, obviously, this being a utility library, but why reinvent the wheel when there's this large set of finely tuned utility functions at our disposal

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In any case, there's enough stuff using lodash today that I'd defer the removal of lodash calls to a new PR unless it's already being actively worked on

SGTM


import React, {
Component,
} from 'react';
Expand All @@ -8,6 +10,7 @@ import {
EuiFieldPassword,
EuiFieldSearch,
EuiFieldText,
EuiFieldBadges,
EuiRange,
EuiRadioGroup,
EuiSelect,
Expand Down Expand Up @@ -50,6 +53,7 @@ export default class extends Component {
}],
radioIdSelected: `${idPrefix}5`,
numberInputValue: '',
badges: ['a', 'b', 'c', 'd']
};
}

Expand Down Expand Up @@ -85,6 +89,16 @@ export default class extends Component {
});
}

onInsertBadge = value => {
const added = [...this.state.badges, value];
this.setState({ badges: added });
}

onRemoveBadge = value => {
const removed = without(this.state.badges, value);
this.setState({ badges: removed });
}

render() {
return (
<div>
Expand Down Expand Up @@ -143,6 +157,17 @@ export default class extends Component {
<br />
<br />

<EuiFieldBadges
values={this.state.badges}
renderContent={value => value.toUpperCase()}
onInsert={this.onInsertBadge}
onRemove={this.onRemoveBadge}
placeholder="Type and press enter …"
/>

<br />
<br />

<EuiRange />

<br />
Expand Down
Loading