{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "suggestion",
  "title": "Suggestion",
  "description": "MateChat Suggestion",
  "files": [
    {
      "path": "src/suggestion.tsx",
      "content": "import React from \"react\";\nimport type { SelectItemOptionsType } from \"@/list\";\nimport { List } from \"@/list\";\n\nexport interface TriggerConfig {\n  char: string;\n  data: SelectItemOptionsType;\n  optionLabel: string;\n  optionGroupLabel?: string;\n  optionGroupChildren?: string;\n}\n\nexport type OnTextInject = (newText: string, newCursorPosition: number) => void;\n\ninterface SuggestionProps {\n  message: string;\n  textareaRef: React.RefObject<HTMLTextAreaElement | null>;\n  triggerConfigs: TriggerConfig[];\n  onInject: OnTextInject;\n}\n\nconst useSuggestionContext = (\n  message: string,\n  textareaRef: React.RefObject<HTMLTextAreaElement | null>,\n  triggerConfigs: TriggerConfig[],\n) => {\n  const context = React.useMemo(() => {\n    const textarea = textareaRef?.current;\n    if (!textarea) return null;\n\n    const caretPositionIndex = textarea.selectionStart ?? message.length;\n    let currentConfig: TriggerConfig | undefined;\n    let triggerIndex = -1;\n    let queryText = \"\";\n\n    for (let i = caretPositionIndex - 1; i >= 0; i--) {\n      const char = message[i];\n      const matchedConfig = triggerConfigs.find(\n        (config) => config.char === char,\n      );\n      if (matchedConfig) {\n        currentConfig = matchedConfig;\n        triggerIndex = i;\n        queryText = message.slice(i + 1, caretPositionIndex);\n        break;\n      }\n    }\n    if (!currentConfig) return null;\n    return {\n      currentConfig,\n      triggerIndex,\n      queryText,\n    };\n  }, [message, textareaRef, triggerConfigs]);\n  return context;\n};\nexport default function Suggestion({\n  message,\n  textareaRef,\n  triggerConfigs,\n  onInject,\n}: SuggestionProps) {\n  const context = useSuggestionContext(message, textareaRef, triggerConfigs);\n\n  if (!context) return null;\n\n  return (\n    <div className=\"absolute bottom-full left-0 w-full bg-white dark:bg-gray-50 rounded-lg shadow-amber-50 max-h-64 overflow-y-auto\">\n      <List\n        options={context.currentConfig.data}\n        optionLabel={context.currentConfig.optionLabel ?? \"default value\"}\n        optionGroupLabel={context.currentConfig.optionGroupLabel}\n        optionGroupChildren={context.currentConfig.optionGroupChildren}\n        onChange={(e) => {\n          const newText: string = (e.value ?? e.target.value) as string;\n          onInject(newText, context.triggerIndex);\n        }}\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "src/list.tsx",
      "content": "import clsx from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport type UnknownRecord = Record<string, unknown>;\n\nexport type GroupData = UnknownRecord & {\n  [key: string]: unknown;\n};\n\nexport type OptionData = UnknownRecord & {\n  [key: string]: unknown;\n};\n\nexport type SelectItemOptionsType = OptionData[] | GroupData[];\n\ninterface ListChangeTargetOptions {\n  name: string;\n  id: string;\n  value: unknown;\n}\n\ninterface ListChangeEvent {\n  originalEvent: React.SyntheticEvent;\n  value: unknown;\n  stopPropagation(): void;\n  preventDefault(): void;\n  target: ListChangeTargetOptions;\n}\n\nexport interface ListProps\n  extends Omit<\n    React.DetailedHTMLProps<\n      React.HTMLAttributes<HTMLDivElement>,\n      HTMLDivElement\n    >,\n    \"onChange\"\n  > {\n  listBoxStyle?: React.CSSProperties | undefined;\n  listGroupStyle?: React.CSSProperties | undefined;\n  listItemStyle?: React.CSSProperties | undefined;\n  listStyle?: React.CSSProperties | undefined;\n  optionGroupChildren?: string | undefined;\n  optionGroupLabel?: string | undefined;\n  optionLabel: string;\n  options?: SelectItemOptionsType | undefined;\n  value?: string | null;\n  onChange?: (event: ListChangeEvent) => void;\n}\n\ninterface OptionGroupItem {\n  optionGroup: GroupData;\n  group: boolean;\n  index: number;\n  label: unknown;\n}\n\nexport type MixedOptionArray = Array<OptionGroupItem | OptionData>;\n\nexport const List = ({\n  listBoxStyle,\n  listGroupStyle,\n  listItemStyle,\n  listStyle,\n  optionGroupChildren,\n  optionGroupLabel,\n  optionLabel,\n  options,\n  value,\n  onChange,\n  ...props\n}: ListProps) => {\n  const handleSelect = (\n    event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>,\n    option: UnknownRecord,\n  ) => {\n    const optionValue = option[optionLabel];\n    const changeEvent: ListChangeEvent = {\n      originalEvent: event,\n      value: optionValue,\n      stopPropagation: () => event.stopPropagation(),\n      preventDefault: () => event.preventDefault(),\n      target: {\n        name: option.label?.toString() || \"\",\n        id: \"list\",\n        value: optionValue,\n      },\n    };\n    onChange?.(changeEvent);\n  };\n\n  const flatOptions = (options: GroupData[] | undefined) => {\n    return (options || []).reduce(\n      (result: MixedOptionArray, option: GroupData, index: number) => {\n        result.push({\n          optionGroup: option,\n          group: true,\n          index,\n          label: option[optionGroupLabel as keyof GroupData],\n        });\n\n        const groupChildren = optionGroupChildren\n          ? option[optionGroupChildren as keyof GroupData]\n          : null;\n\n        if (Array.isArray(groupChildren)) {\n          groupChildren.forEach((o: OptionData) => {\n            result.push(o);\n          });\n        }\n        return result;\n      },\n      [] as MixedOptionArray,\n    );\n  };\n\n  const createItem = (option: OptionData | OptionGroupItem, index: number) => {\n    const isOptionGroupItem = (\n      item: OptionData | OptionGroupItem,\n    ): item is OptionGroupItem => {\n      return \"group\" in item && item.group === true;\n    };\n\n    if (isOptionGroupItem(option)) {\n      const groupContent =\n        option.optionGroup[optionGroupLabel as keyof GroupData];\n      const key = `group_${index}_${String(groupContent)}`;\n\n      return (\n        <li\n          key={key}\n          role-slot=\"list-group\"\n          aria-label={String(groupContent || \"\")}\n          className={clsx(\"font-bold dark:text-gray-400 py-2 px-4 text-sm\")}\n          style={listGroupStyle}\n        >\n          {String(groupContent || \" \")}\n        </li>\n      );\n    }\n\n    const optionData = option as OptionData;\n    const optionKey = optionData[optionLabel as keyof OptionData];\n    const key = `option_${index}_${String(optionKey)}`;\n    const handleClick = (e: React.MouseEvent<HTMLElement>) => {\n      handleSelect(e, optionData);\n    };\n\n    const optionValue = optionData[optionLabel];\n    const isSelected = String(optionValue) === String(value);\n\n    return (\n      <li\n        key={key}\n        role-slot=\"list-item\"\n        className={twMerge(\n          clsx(\"py-2 px-4 hover:bg-gray-200 text-gray-600 cursor-pointer\", {\n            \"bg-blue-500 text-white hover:bg-blue-600\": isSelected,\n          }),\n        )}\n        id={`lo_id_${index}`}\n        style={listItemStyle}\n        onClick={handleClick}\n        onKeyDown={(e) => {\n          if (e.key === \"Enter\" || e.key === \" \") {\n            e.preventDefault();\n            handleSelect(\n              e as\n                | React.MouseEvent<HTMLElement>\n                | React.KeyboardEvent<HTMLElement>,\n              optionData,\n            );\n          }\n        }}\n      >\n        {String(optionKey || \"\")}\n      </li>\n    );\n  };\n\n  const createList = () => {\n    const finalOptions = optionGroupLabel\n      ? flatOptions(options as GroupData[] | undefined)\n      : (options as OptionData[]);\n\n    return (\n      <ul role-slot=\"listbox\" style={listBoxStyle}>\n        {finalOptions?.map((option, index) => createItem(option, index))}\n      </ul>\n    );\n  };\n\n  const list = createList();\n\n  return (\n    <div style={listStyle} data-slot=\"list\" {...props}>\n      {list}\n    </div>\n  );\n};\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}