normally i work a lot in Vue.js and we have a api called scoped slots. Basically what it comes do to is that we can pass a slot to a component and use the data from the parent in the slot, which would also show what data is being passed via Typescript.
My problem is that i want to create a combobox select which as default shows a normal list as items. But via generics it could pass a complete other object as long as it has the ID and Label properties. I would like to also pass a component via the prop which it should render inside a for loop which can access the option he needs to render.
import { cloneElement } from "react";
import { Listbox } from "@headlessui/react";
import { CheckIcon } from "@heroicons/react/20/solid";
export type ListboxOptionBase = {
label: string;
id: string;
};
type Props<TOpion> = {
listItem?: React.ReactElement;
options: TOpion[];
};
type SelectOptionProps<T extends ListboxOptionBase = ListboxOptionBase> = {
option: T;
selected: boolean;
active: boolean;
};
export function selectOption({ option, selected, active }: SelectOptionProps) {
return (
<div>
{option.label}
{selected ? <CheckIcon className="h-5 w-5" aria-hidden="true" /> : null}
</div>
);
}
// Spreading props will lose types for multiple: true|false for the value to be an array or not
export default function Select<T extends ListboxOptionBase>(props: Props<T>) {
const renderElement = ({ active, selected, option }: SelectOptionProps) => {
return cloneElement(props.listItem ?? selectOption, {
option,
selected,
active,
});
};
return props.options.map((option) => (
<Listbox.Option key={option.id} value={option.id}>
{({ selected, active }) => renderElement({ option, selected, active })}
</Listbox.Option>
));
}
If i dont pass anything like this
<Select options={options} />
it should render the selectOption
But here i want to provide it with a custom selectOption which also holds different data.
type CustomOption = ListboxOptionBase & { image: string };
export function customSelectOption({ option, selected, active }: SelectOptionProps<CustomOption> {
return (
<div>
{option.image}
{option.label}
{selected ? <CheckIcon className="h-5 w-5" aria-hidden="true" /> : null}
</div>
);
}
<Select options={options} listItem={customSelectOption}/>
Is something like this even doable?
Above code is what i tried but no luck so far.