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

[core] feat(TagInput): resizable input that scales as you type #5966

Merged
merged 10 commits into from
Feb 27, 2023
1 change: 1 addition & 0 deletions packages/core/src/components/tag-input/_tag-input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ $tag-input-icon-padding-large: ($pt-input-height-large - $pt-icon-size-large) *
margin-top: $tag-input-padding;
// allow tags to ellipse and not overflow the container
min-width: 0;
position: relative;

// use the larger, conventional input padding when there are no tags and no left icon present.
// see: https://github.com/palantir/blueprint/issues/2872
Expand Down
50 changes: 50 additions & 0 deletions packages/core/src/components/tag-input/resizeableInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/* !
* (c) Copyright 2022 Palantir Technologies Inc. All rights reserved.
*/

import React, { forwardRef, useEffect, useRef, useState } from "react";

import { HTMLInputProps } from "../../common";

type ResizeableInputProps = HTMLInputProps;
export type Ref = HTMLInputElement;

export const ResizeableInput = forwardRef<Ref, ResizeableInputProps>(function ResizeableInput(props, ref) {
JosephChotard marked this conversation as resolved.
Show resolved Hide resolved
const [content, setContent] = useState("");
const [width, setWidth] = useState(0);
const span = useRef<HTMLSpanElement>(null);

useEffect(() => {
if (span.current) {
JosephChotard marked this conversation as resolved.
Show resolved Hide resolved
setWidth(span.current.offsetWidth);
}
}, [content]);

const { onChange, ...otherProps } = props;

const changeHandler: React.ChangeEventHandler<HTMLInputElement> = evt => {
JosephChotard marked this conversation as resolved.
Show resolved Hide resolved
setContent(evt.target.value);
onChange?.(evt);
};

return (
<span>
<span
id="hide"
ref={span}
style={{
JosephChotard marked this conversation as resolved.
Show resolved Hide resolved
maxHeight: "0",
maxWidth: "100%",
minWidth: "80px",
position: "absolute",
whiteSpace: "nowrap",
zIndex: -100,
JosephChotard marked this conversation as resolved.
Show resolved Hide resolved
}}
>
{/* Need to replace spaces with the html character for them to be preserved */}
JosephChotard marked this conversation as resolved.
Show resolved Hide resolved
{content.replace(/ /g, "\u00a0")}
</span>
<input type="text" style={{ width }} autoFocus={true} onChange={changeHandler} ref={ref} {...otherProps} />
</span>
);
});
3 changes: 2 additions & 1 deletion packages/core/src/components/tag-input/tagInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { DISPLAYNAME_PREFIX, HTMLInputProps, IntentProps, MaybeElement, Props }
import { getActiveElement } from "../../common/utils";
import { Icon, IconName, IconSize } from "../icon/icon";
import { Tag, TagProps } from "../tag/tag";
import { ResizeableInput } from "./resizeableInput";

/**
* The method in which a `TagInput` value was added.
Expand Down Expand Up @@ -262,7 +263,7 @@ export class TagInput extends AbstractPureComponent2<TagInputProps, ITagInputSta
<div className={Classes.TAG_INPUT_VALUES}>
{values.map(this.maybeRenderTag)}
{this.props.children}
<input
<ResizeableInput
value={this.state.inputValue}
{...inputProps}
onFocus={this.handleInputFocus}
Expand Down