import React, { useMemo, useCallback, useRef, forwardRef } from "react" import { atom, useAtom } from "jotai" import { useVirtualizer } from "@tanstack/react-virtual" import { Button, buttonVariants } from "@/components/ui/button" import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover" import { cn } from "@/lib/utils" import { LaIcon } from "@/components/custom/la-icon" import { Command, CommandInput, CommandList, CommandItem, CommandGroup } from "@/components/ui/command" import { useCoState } from "@/lib/providers/jazz-provider" import { PublicGlobalGroup } from "@/lib/schema/master/public-group" import { ListOfTopics, Topic } from "@/lib/schema" import { JAZZ_GLOBAL_GROUP_ID } from "@/lib/constants" import { VariantProps } from "class-variance-authority" interface TopicSelectorProps extends VariantProps { showSearch?: boolean defaultLabel?: string searchPlaceholder?: string value?: string | null onChange?: (value: string) => void onTopicChange?: (value: Topic) => void className?: string renderSelectedText?: (value?: string | null) => React.ReactNode side?: "bottom" | "top" | "right" | "left" align?: "center" | "end" | "start" } export const topicSelectorAtom = atom(false) export const TopicSelector = forwardRef( ( { showSearch = true, defaultLabel = "Select topic", searchPlaceholder = "Search topic...", value, onChange, onTopicChange, className, renderSelectedText, side = "bottom", align = "end", ...props }, ref ) => { const [isTopicSelectorOpen, setIsTopicSelectorOpen] = useAtom(topicSelectorAtom) const group = useCoState(PublicGlobalGroup, JAZZ_GLOBAL_GROUP_ID, { root: { topics: [] } }) const handleSelect = useCallback( (selectedTopicName: string, topic: Topic) => { onChange?.(selectedTopicName) onTopicChange?.(topic) setIsTopicSelectorOpen(false) }, [onChange, setIsTopicSelectorOpen, onTopicChange] ) const displaySelectedText = useMemo(() => { if (renderSelectedText) { return renderSelectedText(value) } return {value || defaultLabel} }, [value, defaultLabel, renderSelectedText]) return ( e.preventDefault()} > {group?.root.topics && ( )} ) } ) TopicSelector.displayName = "TopicSelector" interface TopicSelectorContentProps extends Omit { onSelect: (value: string, topic: Topic) => void topics: ListOfTopics } const TopicSelectorContent: React.FC = React.memo( ({ showSearch, searchPlaceholder, value, onSelect, topics }) => { const [search, setSearch] = React.useState("") const filteredTopics = useMemo( () => topics.filter(topic => topic?.prettyName.toLowerCase().includes(search.toLowerCase())), [topics, search] ) const parentRef = useRef(null) const rowVirtualizer = useVirtualizer({ count: filteredTopics.length, getScrollElement: () => parentRef.current, estimateSize: () => 35, overscan: 5 }) return ( {showSearch && ( )}
{rowVirtualizer.getVirtualItems().map(virtualRow => { const topic = filteredTopics[virtualRow.index] return ( topic && ( onSelect(value, topic)} style={{ position: "absolute", top: 0, left: 0, width: "100%", height: `${virtualRow.size}px`, transform: `translateY(${virtualRow.start}px)` }} > {topic.prettyName} ) ) })}
) } ) TopicSelectorContent.displayName = "TopicSelectorContent" export default TopicSelector