mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
SearchInput refactoring
Signed-off-by: Alex Andreev <alex.andreev.email@gmail.com>
This commit is contained in:
parent
14e88a5bb6
commit
de33f52a30
@ -11,7 +11,7 @@ import { navigation } from "../../navigation";
|
|||||||
import { ItemListLayout } from "../item-object-list/item-list-layout";
|
import { ItemListLayout } from "../item-object-list/item-list-layout";
|
||||||
import { t, Trans } from "@lingui/macro";
|
import { t, Trans } from "@lingui/macro";
|
||||||
import { _i18n } from "../../i18n";
|
import { _i18n } from "../../i18n";
|
||||||
import { SearchInput } from "../input";
|
import { SearchInputUrl } from "../input";
|
||||||
|
|
||||||
enum sortBy {
|
enum sortBy {
|
||||||
name = "name",
|
name = "name",
|
||||||
@ -72,7 +72,7 @@ export class HelmCharts extends Component<Props> {
|
|||||||
(items: HelmChart[]) => items.filter(item => !item.deprecated)
|
(items: HelmChart[]) => items.filter(item => !item.deprecated)
|
||||||
]}
|
]}
|
||||||
customizeHeader={() => (
|
customizeHeader={() => (
|
||||||
<SearchInput placeholder={_i18n._(t`Search Helm Charts`)} />
|
<SearchInputUrl placeholder={_i18n._(t`Search Helm Charts`)} />
|
||||||
)}
|
)}
|
||||||
renderTableHeader={[
|
renderTableHeader={[
|
||||||
{ className: "icon" },
|
{ className: "icon" },
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export * from './input'
|
export * from './input'
|
||||||
export * from './search-input'
|
export * from './search-input'
|
||||||
|
export * from './search-input-url'
|
||||||
export * from './file-input'
|
export * from './file-input'
|
||||||
|
|||||||
@ -24,6 +24,7 @@ export type InputProps<T = string> = Omit<InputElementProps, "onChange" | "onSub
|
|||||||
showValidationLine?: boolean; // show animated validation line for async validators
|
showValidationLine?: boolean; // show animated validation line for async validators
|
||||||
iconLeft?: string | React.ReactNode; // material-icon name in case of string-type
|
iconLeft?: string | React.ReactNode; // material-icon name in case of string-type
|
||||||
iconRight?: string | React.ReactNode;
|
iconRight?: string | React.ReactNode;
|
||||||
|
contentRight?: string | React.ReactNode; // Any component of string goes after iconRight
|
||||||
validators?: Validator | Validator[];
|
validators?: Validator | Validator[];
|
||||||
onChange?(value: T, evt: React.ChangeEvent<InputElement>): void;
|
onChange?(value: T, evt: React.ChangeEvent<InputElement>): void;
|
||||||
onSubmit?(value: T): void;
|
onSubmit?(value: T): void;
|
||||||
@ -258,7 +259,7 @@ export class Input extends React.Component<InputProps, State> {
|
|||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
multiLine, showValidationLine, validators, theme, maxRows, children,
|
multiLine, showValidationLine, validators, theme, maxRows, children,
|
||||||
maxLength, rows, disabled, autoSelectOnFocus, iconLeft, iconRight,
|
maxLength, rows, disabled, autoSelectOnFocus, iconLeft, iconRight, contentRight,
|
||||||
...inputProps
|
...inputProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { focused, dirty, valid, validating, errors } = this.state;
|
const { focused, dirty, valid, validating, errors } = this.state;
|
||||||
@ -291,6 +292,7 @@ export class Input extends React.Component<InputProps, State> {
|
|||||||
{isString(iconLeft) ? <Icon material={iconLeft} /> : iconLeft}
|
{isString(iconLeft) ? <Icon material={iconLeft} /> : iconLeft}
|
||||||
{multiLine ? <textarea {...inputProps as any} /> : <input {...inputProps as any} />}
|
{multiLine ? <textarea {...inputProps as any} /> : <input {...inputProps as any} />}
|
||||||
{isString(iconRight) ? <Icon material={iconRight} /> : iconRight}
|
{isString(iconRight) ? <Icon material={iconRight} /> : iconRight}
|
||||||
|
{contentRight}
|
||||||
</label>
|
</label>
|
||||||
<div className="input-info flex gaps">
|
<div className="input-info flex gaps">
|
||||||
{!valid && dirty && (
|
{!valid && dirty && (
|
||||||
|
|||||||
49
src/renderer/components/input/search-input-url.tsx
Normal file
49
src/renderer/components/input/search-input-url.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import React from "react";
|
||||||
|
import debounce from "lodash/debounce";
|
||||||
|
import { autorun, observable } from "mobx";
|
||||||
|
import { disposeOnUnmount, observer } from "mobx-react";
|
||||||
|
import { getSearch, setSearch } from "../../navigation";
|
||||||
|
import { InputProps } from "./input";
|
||||||
|
import { SearchInput } from "./search-input";
|
||||||
|
|
||||||
|
interface Props extends InputProps {
|
||||||
|
compact?: boolean; // show only search-icon when not focused
|
||||||
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export class SearchInputUrl extends React.Component<Props> {
|
||||||
|
@observable inputVal = ""; // fix: use empty string to avoid react warnings
|
||||||
|
|
||||||
|
@disposeOnUnmount
|
||||||
|
updateInput = autorun(() => this.inputVal = getSearch())
|
||||||
|
updateUrl = debounce((val: string) => setSearch(val), 250)
|
||||||
|
|
||||||
|
setValue = (value: string) => {
|
||||||
|
this.inputVal = value;
|
||||||
|
this.updateUrl(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
clear = () => {
|
||||||
|
this.setValue("");
|
||||||
|
this.updateUrl.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange = (val: string, evt: React.ChangeEvent<any>) => {
|
||||||
|
this.setValue(val);
|
||||||
|
if (this.props.onChange) {
|
||||||
|
this.props.onChange(val, evt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { inputVal } = this;
|
||||||
|
return (
|
||||||
|
<SearchInput
|
||||||
|
value={inputVal}
|
||||||
|
onChange={this.onChange}
|
||||||
|
onClear={this.clear}
|
||||||
|
{...this.props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,7 +6,10 @@
|
|||||||
|
|
||||||
> label {
|
> label {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
|
box-shadow: 0 0 0 1px $halfGray;
|
||||||
padding: 6px 6px 6px 10px;
|
padding: 6px 6px 6px 10px;
|
||||||
|
|
||||||
.Icon {
|
.Icon {
|
||||||
|
|||||||
@ -1,24 +1,22 @@
|
|||||||
import "./search-input.scss";
|
import "./search-input.scss";
|
||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import debounce from "lodash/debounce"
|
|
||||||
import { autorun, observable } from "mobx";
|
|
||||||
import { disposeOnUnmount, observer } from "mobx-react";
|
|
||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
import { Icon } from "../icon";
|
import { observer } from "mobx-react";
|
||||||
|
import { _i18n } from "../../i18n";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
|
import { Icon } from "../icon";
|
||||||
import { Input, InputProps } from "./input";
|
import { Input, InputProps } from "./input";
|
||||||
import { getSearch, setSearch } from "../../navigation";
|
|
||||||
import { _i18n } from '../../i18n';
|
|
||||||
|
|
||||||
interface Props extends InputProps {
|
interface Props extends InputProps {
|
||||||
compact?: boolean; // show only search-icon when not focused
|
compact?: boolean; // show only search-icon when not focused
|
||||||
updateUrl?: boolean;
|
closeIcon?: boolean;
|
||||||
|
onClear?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultProps: Partial<Props> = {
|
const defaultProps: Partial<Props> = {
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
updateUrl: true,
|
closeIcon: true,
|
||||||
get placeholder() {
|
get placeholder() {
|
||||||
return _i18n._(t`Search...`)
|
return _i18n._(t`Search...`)
|
||||||
},
|
},
|
||||||
@ -28,37 +26,15 @@ const defaultProps: Partial<Props> = {
|
|||||||
export class SearchInput extends React.Component<Props> {
|
export class SearchInput extends React.Component<Props> {
|
||||||
static defaultProps = defaultProps as object;
|
static defaultProps = defaultProps as object;
|
||||||
|
|
||||||
@observable inputVal = this.props.value || ""; // fix: use empty string to avoid react warnings
|
|
||||||
|
|
||||||
@disposeOnUnmount
|
|
||||||
updateInput = autorun(() => {
|
|
||||||
if (this.props.updateUrl) this.inputVal = getSearch();
|
|
||||||
})
|
|
||||||
updateUrl = debounce((val: string) => setSearch(val), 250)
|
|
||||||
|
|
||||||
setValue = (value: string) => {
|
|
||||||
this.inputVal = value;
|
|
||||||
if (this.props.updateUrl) {
|
|
||||||
this.updateUrl(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clear = () => {
|
clear = () => {
|
||||||
this.setValue("");
|
if (this.props.onClear) {
|
||||||
if (this.props.updateUrl) {
|
this.props.onClear();
|
||||||
this.updateUrl.flush();
|
|
||||||
}
|
|
||||||
if (this.props.onChange) {
|
|
||||||
this.props.onChange("", null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = (val: string, evt: React.ChangeEvent<any>) => {
|
onChange = (val: string, evt: React.ChangeEvent<any>) => {
|
||||||
this.setValue(val);
|
|
||||||
if (this.props.onChange) {
|
|
||||||
this.props.onChange(val, evt);
|
this.props.onChange(val, evt);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onKeyDown = (evt: React.KeyboardEvent<any>) => {
|
onKeyDown = (evt: React.KeyboardEvent<any>) => {
|
||||||
if (this.props.onKeyDown) {
|
if (this.props.onKeyDown) {
|
||||||
@ -73,16 +49,14 @@ export class SearchInput extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { inputVal } = this;
|
const { className, compact, closeIcon, onClear, ...inputProps } = this.props;
|
||||||
const { className, compact, updateUrl, ...inputProps } = this.props;
|
const icon = this.props.value
|
||||||
const icon = inputVal
|
? closeIcon ? <Icon small material="close" onClick={this.clear}/> : null
|
||||||
? <Icon small material="close" onClick={this.clear}/>
|
|
||||||
: <Icon small material="search"/>
|
: <Icon small material="search"/>
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
{...inputProps}
|
{...inputProps}
|
||||||
className={cssNames("SearchInput", className, { compact })}
|
className={cssNames("SearchInput", className, { compact })}
|
||||||
value={inputVal}
|
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
iconRight={icon}
|
iconRight={icon}
|
||||||
|
|||||||
@ -22,15 +22,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.SearchInput {
|
|
||||||
label {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
border-radius: $radius;
|
|
||||||
box-shadow: 0 0 0 1px $halfGray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
> .items {
|
> .items {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { AddRemoveButtons, AddRemoveButtonsProps } from "../add-remove-buttons";
|
|||||||
import { NoItems } from "../no-items";
|
import { NoItems } from "../no-items";
|
||||||
import { Spinner } from "../spinner";
|
import { Spinner } from "../spinner";
|
||||||
import { ItemObject, ItemStore } from "../../item.store";
|
import { ItemObject, ItemStore } from "../../item.store";
|
||||||
import { SearchInput } from "../input";
|
import { SearchInputUrl } from "../input";
|
||||||
import { namespaceStore } from "../+namespaces/namespace.store";
|
import { namespaceStore } from "../+namespaces/namespace.store";
|
||||||
import { Filter, FilterType, pageFilters } from "./page-filters.store";
|
import { Filter, FilterType, pageFilters } from "./page-filters.store";
|
||||||
import { PageFiltersList } from "./page-filters-list";
|
import { PageFiltersList } from "./page-filters-list";
|
||||||
@ -349,7 +349,7 @@ export class ItemListLayout extends React.Component<ItemListLayoutProps> {
|
|||||||
[FilterType.NAMESPACE]: true, // namespace-select used instead
|
[FilterType.NAMESPACE]: true, // namespace-select used instead
|
||||||
}}/>
|
}}/>
|
||||||
</>,
|
</>,
|
||||||
search: <SearchInput/>,
|
search: <SearchInputUrl/>,
|
||||||
}
|
}
|
||||||
let header = this.renderHeaderContent(placeholders);
|
let header = this.renderHeaderContent(placeholders);
|
||||||
if (customizeHeader) {
|
if (customizeHeader) {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import "./virtual-list.scss";
|
|||||||
|
|
||||||
import React, { Component } from "react";
|
import React, { Component } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Align, ListChildComponentProps, VariableSizeList } from "react-window";
|
import { Align, ListChildComponentProps, ListOnScrollProps, VariableSizeList } from "react-window";
|
||||||
import { cssNames, noop } from "../../utils";
|
import { cssNames, noop } from "../../utils";
|
||||||
import { TableRowProps } from "../table/table-row";
|
import { TableRowProps } from "../table/table-row";
|
||||||
import { ItemObject } from "../../item.store";
|
import { ItemObject } from "../../item.store";
|
||||||
@ -22,7 +22,7 @@ interface Props<T extends ItemObject = any> {
|
|||||||
readyOffset?: number;
|
readyOffset?: number;
|
||||||
selectedItemId?: string;
|
selectedItemId?: string;
|
||||||
getRow?: (uid: string | number) => React.ReactElement<any>;
|
getRow?: (uid: string | number) => React.ReactElement<any>;
|
||||||
onScroll?: () => any;
|
onScroll?: (props: ListOnScrollProps) => any;
|
||||||
outerRef?: ((instance: unknown) => void) | React.MutableRefObject<unknown>
|
outerRef?: ((instance: unknown) => void) | React.MutableRefObject<unknown>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ export class VirtualList extends Component<Props, State> {
|
|||||||
ref={this.listRef}
|
ref={this.listRef}
|
||||||
outerRef={outerRef}
|
outerRef={outerRef}
|
||||||
children={Row}
|
children={Row}
|
||||||
onScroll={() => onScroll()}
|
onScroll={onScroll}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user