Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 45 additions & 21 deletions app/components/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,25 @@ import { getCategoryColorName, getCategoryColorValue, Graph } from "./model"
import { useEffect, useRef, useState } from "react"
import { PathNode } from "../page"
import { cn } from "@/lib/utils"
import twcolors from 'tailwindcss/colors'

let colors = twcolors as any
import { prepareArg } from "../utils"

interface Props extends React.InputHTMLAttributes<HTMLInputElement> {
value?: string
graph: Graph
onValueChange: (node: PathNode) => void
handelSubmit?: (node: any) => void
handleSubmit?: (node: any) => void
icon?: React.ReactNode
node?: PathNode
parentClassName?: string
scrollToBottom?: () => void
}

export default function Input({ value, onValueChange, handelSubmit, graph, icon, node, className, parentClassName, scrollToBottom, ...props }: Props) {
export default function Input({ value, onValueChange, handleSubmit, graph, icon, node, className, parentClassName, scrollToBottom, ...props }: Props) {

const [open, setOpen] = useState(false)
const [options, setOptions] = useState<any[]>([])
const [selectedOption, setSelectedOption] = useState<number>(0)
const inputRef = useRef<HTMLInputElement>(null)
const [inputHeight, setInputHeight] = useState(0)
const containerRef = useRef<HTMLDivElement>(null)

useEffect(() => {
Expand All @@ -35,6 +33,7 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon,
}, [open])

useEffect(() => {
let isLastRequest = true
const timeout = setTimeout(async () => {

if (!value || node?.id) {
Expand All @@ -45,7 +44,7 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon,
return
}

const result = await fetch(`/api/repo/${graph.Id}/?prefix=${value}`, {
const result = await fetch(`/api/repo/${prepareArg(graph.Id)}/?prefix=${prepareArg(value)}`, {
method: 'POST'
})

Expand All @@ -58,6 +57,8 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon,
return
}

if (!isLastRequest) return

const json = await result.json()
const { completions } = json.result

Expand All @@ -70,17 +71,21 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon,
}
}, 500)

return () => clearTimeout(timeout)
return () => {
clearTimeout(timeout)
isLastRequest = false
}
}, [value])

const handelKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
const container = containerRef.current
switch (e.code) {
case "Enter": {
e.preventDefault()
const option = options.find((o, i) => i === selectedOption)
if (!option) return
if (handelSubmit) {
handelSubmit(option)
if (handleSubmit) {
handleSubmit(option)
} else {
if (!open) return
onValueChange({ name: option.properties.name, id: option.id })
Expand All @@ -91,16 +96,22 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon,
case "ArrowUp": {
e.preventDefault()
setSelectedOption(prev => {
containerRef.current?.scrollTo({ behavior: 'smooth', top: (prev <= 0 ? options.length - 1 : prev - 1) * containerRef.current.children[0].clientHeight })
return prev <= 0 ? options.length - 1 : prev - 1
const newIndex = prev <= 0 ? options.length - 1 : prev - 1
if (container) {
container.scrollTo({ behavior: 'smooth', top: 64 * newIndex })
}
return newIndex
})
return
}
case "ArrowDown": {
e.preventDefault()
setSelectedOption(prev => {
containerRef.current?.scrollTo({ behavior: 'smooth', top: ((prev + 1) % options.length) * containerRef.current.children[0].clientHeight })
return (prev + 1) % options.length
const newIndex = (prev + 1) % options.length
if (container) {
container.scrollTo({ behavior: 'smooth', top: Math.min(64 * newIndex, container.scrollHeight) })
}
return newIndex
})
return
}
Expand All @@ -124,8 +135,12 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon,
data-name='search-bar'
>
<input
ref={inputRef}
onKeyDown={handelKeyDown}
ref={ref => {
if (ref) {
setInputHeight(ref.scrollHeight)
}
}}
onKeyDown={handleKeyDown}
className={cn("w-full border p-2 rounded-md pointer-events-auto", className)}
value={value || ""}
onChange={(e) => {
Expand All @@ -146,16 +161,25 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon,
className="z-10 w-full bg-white absolute flex flex-col pointer-events-auto border rounded-md max-h-[50dvh] overflow-y-auto overflow-x-hidden p-2 gap-2"
data-name='search-bar-list'
style={{
top: (inputRef.current?.clientHeight || 0) + 16
top: inputHeight + 16
}}
>
{
options.length > 0 &&
options.map((option, index) => {
const label = option.labels[0]
const name = option.properties.name
const path = option.properties.path
const colorName = getCategoryColorName(graph.CategoriesMap.get(label)!.index)
const color = getCategoryColorValue(graph.CategoriesMap.get(label)!.index)
let category = graph.CategoriesMap.get(label)

if (!category) {
category = { name: label, index: graph.CategoriesMap.size, show: true }
graph.CategoriesMap.set(label, category)
graph.Categories.push(category)
}

const colorName = getCategoryColorName(category.index)
const color = getCategoryColorValue(category.index)
return (
<button
className={cn(
Expand All @@ -165,7 +189,7 @@ export default function Input({ value, onValueChange, handelSubmit, graph, icon,
onMouseEnter={() => setSelectedOption(index)}
onClick={() => {
onValueChange({ name: option.properties.name, id: option.id })
handelSubmit && handelSubmit(option)
handleSubmit && handleSubmit(option)
setOpen(false)
}}
key={option.id}
Expand Down
Loading
Loading