mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-04-16 14:48:38 +01:00
Move inputTypeahead to react (#946)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
This commit is contained in:
parent
ee4404c439
commit
51f3f9a718
@ -5,6 +5,9 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
.input-type-head-text-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
textarea {
|
textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
@ -21,4 +24,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.input-typeahead-chocies-container {
|
||||||
|
border: 1px solid lightgrey;
|
||||||
|
padding: 5px 10px 5px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
.choice-caption{
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
@ -6,14 +6,13 @@
|
|||||||
* typeaheadOverrideOptions: { dynamic:false }
|
* typeaheadOverrideOptions: { dynamic:false }
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import "jquery-typeahead";
|
import { getTheme, IconButton, IIconProps, List, Stack, TextField } from "@fluentui/react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { KeyCodes } from "../../../Common/Constants";
|
|
||||||
import "./InputTypeahead.less";
|
import "./InputTypeahead.less";
|
||||||
|
|
||||||
export interface Item {
|
export interface Item {
|
||||||
caption: string;
|
caption: string;
|
||||||
value: any;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -75,170 +74,125 @@ export interface InputTypeaheadComponentProps {
|
|||||||
useTextarea?: boolean;
|
useTextarea?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnClickItem {
|
interface InputTypeaheadComponentState {
|
||||||
matchedKey: string;
|
isSuggestionVisible: boolean;
|
||||||
value: any;
|
selectedChoice: Item;
|
||||||
caption: string;
|
filteredChoices: Item[];
|
||||||
group: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Cache {
|
|
||||||
inputValue: string;
|
|
||||||
selection: Item;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface InputTypeaheadComponentState {}
|
|
||||||
|
|
||||||
export class InputTypeaheadComponent extends React.Component<
|
export class InputTypeaheadComponent extends React.Component<
|
||||||
InputTypeaheadComponentProps,
|
InputTypeaheadComponentProps,
|
||||||
InputTypeaheadComponentState
|
InputTypeaheadComponentState
|
||||||
> {
|
> {
|
||||||
private inputElt: HTMLElement;
|
constructor(props: InputTypeaheadComponentProps) {
|
||||||
private containerElt: HTMLElement;
|
|
||||||
|
|
||||||
private cache: Cache;
|
|
||||||
private inputValue: string;
|
|
||||||
private selection: Item;
|
|
||||||
|
|
||||||
public constructor(props: InputTypeaheadComponentProps) {
|
|
||||||
super(props);
|
super(props);
|
||||||
this.cache = {
|
this.state = {
|
||||||
inputValue: null,
|
isSuggestionVisible: false,
|
||||||
selection: null,
|
filteredChoices: [],
|
||||||
|
selectedChoice: {
|
||||||
|
caption: "",
|
||||||
|
value: "",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private onRenderCell = (item: Item): JSX.Element => {
|
||||||
* Props have changed
|
|
||||||
* @param prevProps
|
|
||||||
* @param prevState
|
|
||||||
* @param snapshot
|
|
||||||
*/
|
|
||||||
public componentDidUpdate(
|
|
||||||
prevProps: InputTypeaheadComponentProps,
|
|
||||||
prevState: InputTypeaheadComponentState,
|
|
||||||
snapshot: any
|
|
||||||
): void {
|
|
||||||
if (prevProps.defaultValue !== this.props.defaultValue) {
|
|
||||||
$(this.inputElt).val(this.props.defaultValue);
|
|
||||||
this.initializeTypeahead();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executed once react is done building the DOM for this component
|
|
||||||
*/
|
|
||||||
public componentDidMount(): void {
|
|
||||||
this.initializeTypeahead();
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(): JSX.Element {
|
|
||||||
return (
|
return (
|
||||||
<span className="input-typeahead-container">
|
<div className="input-typeahead-chocies-container" onClick={() => this.onChoiceClick(item)}>
|
||||||
<div
|
<p className="choice-caption">{item.caption}</p>
|
||||||
className="input-typehead"
|
<span>{item.value}</span>
|
||||||
onKeyDown={(event: React.KeyboardEvent<HTMLDivElement>) => this.onKeyDown(event)}
|
</div>
|
||||||
>
|
|
||||||
<div className="typeahead__container" ref={(input) => (this.containerElt = input)}>
|
|
||||||
<div className="typeahead__field">
|
|
||||||
<span className="typeahead__query">
|
|
||||||
{this.props.useTextarea ? (
|
|
||||||
<textarea
|
|
||||||
rows={1}
|
|
||||||
name="q"
|
|
||||||
autoComplete="off"
|
|
||||||
aria-label="Input query"
|
|
||||||
ref={(input) => (this.inputElt = input)}
|
|
||||||
defaultValue={this.props.defaultValue}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<input
|
|
||||||
name="q"
|
|
||||||
type="search"
|
|
||||||
autoComplete="off"
|
|
||||||
aria-label="Input query"
|
|
||||||
ref={(input) => (this.inputElt = input)}
|
|
||||||
defaultValue={this.props.defaultValue}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
{this.props.showSearchButton && (
|
|
||||||
<span className="typeahead__button">
|
|
||||||
<button type="submit">
|
|
||||||
<span className="typeahead__search-icon" />
|
|
||||||
</button>
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
private onKeyDown(event: React.KeyboardEvent<HTMLElement>) {
|
private onChoiceClick = (item: Item): void => {
|
||||||
if (event.keyCode === KeyCodes.Enter) {
|
this.props.onNewValue(item.caption);
|
||||||
|
this.setState({ isSuggestionVisible: false, selectedChoice: item });
|
||||||
|
};
|
||||||
|
|
||||||
|
private handleChange = (value: string): void => {
|
||||||
|
if (!value) {
|
||||||
|
this.setState({ isSuggestionVisible: true });
|
||||||
|
}
|
||||||
|
this.props.onNewValue(value);
|
||||||
|
const filteredChoices = this.filterChoiceByValue(this.props.choices, value);
|
||||||
|
this.setState({ filteredChoices });
|
||||||
|
};
|
||||||
|
|
||||||
|
private onSubmit = (event: React.KeyboardEvent<HTMLElement>): void => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
if (this.props.submitFct) {
|
if (this.props.submitFct) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.props.submitFct(this.cache.inputValue, this.cache.selection);
|
this.props.submitFct(this.props.defaultValue, this.state.selectedChoice);
|
||||||
$(this.containerElt).children(".typeahead__result").hide();
|
this.setState({ isSuggestionVisible: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
private filterChoiceByValue = (choices: Item[], searchKeyword: string): Item[] => {
|
||||||
* Must execute once ko is rendered, so that it can find the input element by id
|
return choices.filter((choice) =>
|
||||||
*/
|
// @ts-ignore
|
||||||
private initializeTypeahead(): void {
|
Object.keys(choice).some((key) => choice[key].toLowerCase().includes(searchKeyword.toLowerCase()))
|
||||||
const props = this.props;
|
);
|
||||||
let cache = this.cache;
|
};
|
||||||
let options: any = {
|
|
||||||
input: this.inputElt,
|
|
||||||
order: "asc",
|
|
||||||
minLength: 0,
|
|
||||||
searchOnFocus: true,
|
|
||||||
source: {
|
|
||||||
display: "caption",
|
|
||||||
data: () => {
|
|
||||||
return props.choices;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
callback: {
|
|
||||||
onClick: (node: any, a: any, item: OnClickItem, event: any) => {
|
|
||||||
cache.selection = item;
|
|
||||||
|
|
||||||
if (props.onSelected) {
|
public render(): JSX.Element {
|
||||||
props.onSelected(item);
|
const { defaultValue, useTextarea, placeholder, onNewValue } = this.props;
|
||||||
}
|
const { isSuggestionVisible, selectedChoice, filteredChoices } = this.state;
|
||||||
},
|
const theme = getTheme();
|
||||||
onResult(node: any, query: any, result: any, resultCount: any, resultCountPerGroup: any) {
|
|
||||||
cache.inputValue = query;
|
const iconButtonStyles = {
|
||||||
if (props.onNewValue) {
|
root: {
|
||||||
props.onNewValue(query);
|
color: theme.palette.neutralPrimary,
|
||||||
}
|
marginLeft: "10px !important",
|
||||||
},
|
marginTop: "0px",
|
||||||
|
marginRight: "2px",
|
||||||
|
width: "42px",
|
||||||
},
|
},
|
||||||
template: (query: string, item: any) => {
|
rootHovered: {
|
||||||
// Don't display id if caption *IS* the id
|
color: theme.palette.neutralDark,
|
||||||
return item.caption === item.value
|
|
||||||
? "<span>{{caption}}</span>"
|
|
||||||
: "<span><div>{{caption}}</div><div><small>{{value}}</small></div></span>";
|
|
||||||
},
|
},
|
||||||
dynamic: true,
|
|
||||||
};
|
};
|
||||||
|
const cancelIcon: IIconProps = { iconName: "cancel" };
|
||||||
|
const searchIcon: IIconProps = { iconName: "Search" };
|
||||||
|
|
||||||
// Override options
|
return (
|
||||||
if (props.typeaheadOverrideOptions) {
|
<div className="input-typeahead-container">
|
||||||
for (const p in props.typeaheadOverrideOptions) {
|
<Stack horizontal>
|
||||||
options[p] = props.typeaheadOverrideOptions[p];
|
<TextField
|
||||||
}
|
multiline={useTextarea}
|
||||||
}
|
rows={1}
|
||||||
|
defaultValue={defaultValue}
|
||||||
if (props.hasOwnProperty("showCancelButton")) {
|
ariaLabel="Input query"
|
||||||
options.cancelButton = props.showCancelButton;
|
placeholder={placeholder}
|
||||||
}
|
className="input-type-head-text-field"
|
||||||
|
value={defaultValue}
|
||||||
$(this.inputElt).typeahead(options);
|
onKeyDown={this.onSubmit}
|
||||||
|
onFocus={() => this.setState({ isSuggestionVisible: true })}
|
||||||
|
onChange={(_event, newValue?: string) => this.handleChange(newValue)}
|
||||||
|
/>
|
||||||
|
{this.props.showCancelButton && (
|
||||||
|
<IconButton
|
||||||
|
styles={iconButtonStyles}
|
||||||
|
iconProps={cancelIcon}
|
||||||
|
ariaLabel="cancel Button"
|
||||||
|
onClick={() => onNewValue("")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{this.props.showSearchButton && (
|
||||||
|
<IconButton
|
||||||
|
styles={iconButtonStyles}
|
||||||
|
iconProps={searchIcon}
|
||||||
|
ariaLabel="Search Button"
|
||||||
|
onClick={() => this.props.submitFct(defaultValue, selectedChoice)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
{filteredChoices.length && isSuggestionVisible ? (
|
||||||
|
<List items={filteredChoices} onRenderCell={this.onRenderCell} />
|
||||||
|
) : undefined}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,61 +1,43 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`inputTypeahead renders <input /> 1`] = `
|
exports[`inputTypeahead renders <input /> 1`] = `
|
||||||
<span
|
<div
|
||||||
className="input-typeahead-container"
|
className="input-typeahead-container"
|
||||||
>
|
>
|
||||||
<div
|
<Stack
|
||||||
className="input-typehead"
|
horizontal={true}
|
||||||
onKeyDown={[Function]}
|
|
||||||
>
|
>
|
||||||
<div
|
<StyledTextFieldBase
|
||||||
className="typeahead__container"
|
ariaLabel="Input query"
|
||||||
>
|
className="input-type-head-text-field"
|
||||||
<div
|
multiline={false}
|
||||||
className="typeahead__field"
|
onChange={[Function]}
|
||||||
>
|
onFocus={[Function]}
|
||||||
<span
|
onKeyDown={[Function]}
|
||||||
className="typeahead__query"
|
placeholder="placeholder"
|
||||||
>
|
rows={1}
|
||||||
<input
|
/>
|
||||||
aria-label="Input query"
|
</Stack>
|
||||||
autoComplete="off"
|
</div>
|
||||||
name="q"
|
|
||||||
type="search"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`inputTypeahead renders <textarea /> 1`] = `
|
exports[`inputTypeahead renders <textarea /> 1`] = `
|
||||||
<span
|
<div
|
||||||
className="input-typeahead-container"
|
className="input-typeahead-container"
|
||||||
>
|
>
|
||||||
<div
|
<Stack
|
||||||
className="input-typehead"
|
horizontal={true}
|
||||||
onKeyDown={[Function]}
|
|
||||||
>
|
>
|
||||||
<div
|
<StyledTextFieldBase
|
||||||
className="typeahead__container"
|
ariaLabel="Input query"
|
||||||
>
|
className="input-type-head-text-field"
|
||||||
<div
|
multiline={true}
|
||||||
className="typeahead__field"
|
onChange={[Function]}
|
||||||
>
|
onFocus={[Function]}
|
||||||
<span
|
onKeyDown={[Function]}
|
||||||
className="typeahead__query"
|
placeholder="placeholder"
|
||||||
>
|
rows={1}
|
||||||
<textarea
|
/>
|
||||||
aria-label="Input query"
|
</Stack>
|
||||||
autoComplete="off"
|
</div>
|
||||||
name="q"
|
|
||||||
rows={1}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
`;
|
`;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user