Conversation
|
Fancy! 🎉 🎉 |
cjcenizal
left a comment
There was a problem hiding this comment.
I've given a very cursory glance through this PR, but I'll have more time to look at it more closely tomorrow. From looking at the PR description, this looks really cool!
A couple things jumped out at me from my quick once-over. Also, can we add tests for these new components?
| padding: 0; | ||
| } | ||
|
|
||
| .euiFieldDiscrete-input { |
There was a problem hiding this comment.
For child elements, can we use a double underscore to concatenate the parts of the name? e.g. euiFieldDiscrete__input. We've been following the Kibana CSS style guide for our styles.
|
|
||
| const fieldClasses = classNames('euiFieldDiscrete', className); | ||
|
|
||
| const isInvalid = value !== `` && !isValid(value); |
There was a problem hiding this comment.
Why check against an empty string here? Shouldn't isValid be responsible for determining validity?
There was a problem hiding this comment.
This is internal state, an empty value means the input doesn't have anything, so we don't accept "nothing" as valid input, but marking the field as invalid because its empty would be weird? I can see how the consumer might be the one who gets to decide that :)
| const { value } = this.state; | ||
| const { isValid, parse, onInsert } = this.props; | ||
|
|
||
| if (e.key === 'Enter') { |
There was a problem hiding this comment.
Can we import the keyCodes service and use it here?
| accent: 'euiBadge--accent', | ||
| warning: 'euiBadge--warning', | ||
| danger: 'euiBadge--danger', | ||
| ghost: 'euiBadge--ghost', |
There was a problem hiding this comment.
Can we update the Badge page of the doc site with an example of this?
| inset 0 -2px 0 0 $euiColorPrimary; | ||
| } | ||
|
|
||
| @mixin euiFormControlStyleDisabled { |
There was a problem hiding this comment.
I love that you pulled these out!
cjcenizal
left a comment
There was a problem hiding this comment.
This is really cool Nico!! I love it. I have a few suggestions and some requests.
Tests
Would you mind adding Jest tests for the new components you've added?
Hover state
Currently, the hover state replaces the entire badge with the "x" icon. This makes for a tough UX for me because when I hover it I can't confirm what I'm deleting. I'm sure @snide and @cchaos can provide a strong suggestion. Off-hand, I imagine showing the "x" icon on the right side of the badge would work. Maybe keeping it permanently visible:
Long badge text
When entering long badge text, I was able to get the component to enter a broken visual state.
Maybe we should add a max-width to each badge and truncate the overflowing text? That's what they did for the example I provided in my previous point.
| import React, { Component } from 'react'; | ||
| import classNames from 'classnames'; | ||
|
|
||
| export class EuiFocusEmulator extends Component { |
There was a problem hiding this comment.
Can we add an example of this to the doc site? Perhaps in its own example page.
There was a problem hiding this comment.
And I would be out of character if I didn't suggest another name... 😄 "Emulator" confuses me... how about naming this EuiFieldProxy? Because it surfaces the visual cues of a field, driven by the "true field" beneath the surface. So it proxies the underlying field for visually communicating state to the user.
| this.setState({ hasFocus: false }); | ||
| } | ||
|
|
||
| onClick = (...rest) => { |
There was a problem hiding this comment.
I'm not sure I understand the role this plays. Can you explain it for me?
There was a problem hiding this comment.
Sure. Rather than assuming an e parameter and passing that to this.props.onClick, I call this.props.onClick(...rest) and I never have to worry about the API for onClick changing or adding parameters I didn't originally account for. Or so the theory goes
There was a problem hiding this comment.
Ah I see that makes sense. Though it would be such a breaking change to the API that I am certain it would never happen, so maybe this is being overly defensive? Using ...rest instead of e makes this code look special so I can see other people being confused by it if they come across it. Can we either change the parameter to being the more conventional event, or add a comment to explain it the way you just did for me?
| EuiFocusEmulator | ||
| } from '../focus_emulator'; | ||
|
|
||
| export class EuiFieldDiscrete extends Component { |
There was a problem hiding this comment.
Naming suggestion: how about EuiFieldBadges or EuiFieldTags? The dumber the better. This way people don't need to learn what "discrete" means in this context.
| @include euiFormControlStyleDisabled; | ||
| } | ||
|
|
||
| .euiFocusEmulator--invalid { |
There was a problem hiding this comment.
All three of these classes are currently formatted as modifiers, but they're actually states. Can we rename them?
.euiFocusEmulator-isFocused
.euiFocusEmulator-isDisabled
.euiFocusEmulator-isInvalid| @@ -0,0 +1,150 @@ | |||
| import { noop, omit } from 'lodash'; | |||
There was a problem hiding this comment.
We're removing our dependency upon lodash. Could you inline these functions? CC @uboness
| @@ -1,3 +1,5 @@ | |||
| import { without } from 'lodash'; | |||
There was a problem hiding this comment.
We're removing our dependency upon lodash. Could you inline these?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
What do you think of this approach? Are there modules in lodash you feel we really need, which we would have trouble maintaining?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
| } | ||
| }; | ||
|
|
||
| const otherProps = omit(rest, 'parse', 'onInsert'); |
There was a problem hiding this comment.
Could you explain this bit to me?
There was a problem hiding this comment.
I'm passing otherProps to the input (or your suggestion to pass them to the parent div, not sure which is better). parse and onInsert aren't using on render, so I need to exclude them from the rest bag even after destructuring props that we do use, otherwise they'd fall through to the child component
There was a problem hiding this comment.
Oh I see! Thanks, that makes sense. Could I suggest pulling them out in the original destructuring assignment on line 34 and using // eslint-disable-line no-unused-vars? I believe this is how we've been doing this kind of thing elsewhere in the codebase.
| onRemove, | ||
| className, | ||
| disabled, | ||
| ...rest |
There was a problem hiding this comment.
Typically we pass ...rest through to some part of the rendered DOM, so that the consumer can provide arbitrary HTML attributes. How about on line 71?
There was a problem hiding this comment.
See previous comment, was already passing ...otherProps to the <input />
| disabled={disabled} | ||
| invalid={isInvalid} | ||
| > | ||
| <div className={fieldClasses}> |
There was a problem hiding this comment.
My suggestion is:
<div className={fieldClasses} {...rest}>| color="ghost" | ||
| className="euiFieldDiscrete-value" | ||
| onClick={() => onRemove(value)} | ||
| > |
There was a problem hiding this comment.
Would you mind adding a data-test-subj="fieldProxyBadge" attribute to this element so we can query it in our tests?
cjcenizal
left a comment
There was a problem hiding this comment.
Had a couple more thoughts about accessibility.
| key={hash(value)} | ||
| color="ghost" | ||
| className="euiFieldDiscrete-value" | ||
| onClick={() => onRemove(value)} |
There was a problem hiding this comment.
@timroes How do you think we should support screen reader accessibility for this component? I think it might make sense to pull aria-labelledby off the props and apply it to each badge.
There was a problem hiding this comment.
@snide I think we need to solve the problem of clickable badges, e.g. by updating EuiBadge to support clicking on them or creating a new component for this role. Currently this component isn't keyboard-accessible because we didn't design for this use case.
There was a problem hiding this comment.
I think @cchaos is coming up with something awesome for this FYI.
| fullWidth: false, | ||
| isLoading: false, | ||
| isValid: () => true, | ||
| renderValue: value => String(value), |
There was a problem hiding this comment.
I think we should create an individual example page for this component. Then we can add examples of how to use this renderValue, parse, and isValid.
|
Hey @bevacqua , I can help you a bit on the UI for this. I have a couple questions:
|
| renderValue={value => value.toUpperCase()} | ||
| onInsert={this.onDiscreteInsert} | ||
| onRemove={this.onDiscreteRemove} | ||
| placeholder="Type and press enter …" |
There was a problem hiding this comment.
I would remove the ellipses. We have some writing guidelines on the usage of punctuation, in particular ellipses: https://elastic.github.io/eui/#/writing (under Punctuation).
I also noticed that in your screenshots it stuck around once someone was typing. This seems a bit of odd behavior.
There was a problem hiding this comment.
I also noticed that in your screenshots it stuck around once someone was typing. This seems a bit of odd behavior.
I just typed some more … to drive you mad, but they don't stick around 😅
| className={classes} | ||
| disabled={disabled} | ||
| invalid={invalid} | ||
| onClick={this.onClick} |
There was a problem hiding this comment.
I think it makes sense to look again at this in the next iteration, so this comment is more a note for myself.... this onClick is currently inaccessible, which can be solved in a number of ways. But I think the appropriate solution will become apparent after we address the accessibility of the pills.
* Move react-color from devDependencies to dependencies. * Update CHANGELOG.
- created a `predicate` module that exports all predicate function (e.g. `isString`, `isFunction`,...) - created `isNil` predicate - `sortable_properties` no longer uses lodash `sortBy` and uses our comparators instaed this change should enable using tables in Kibana.
I would, but I wouldn't want the component to be that smart. If I need this, I'll do it as a consumer, imo.
They can be rendered however the consumer chooses, it's completely up to the consumer |
No, but it's not a problem anymore now that the delete action shows up top right instead of overlaying |
|
@bevacqua On the Kibana side, we've found that we're able to make faster and steadier progress using EUI if we bake some of our design rules into our components. This has meant that recently we've started approaching our component design expecting them to be more opinionated than we have in the past. What has the experience regarding opinionated components been like on your side? Have you had cases where greater flexibility has been advantageous? In terms of this component, I'd like to propose this solution, which I think will strike the right balance between defaulting to an opinionated visual state (and thereby a simpler interface) while still offering an escape hatch for consumers who want greater control:
Thoughts? |
|
This is getting out of hand, unless we can move those requests to a future iteration I'll need to pull this code into Cloud and come back to this PR in the future. |
|
Closing for now |
|
Sorry, Nico! Don't want to hold you up, but I also want to be a good shepherd to EUI. :) Feel free to put this on hold if you have to -- we'll probably need a little time to update EuiBadge to support our new design anyway. Not that it matters, but I think most of my comments mostly entailed moving the parsing code out of the component into the consumer, which seems like mostly cut-and-pasting...? Or not, I could be wrong. |








A new kind of input field that conforms to our design guidelines but allows the user to add or remove discrete values.
Usage example where
this.state.discreteValuesis['a', 'b', 'c', 'd'], and insert/remove modify said state object via React'ssetState:isValidprop can be provided, and should returntrueonly when an input string would be validparseprop you can provide, which should worry about taking the plain text user input and mapping it into whatever value we actually need for rendering and for our collection of discrete valuesrenderValueand return whatever you want to display each itemThe input renders the list of values using the
renderValueprop, or just casting them to strings.On hover, items show a cross that signals they can be removed.
When clicking on the input, the entire discrete input gets focus styles, and not just the actual input element
The focus behavior is enabled thanks to a second component introduced in this PR,
EuiFocusEmulator, meant to be used internally by EUI, which makes sure to transfer focus/disabled/invalid state and styles to the parent when the input gets one of those states.Usage of
<EuiFocusEmulator>is below. The<input />doesn't need to be inside the emulator, but when the input is clicked, the<div>emitted by the emulator will get the styles that were meant for the input. Note that it should be a raw input, not a<EuiFieldText>or such, since the emulator is meant to be the target of our styling, so this component is meant mostly to be reused in cases like this where we have an actual input inside a component that's supposed to say "hey, this whole thing has focus", not just the input itself, which would look bad"/cc @andrew-moldovan @gjones