lifeto-shop/src/components/inventory/movetarget.tsx

177 lines
4.9 KiB
TypeScript
Raw Normal View History

2024-08-12 01:13:42 +00:00
2025-05-25 05:17:41 +00:00
import { forwardRef, useId, useMemo, useRef, useState} from "react";
2025-05-13 21:02:59 +00:00
import { useAtom, useAtomValue } from "jotai";
import { autoUpdate, flip, FloatingFocusManager, FloatingPortal, size, useDismiss, useFloating, useInteractions, useListNavigation, useRole } from "@floating-ui/react";
import Fuse from "fuse.js";
2025-05-25 05:17:41 +00:00
import { charactersAtom, selectedTargetInventoryAtom } from "@/state/atoms";
2024-08-12 01:13:42 +00:00
2025-05-25 05:17:41 +00:00
interface AccountInventorySelectorItemProps {
2025-05-13 21:02:59 +00:00
children: React.ReactNode;
active: boolean;
}
2025-05-25 05:17:41 +00:00
const AccountInventorySelectorItem = forwardRef<
2025-05-13 21:02:59 +00:00
HTMLDivElement,
2025-05-25 05:17:41 +00:00
AccountInventorySelectorItemProps & React.HTMLProps<HTMLDivElement>
2025-05-13 21:02:59 +00:00
>(({ children, active, ...rest }, ref) => {
const id = useId();
return (
<div
ref={ref}
role="option"
id={id}
aria-selected={active}
{...rest}
style={{
background: active ? "lightblue" : "none",
padding: 4,
cursor: "default",
...rest.style,
}}
>
{children}
</div>
);
});
2025-05-25 05:17:41 +00:00
export const InventoryTargetSelector = () => {
2025-05-13 21:02:59 +00:00
const [open, setOpen] = useState(false);
const [inputValue, setInputValue] = useState("");
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const listRef = useRef<Array<HTMLElement | null>>([]);
const { refs, floatingStyles, context } = useFloating<HTMLInputElement>({
whileElementsMounted: autoUpdate,
open,
onOpenChange: setOpen,
middleware: [
flip({ padding: 10 }),
size({
apply({ rects, availableHeight, elements }) {
Object.assign(elements.floating.style, {
width: `${rects.reference.width}px`,
maxHeight: `${availableHeight}px`,
});
},
padding: 10,
}),
],
});
const role = useRole(context, { role: "listbox" });
const dismiss = useDismiss(context);
const listNav = useListNavigation(context, {
listRef,
activeIndex,
onNavigate: setActiveIndex,
virtual: true,
loop: true,
});
const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions(
[role, dismiss, listNav]
);
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
const value = event.target.value;
setInputValue(value);
setSelectedTargetInventory(undefined)
if (value) {
setOpen(true);
setActiveIndex(0);
} else {
setOpen(false);
}
}
const { data: subaccounts } = useAtomValue(charactersAtom)
const [selectedTargetInventory, setSelectedTargetInventory] = useAtom(selectedTargetInventoryAtom)
const searcher = useMemo(()=>{
return new Fuse(subaccounts?.flatMap(x=>[
x.bank,
x.character,
])||[], {
keys:["path","name"],
findAllMatches: true,
threshold: 0.8,
useExtendedSearch: true,
})
}, [subaccounts])
const items = searcher.search(inputValue || "!-", {limit: 10}).map(x=>x.item)
return (
<>
<input
className="border border-black-1 bg-gray-100 placeholder-gray-600"
{...getReferenceProps({
ref: refs.setReference,
onChange,
value: selectedTargetInventory !== undefined ? selectedTargetInventory.name : inputValue,
placeholder: "Target Inventory",
"aria-autocomplete": "list",
2025-05-25 05:17:41 +00:00
onFocus() {
setOpen(true);
},
2025-05-13 21:02:59 +00:00
onKeyDown(event) {
if (
event.key === "Enter" &&
activeIndex != null &&
items[activeIndex]
) {
setSelectedTargetInventory(items[activeIndex])
setInputValue(items[activeIndex].name);
setActiveIndex(null);
setOpen(false);
}
},
})}
/>
{open && (
<FloatingPortal>
<FloatingFocusManager
context={context}
initialFocus={-1}
visuallyHiddenDismiss
>
<div
{...getFloatingProps({
ref: refs.setFloating,
style: {
...floatingStyles,
background: "#eee",
color: "black",
overflowY: "auto",
},
})}
>
{items.map((item, index) => (
2025-05-25 05:17:41 +00:00
<AccountInventorySelectorItem
2025-05-13 21:02:59 +00:00
{...getItemProps({
key: item.path,
ref(node) {
listRef.current[index] = node;
},
onClick() {
setInputValue(item.name);
setSelectedTargetInventory(item);
setOpen(false);
refs.domReference.current?.focus();
},
})}
active={activeIndex === index}
>
{item.name}
2025-05-25 05:17:41 +00:00
</AccountInventorySelectorItem>
2025-05-13 21:02:59 +00:00
))}
</div>
</FloatingFocusManager>
</FloatingPortal>
)}
</>
);
}