Conversation
BrendanAnnable
left a comment
There was a problem hiding this comment.
Nice!
Gave it a quick skim, I'll have another deeper look soon 🙂
| ) | ||
|
|
||
| return ( | ||
| <OutsideClickHandler onOutsideClick={() => this.close()}> |
There was a problem hiding this comment.
This render method currently creates functions every time it executes, this is generally to be avoided for performance reasons, ideally it should create none.
Aside: We should probably add the jsx-no-lambda tslint rule from https://github.com/palantir/tslint-react to make it easy to spot 🙂
Event handlers can be fixed by using the @action.bound annotation instead of just @action, then you can just refer to this.close here instead.
The child items (e.g. which use () => this.onSelect(option)) can be solved by splitting out each item into its own react component, such that each item has its own @action.bound onSelect() {} method that can access its own props. I can give an example if that makes no sense.
There was a problem hiding this comment.
That makes sense 👍. I'll update to use @action.bound, and extract a standalone select option component.
| private removeListeners?: () => void | ||
|
|
||
| componentDidMount() { | ||
| const onKeydown = (event: KeyboardEvent) => this.onDocumentKeydown(event) |
There was a problem hiding this comment.
Just use property syntax on the method, then it doesn't need to be rebound here. e.g.
private readonly onDocumentKeydown = (event: KeyboardEvent) => {
// ...
}| @observer | ||
| export class Select extends React.Component<SelectProps> { | ||
| @observable | ||
| private isOpen: boolean = false |
There was a problem hiding this comment.
Do you think we will ever want to control the open-state of this component from outside?
There was a problem hiding this comment.
Probably.
I couldn't think of a nice way to allow it to be controlled from outside (via a prop) while still being able to open/close it from inside, since changing the prop would violate the one-way data flow from parent to child.
Maybe there's a pattern I'm missing here.
There was a problem hiding this comment.
Happy to leave it for now, but if we ever do, the pattern is generally to make 2 components:
- The first is a pure/dumb component, no internal state, only reacts to its props. Basically just visual styling only.
- The seconds wraps/controls the first, it manages all the state (it has all the observables and actions), and just delegates to the first.
https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
| onChange: action('onChange'), | ||
| } | ||
|
|
||
| const container = { maxWidth: '320px', fontFamily: 'Arial, sans-serif' } |
There was a problem hiding this comment.
Might as well just make this a component
const Container = ({ children }: { children: any }) => {
<div style={{ maxWidth: '320px', fontFamily: 'Arial, sans-serif' }}>
{children}
</div>
}Each story can then use
<Container>
<Select props/>
</Container>Also fix lint errors from the new rule
|
Could we add an interactive story as well? Like the switch has 🙂 |
BrendanAnnable
left a comment
There was a problem hiding this comment.
Some suggestions, but LGTM 🎉
| @observer | ||
| export class Select extends React.Component<SelectProps> { | ||
| @observable | ||
| private isOpen: boolean = false |
There was a problem hiding this comment.
Happy to leave it for now, but if we ever do, the pattern is generally to make 2 components:
- The first is a pure/dumb component, no internal state, only reacts to its props. Basically just visual styling only.
- The seconds wraps/controls the first, it manages all the state (it has all the observables and actions), and just delegates to the first.
https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
| } | ||
|
|
||
| private readonly onDocumentKeydown = (event: KeyboardEvent) => { | ||
| if (this.isOpen && event.keyCode === KeyCode.Escape) { |
There was a problem hiding this comment.
FYI you can now just use event.key === 'Escape'
http://keycode.info/ is handy.
| "jsx-use-translation-function": false, | ||
| "jsx-self-close": false, | ||
| "jsx-space-before-trailing-slash": false, | ||
| "jsx-wrap-multiline": false |
There was a problem hiding this comment.
I haven't really reviewed these settings, but can we revisit if they ever become a problem!
There was a problem hiding this comment.
I disabled those rules since I only wanted the jsx-no-lambda, but they're all enabled by default when you extend the preset.
Enabling them might require some fixes across the codebase.
|
|
||
| export type SelectProps = { | ||
| className?: string | ||
| dropdownMenuClassName?: string |
There was a problem hiding this comment.
Passing classes directly into components is generally an anti-pattern. It breaks encapsulation.
It forces the consumer to provide a class which must understand the internal DOM structure of the component, which means you can't refactor the component without potentially breaking consumers who put weird CSS properties all over the place.
It's better for the component to remain responsible for styling itself, and allow consumers to set various "styles" via a constrained set of props.
I'm not sure what we plan to use this for, but for example instead of passing a className to increase the padding of an element, it would be better to instead pass in paddingStyle: 'compact' | 'wide' prop and let the component own the CSS styling.
There was a problem hiding this comment.
We need it to style the dropdown (width, shadows, etc), since the basic dropdown component only sets the position.
Maybe we can move that there.
There was a problem hiding this comment.
Removed as it wasn't needed.
Also removed the dropdownMenuClassName prop on the Dropdown component, replaced with two new props:
isFullwidth: boolean: which adds a class forwidth:100%`dropdownPosition: 'left' | 'right': adds a class which styles right-aligned dropdowns, used by the current robot selector
Instead use other props which handle the styling internally. Also remove KeyCode enum
|
@BrendanAnnable Added an interactive story and made other suggested changes. I think it's ready to merge. |
This PR adds a generic single select component. We might expand it to allow multi-select later.
Used in the robot selector for #208.
Storybook
https://nusight-pr-227.herokuapp.com/storybook/?selectedKind=components.select&selectedStory=renders%20basic&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Factions%2Factions-panel