From 485783167d6e621e3721a9d99ff2c2c3e871e33c Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 12 Jul 2021 16:13:24 -0400 Subject: [PATCH] Add SystemName validation to AccessibleNamespaces (#2754) - Don't validate after a submit Signed-off-by: Sebastian Malton --- .../cluster-accessible-namespaces.tsx | 2 + .../editable-list/editable-list.scss | 62 +++++++++++++---- .../editable-list/editable-list.tsx | 10 ++- src/renderer/components/input/input.tsx | 68 +++++++++---------- 4 files changed, 92 insertions(+), 50 deletions(-) diff --git a/src/renderer/components/cluster-settings/components/cluster-accessible-namespaces.tsx b/src/renderer/components/cluster-settings/components/cluster-accessible-namespaces.tsx index 6794646bea..0c087083fe 100644 --- a/src/renderer/components/cluster-settings/components/cluster-accessible-namespaces.tsx +++ b/src/renderer/components/cluster-settings/components/cluster-accessible-namespaces.tsx @@ -25,6 +25,7 @@ import type { Cluster } from "../../../../main/cluster"; import { SubTitle } from "../../layout/sub-title"; import { EditableList } from "../../editable-list"; import { observable, makeObservable } from "mobx"; +import { systemName } from "../../input/input_validators"; interface Props { cluster: Cluster; @@ -49,6 +50,7 @@ export class ClusterAccessibleNamespaces extends React.Component { this.namespaces.add(newNamespace); this.props.cluster.accessibleNamespaces = Array.from(this.namespaces); }} + validators={systemName} items={Array.from(this.namespaces)} remove={({ oldItem: oldNamesapce }) => { this.namespaces.delete(oldNamesapce); diff --git a/src/renderer/components/editable-list/editable-list.scss b/src/renderer/components/editable-list/editable-list.scss index 0f88d009d9..6c62c0c59e 100644 --- a/src/renderer/components/editable-list/editable-list.scss +++ b/src/renderer/components/editable-list/editable-list.scss @@ -20,8 +20,13 @@ */ .EditableList { + --gradientColor: var(--colorVague); + .el-contents { display: flex; + background-color: var(--colorVague); + color: $textColorSecondary; + border-radius: $radius; flex-direction: column; &:not(:empty) { @@ -35,20 +40,53 @@ } .el-item { - display: grid; - grid-template-columns: 1fr auto; - padding: $padding $padding * 2; - margin-bottom: $padding / 4; - backdrop-filter: brightness(0.75); - border-radius: var(--border-radius); - - :last-child { - margin-bottom: unset; - } - - :first-child { + display: flex; + flex-direction: row; + padding: $padding 0; + margin-bottom: 1px; + + .el-value-container { + position: relative; + max-width: calc(100% - 29px); align-self: center; + + &::before, &::after { + content: ' '; + position: absolute; + z-index: 20; + display: block; + width: 8px; + height: var(--font-size); + } + + &::before { + left: 0px; + top: 0px; + background: linear-gradient(to right, var(--gradientColor) 0px, transparent); + } + + &::after { + right: 0px; + top: 0px; + background: linear-gradient(to left, var(--gradientColor) 0px, transparent); + } } + + .el-value { + white-space: nowrap; + overflow: scroll!important; + text-overflow: unset!important; + padding-left: 8px; + padding-right: 8px; + + &::-webkit-scrollbar { + display: none; + } + } + } + + :last-child { + margin-bottom: unset; } } } diff --git a/src/renderer/components/editable-list/editable-list.tsx b/src/renderer/components/editable-list/editable-list.tsx index 0b427b6f2a..61bf70bb70 100644 --- a/src/renderer/components/editable-list/editable-list.tsx +++ b/src/renderer/components/editable-list/editable-list.tsx @@ -25,7 +25,7 @@ import { observer } from "mobx-react"; import React from "react"; import { Icon } from "../icon"; -import { Input, InputProps } from "../input"; +import { Input, InputProps, InputValidator } from "../input"; import { boundMethod } from "../../utils"; export interface Props { @@ -33,6 +33,7 @@ export interface Props { add: (newItem: string) => void, remove: (info: { oldItem: T, index: number }) => void, placeholder?: string, + validators?: InputValidator | InputValidator[]; // An optional prop used to convert T to a displayable string // defaults to `String` @@ -61,7 +62,7 @@ export class EditableList extends React.Component> { } render() { - const { items, remove, renderItem, placeholder, inputTheme } = this.props; + const { items, remove, renderItem, placeholder, validators, inputTheme } = this.props; return (
@@ -69,6 +70,7 @@ export class EditableList extends React.Component> {
@@ -76,7 +78,9 @@ export class EditableList extends React.Component> { { items.map((item, index) => (
-
{renderItem(item, index)}
+
+
{renderItem(item, index)}
+
remove(({ index, oldItem: item }))} />
diff --git a/src/renderer/components/input/input.tsx b/src/renderer/components/input/input.tsx index fa1692e988..d182a8c202 100644 --- a/src/renderer/components/input/input.tsx +++ b/src/renderer/components/input/input.tsx @@ -31,6 +31,7 @@ import isString from "lodash/isString"; import isFunction from "lodash/isFunction"; import isBoolean from "lodash/isBoolean"; import uniqueId from "lodash/uniqueId"; +import { debounce } from "lodash"; const { conditionalValidators, ...InputValidators } = Validators; @@ -59,12 +60,12 @@ export type InputProps = Omit = { @@ -81,15 +82,14 @@ export class Input extends React.Component { public validators: InputValidator[] = []; public state: State = { - dirty: !!this.props.dirty, + focused: false, valid: true, + validating: false, + dirty: !!this.props.dirty, errors: [], + submitted: false, }; - isValid() { - return this.state.valid; - } - setValue(value = "") { if (value !== this.getValue()) { const nativeInputValueSetter = Object.getOwnPropertyDescriptor(this.input.constructor.prototype, "value").set; @@ -213,7 +213,6 @@ export class Input extends React.Component { } setDirty(dirty = true) { - if (this.state.dirty === dirty) return; this.setState({ dirty }); } @@ -221,30 +220,25 @@ export class Input extends React.Component { onFocus(evt: React.FocusEvent) { const { onFocus, autoSelectOnFocus } = this.props; - if (onFocus) onFocus(evt); + onFocus?.(evt); if (autoSelectOnFocus) this.select(); this.setState({ focused: true }); } @boundMethod onBlur(evt: React.FocusEvent) { - const { onBlur } = this.props; - - if (onBlur) onBlur(evt); - if (this.state.dirtyOnBlur) this.setState({ dirty: true, dirtyOnBlur: false }); + this.props.onBlur?.(evt); this.setState({ focused: false }); } + setDirtyOnChange = debounce(() => this.setDirty(), 500); + @boundMethod - onChange(evt: React.ChangeEvent) { + onChange(evt: React.ChangeEvent) { this.props.onChange?.(evt.currentTarget.value, evt); this.validate(); this.autoFitHeight(); - - // mark input as dirty for the first time only onBlur() to avoid immediate error-state show when start typing - if (!this.state.dirty) { - this.setState({ dirtyOnBlur: true }); - } + this.setDirtyOnChange(); // re-render component when used as uncontrolled input // when used @defaultValue instead of @value changing real input.value doesn't call render() @@ -255,20 +249,20 @@ export class Input extends React.Component { @boundMethod onKeyDown(evt: React.KeyboardEvent) { - const modified = evt.shiftKey || evt.metaKey || evt.altKey || evt.ctrlKey; - this.props.onKeyDown?.(evt); - switch (evt.key) { - case "Enter": - if (this.props.onSubmit && !modified && !evt.repeat && this.isValid()) { - this.props.onSubmit(this.getValue(), evt); + if (evt.shiftKey || evt.metaKey || evt.altKey || evt.ctrlKey || evt.repeat) { + return; + } - if (this.isUncontrolled) { - this.setValue(); - } - } - break; + if (evt.key === "Enter") { + if (this.state.valid) { + this.props.onSubmit?.(this.getValue(), evt); + this.setDirtyOnChange.cancel(); + this.setState({ submitted: true }); + } else { + this.setDirty(); + } } } @@ -291,7 +285,11 @@ export class Input extends React.Component { const { defaultValue, value, dirty, validators } = this.props; if (prevProps.value !== value || defaultValue !== prevProps.defaultValue) { - this.validate(); + if (!this.state.submitted) { + this.validate(); + } else { + this.setState({ submitted: false }); + } this.autoFitHeight(); }