diff --git a/KaiUIngInferno/package.json b/KaiUIngInferno/package.json
index 4843766..bfa0982 100644
--- a/KaiUIngInferno/package.json
+++ b/KaiUIngInferno/package.json
@@ -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",
diff --git a/KaiUIngInferno/src/index.ts b/KaiUIngInferno/src/index.ts
index 0ba3025..d8cfe28 100644
--- a/KaiUIngInferno/src/index.ts
+++ b/KaiUIngInferno/src/index.ts
@@ -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";
@@ -37,4 +39,6 @@ export {
Button,
asArray,
toast,
+ OptionMenu,
+ OptionItem,
};
diff --git a/KaiUIngInferno/src/ui/OptionItem.tsx b/KaiUIngInferno/src/ui/OptionItem.tsx
new file mode 100644
index 0000000..e4ceec4
--- /dev/null
+++ b/KaiUIngInferno/src/ui/OptionItem.tsx
@@ -0,0 +1,20 @@
+interface OptionItemProps {
+ text: string;
+ isFocused?: boolean;
+}
+
+export default function OptionItem({ text, isFocused }: OptionItemProps) {
+ return (
+
{
+ if (ref) {
+ isFocused ? ref.focus() : ref.blur();
+ }
+ }}
+ className="kai-om-item"
+ $HasTextChildren
+ >
+ {text}
+
);
+}
diff --git a/KaiUIngInferno/src/ui/OptionMenu.tsx b/KaiUIngInferno/src/ui/OptionMenu.tsx
new file mode 100644
index 0000000..3486285
--- /dev/null
+++ b/KaiUIngInferno/src/ui/OptionMenu.tsx
@@ -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 {
+ 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(
+