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

TextInput won't let Arrow Left and Right keys propagate outside when focused #22

Merged
merged 10 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions KaiUIngInferno/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
},
"devDependencies": {
"@babel/core": "^7.21.0",
"@parcel/packager-ts": "2.8.3",
"@parcel/packager-ts": "2.9.3",
"@parcel/transformer-babel": "^2.8.3",
"@parcel/transformer-sass": "^2.8.3",
"@parcel/transformer-typescript-tsc": "^2.8.3",
"@parcel/transformer-typescript-types": "2.8.3",
"@parcel/transformer-typescript-types": "2.9.3",
"babel-plugin-inferno": "^6.6.0",
"classnames": "^2.3.2",
"inferno": "^8.2.2",
Expand Down
4 changes: 4 additions & 0 deletions KaiUIngInferno/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import Button from "./ui/Button";

import Avatar from "./ui/Avatar";
import DropDownMenu from "./DropDownMenu";
import OptionItem from "./ui/OptionItem";
import OptionMenu from "./ui/OptionMenu";

import ListView from "./views/ListView";
import ListViewKeyed from "./views/ListViewKeyed";
Expand Down Expand Up @@ -37,4 +39,6 @@ export {
Button,
asArray,
toast,
OptionMenu,
OptionItem,
};
20 changes: 20 additions & 0 deletions KaiUIngInferno/src/ui/OptionItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
interface OptionItemProps {
text: string;
isFocused?: boolean;
}

export default function OptionItem({ text, isFocused }: OptionItemProps) {
return (
<div
tabIndex={0}
ref={(ref: HTMLElement | null) => {
if (ref) {
isFocused ? ref.focus() : ref.blur();
}
}}
className="kai-om-item"
$HasTextChildren
>
{text}
</div>);
}
105 changes: 105 additions & 0 deletions KaiUIngInferno/src/ui/OptionMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Component } from "inferno";
import TextInput from "./TextInput";
import "KaiUI/src/components/OptionMenu/OptionMenu.scss";
import { asArray } from "../utils";

interface OptionMenuProps {
header: string;
children: any;
onChangeIndex?: (index: number) => void;
isActive: boolean;
onExit: () => void;
enableSearch?: boolean;
}

interface OptionMenuState {
selectedItem: number;
searchTerm: string;
}

export default class OptionMenu extends Component<OptionMenuProps, OptionMenuState> {
public state: OptionMenuState;

handleKeyDown = (evt: KeyboardEvent) => {
if (!this.props.isActive) {
return;
}
const childrenArray: any[] = asArray(this.props.children);
const childrenLength = childrenArray.length;
evt.stopPropagation();
let index = this.state.selectedItem;
switch (evt.key) {
case "Backspace":
index !== 0 && this.props.onExit();
break;
case "ArrowDown":
index--;
break;
case "ArrowUp":
index++;
break;
default:
break;
}
index = (index + childrenLength) % childrenLength;
this.setState({ selectedItem: index });
}

constructor(props: any) {
super(props);
this.state = {
selectedItem: 0,
searchTerm: ""
}
}

componentDidMount() {
document.addEventListener("keydown", this.handleKeyDown);
}

componentWillUnmount() {
document.removeEventListener("keydown", this.handleKeyDown);
}

componentDidUpdate(lastProps: OptionMenuProps, lastState: OptionMenuState) {
lastProps.onChangeIndex && lastProps.onChangeIndex(lastState.selectedItem);
}

render() {
const { searchTerm, selectedItem } = this.state;
let childrenToRender = this.props.children;
if (this.props.enableSearch) {
childrenToRender.unshift(
<TextInput
id="optMenuSearch"
defaultValue={searchTerm}
placeholder="Search"
onChange={(text: string) => this.setState({ searchTerm: text })}
label=""
fieldType="text"
/>
);
}

childrenToRender = childrenToRender.filter((child: any) => {
if (child.props.fieldType === "text") {
return true;
}
if (child.props.text && child.props.text.indexOf(searchTerm) >= 0) {
return true;
}
return false;
});

childrenToRender[selectedItem].props.isFocused = true;

return (
<div className="kai-om">
<header $HasTextChildren>{this.props.header}</header>
<nav $HasKeyedChildren>
{childrenToRender}
</nav>
</div>
);
}
};
2 changes: 1 addition & 1 deletion KaiUIngInferno/src/ui/SoftKey.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface ButtonProps {

const prefixCls = "kai-softkey";
function Button(props: ButtonProps) {
let renderedIcon: JSX.Element;
let renderedIcon: Component;
if (props.icon && props.icon.toString().indexOf("kai-") === -1) {
renderedIcon = <img src={props.icon} width={20} height={20} alt="" />;
} else {
Expand Down
48 changes: 34 additions & 14 deletions KaiUIngInferno/src/ui/TextInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component } from "inferno";
import { Component, RefObject, createRef } from "inferno";
import classnames from "classnames";
import "KaiUI/src/components/TextInput/TextInput.scss";
import morecolor from "../morecolor";
Expand All @@ -7,7 +7,7 @@ const prefixCls = "kai-text-input";
const labelCls = `${prefixCls}-label p-thi`;
const inputCls = `${prefixCls}-label p-pri`;

interface TextInputProps {
interface Props {
onChange?: (text: string) => void;
isFocused?: boolean;
fieldType: string;
Expand All @@ -18,26 +18,49 @@ interface TextInputProps {
focusClass?: string;
}

class TextInput extends Component<TextInputProps> {
interface State {
value: string;
}

class TextInput extends Component<Props, State> {
private onChange: (_evt?: Event) => void;
private textInput: any;
private textInputRef: RefObject<HTMLInputElement>;
public state: { value: string };

constructor(props: TextInputProps) {
onKeyDown = (evt: KeyboardEvent) => {
if (!this.props.isFocused) return; // do we need this? --Farooq
if (evt.key === "ArrowLeft" || evt.key === "ArrowRight") {
evt.stopImmediatePropagation();
}
};

constructor(props: any) {
const { defaultValue } = props;
super(props);
this.textInputRef = createRef();
this.onChange = (_evt?: Event) => {
this.setState({ value: this.textInput.value });
if (this.props.onChange) this.props.onChange(this.textInput.value);
if (!this.textInputRef.current) return;
this.setState({ value: this.textInputRef.current.value });
if (this.props.onChange) this.props.onChange(this.textInputRef.current.value);
};

this.state = {
value: defaultValue || "",
};
}



componentDidMount() {
this.textInputRef.current?.addEventListener("keydown", this.onKeyDown, true)
}

componentWillUnmount() {
this.textInputRef.current?.removeEventListener("keydown", this.onKeyDown, true)
}

componentDidUpdate() {
if (this.props.isFocused) this.textInput.focus();
if (this.props.isFocused) this.textInputRef.current?.focus();
}

render() {
Expand All @@ -51,9 +74,8 @@ class TextInput extends Component<TextInputProps> {
id={this.props.id}
tabIndex={0}
className={itemCls}
style={`background-color: ${
this.props.isFocused ? morecolor.focusColor : ""
}`}
style={`background-color: ${this.props.isFocused ? morecolor.focusColor : ""
}`}
>
<label className={labelCls} $HasTextChildren>
{this.props.label}
Expand All @@ -70,9 +92,7 @@ class TextInput extends Component<TextInputProps> {
defaultValue={this.state.value || ""}
placeholder={this.props.placeholder || ""}
style={`color: ${this.props.isFocused ? "var(--text-color)" : ""}`}
ref={(input) => {
this.textInput = input;
}}
ref={this.textInputRef}
/>
</div>
);
Expand Down
4 changes: 1 addition & 3 deletions KaiUIngInferno/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { VNode } from "inferno";

function asArray(children: VNode | VNode[]) : VNode[] {
function asArray(children: any | any[]) : any[] {
if (children instanceof Array) {
return children;
} else {
Expand Down
Loading