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

[Labs] new TagInput component! #1232

Merged
merged 14 commits into from
Jun 16, 2017
Merged

[Labs] new TagInput component! #1232

merged 14 commits into from
Jun 16, 2017

Conversation

giladgray
Copy link
Contributor

Addresses #118, #1064

Changes proposed in this pull request:

TagInput renders Tags inside an input, followed by an actual text input. The container is merely styled to look like a Blueprint input; the actual editable element appears after the last tag. Clicking anywhere on the container will focus the text input for seamless interaction.

Reviewers should focus on:

  • API design. onAdd and onRemove seem sufficient in my testing--i was able to implement a basic multi-select with little trouble.

Screenshot

image

@giladgray giladgray requested a review from adidahiya June 14, 2017 00:23
@blueprint-bot
Copy link

move code to subdirectory, use "fake" names

Preview: documentation
Coverage: core | datetime

/**
* Click handler for remove button.
* Button will only be rendered if this prop is defined.
*/
onRemove?: (e: React.MouseEvent<HTMLSpanElement>) => void;
onRemove?: (e: React.MouseEvent<HTMLButtonElement>) => void;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

previous type was incorrect, as the onClick is applied to a <button>

@@ -14,12 +14,12 @@ import { isFunction } from "../../common/utils";

import * as Classes from "../../common/classes";

export interface ITagProps extends IProps, IIntentProps, React.HTMLProps<HTMLSpanElement> {
export interface ITagProps extends IProps, IIntentProps, React.HTMLAttributes<Tag> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

💥 HTMLAttributes does not include ref so it avoids the nasty { ref, ...htmlInputProps } spread we've had to do! i'll go refactor other usages separately.

@cmslewis
Copy link
Contributor

Stress testing (not much we should do to mitigate this maybe, just FYI):
2017-06-14 13 39 06

Copy link
Contributor

@cmslewis cmslewis left a comment

Choose a reason for hiding this comment

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

Looks and feels great overall. Just have a couple questions.

input: (ref: HTMLInputElement) => {
this.inputElement = ref;
const refHandler = this.props.inputProps.ref;
if (Utils.isFunction(refHandler)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use Utils.safeInvoke?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

great Q!

typings problems with safeInvoke: refHandler is typed string | (ref) => void so i must use isFunction for the type guard. (note that this is exactly what safeInvoke does inside, but it expects functions. I think we can relax those types and maybe also make it a type guard?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

seems there's not much to be done to improve safeInvoke usage. if i make it accept non-functions then it loses all type safety :(. adding code comment instead.


private handleContainerClick = () => {
if (this.inputElement != null) {
this.inputElement.focus();
Copy link
Contributor

Choose a reason for hiding this comment

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

If I'm blurred and then click a tag's remove button, is it still expected that the input focus?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes cuz you've clicked in the .pt-input (the thing that looks like a text input)

private handleInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
const { value } = event.currentTarget;
if (event.which === Keys.ENTER && value.length > 0) {
Utils.safeInvoke(this.props.onAdd, value);
Copy link
Contributor

Choose a reason for hiding this comment

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

Elsewhere, we setState before invoking the callback. Is the switch here intentional?

}

private handleRemoveTag = (event: React.MouseEvent<HTMLSpanElement>) => {
// using data attribute to simplify callback logic -- one handler for all children
Copy link
Contributor

Choose a reason for hiding this comment

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

🥇

@llorca
Copy link
Contributor

llorca commented Jun 14, 2017

Note: aware that .pt-tag.pt-active isn't great visually, I'll push better styles for it later

@blueprint-bot
Copy link

fix lint

Preview: documentation | table
Coverage: core | datetime

input: (ref: HTMLInputElement) => {
this.inputElement = ref;
const refHandler = this.props.inputProps.ref;
if (Utils.isFunction(refHandler)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

great Q!

typings problems with safeInvoke: refHandler is typed string | (ref) => void so i must use isFunction for the type guard. (note that this is exactly what safeInvoke does inside, but it expects functions. I think we can relax those types and maybe also make it a type guard?)

public render() {
const { inputProps, tagProps, values } = this.props;

const classes = classNames(Classes.INPUT, "pt-tag-input", {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

note: no constant

// .pt-dark & {
// background-color: rgba($gray5, 0.2);
// }
// }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

oops

* or a function to customize props per tag.
*
* If you define `onRemove` here then you will have to implement your own tag removal
* handling as the `TagInput` `onRemove` prop will never be invoked.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

...TagInput's own handler will never...

};

public state: ITagInputState = {
activeIndex: -1,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

const NONE


private handleContainerClick = () => {
if (this.inputElement != null) {
this.inputElement.focus();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes cuz you've clicked in the .pt-input (the thing that looks like a text input)

@blueprint-bot
Copy link

🌳 remove logs

Preview: documentation | table
Coverage: core | datetime

@blueprint-bot
Copy link

add docs about keyboard interactions

Preview: documentation
Coverage: core | datetime

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants