import { AbsoluteCenter, Avatar, Box, Combobox, HStack, Icon, Menu, Portal, Span, Spinner, StackSeparator, Text, InputGroup, type ListCollection, Flex } from "@chakra-ui/react"
import type { MetadataResultBase, MetadataResultImage, MetadataResultServiceScore } from "../../../core/Api"
import { getMusicServiceIconElement } from "../icons/ChakraIcons"
import React, { useRef } from "react"
import { MSErrorBoundary } from "../ErrorBoundary"
import { EllipsisButtonMenu } from "../buttonMenus/ButtonMenu"
export const LeftSideMetadataResultContent = (props: MetadataResultServiceScore & MetadataResultImage) => {
if (props.image === undefined) {
return
}
return (
}
{...props} />
)
}
export const MetadataServiceScore = (props: MetadataResultServiceScore) => (
}>
{getMusicServiceIconElement(props.service)}
{props.score !== undefined ? {props.score} : undefined}
)
export const ArtworkAvatar = (props: MetadataResultImage & { overlay?: React.JSX.Element }) => {
let overlayContent: React.JSX.Element | undefined = undefined;
if (props.overlay !== undefined) {
overlayContent = {props.overlay}
}
return (
{overlayContent}
)
}
export type MetadataPartials = Record T }>;
export interface MetadataPickMenuProps {
data: T
partials: MetadataPartials
onPick?: (val: T) => void
}
export const MetadataPickMenu = (props: MetadataPickMenuProps) => {
const { data, partials, onPick } = props;
if (onPick === undefined) {
return undefined;
}
// menu content is portaled but still bubbles through the React tree
// stop it here so Combobox does not select the whole item or treat Enter as freetext
return (
e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()}>
(
{label}
))}
menuCallback={(select) => onPick(partials[select.value].pick(data))}
/>
);
}
export type MetadataSearchResult = MetadataResultBase & MetadataResultServiceScore;
export interface MetadataSearchComboboxProps {
placeholder: string
collection: ListCollection
isLoading: boolean
isError: boolean
inputGroupContent?: React.JSX.Element
initialInput?: string
onChange: (val: T) => void
/** Called with the typed text when the user commits it without selecting a result */
onFreetext: (name: string) => void
onQueryChange: (query: string) => void
renderItem: (item: T, onPick: (val: T) => void) => React.JSX.Element
}
export const MetadataSearchCombobox = (props: MetadataSearchComboboxProps) => {
const { placeholder, collection, isLoading, isError, onChange, onFreetext, onQueryChange, renderItem, initialInput = '' } = props;
// a ref, not state: combobox captures onInteractOutside when the popup opens so state read there is stale
const rawInput = useRef(initialInput === '' ? undefined : initialInput);
const commitFreetext = () => {
if (rawInput.current !== undefined && rawInput.current !== initialInput) {
onFreetext(rawInput.current);
}
};
const inputElm = ;
let input: React.JSX.Element;
if(props.inputGroupContent !== undefined) {
input = (
{inputElm}
)
} else {
input = inputElm;
}
return (
{
// combobox prevents default when Enter selects a highlighted item
if (e.key === 'Enter' && !e.defaultPrevented) {
commitFreetext();
}
}}
collection={collection}
onInteractOutside={commitFreetext}
onValueChange={(val) => {
console.log(val, 'value change');
onChange(val.items[0]);
rawInput.current = undefined;
}}
onSelect={(val) => {
console.log(val, 'select')
}}
onInputValueChange={(e) => {
onQueryChange(e.inputValue);
// selecting an item rewrites the input, this is not freetext
if (e.reason !== 'item-select') {
rawInput.current = e.inputValue;
}
}}
>
{input}
{isLoading ? (
Loading...
) : isError ? (
Error fetching
) : (
{(combobox) => collection.items?.map((item) => (
{renderItem(item, (val) => {
onChange(val);
rawInput.current = undefined;
combobox.setInputValue(collection.stringifyItem(val) ?? '', 'item-select');
combobox.setOpen(false);
})}
))}
)}
No items found
);
}