feat: add rpc-calls page
This commit is contained in:
@@ -10,17 +10,26 @@ import {
|
||||
CommandList,
|
||||
} from "./ui/command"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export const SearchableSelect = <T,>({
|
||||
value,
|
||||
setValue,
|
||||
options,
|
||||
className,
|
||||
contentClassName,
|
||||
allowCustomValue,
|
||||
}: {
|
||||
value: T | null
|
||||
setValue: (val: T | null) => void
|
||||
options: { value: T; text: string }[]
|
||||
className?: string
|
||||
contentClassName?: string
|
||||
// only for T=string
|
||||
allowCustomValue?: boolean
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [filterValue, setFilterValue] = useState("")
|
||||
|
||||
const onTriggerKeyDown = (evt: React.KeyboardEvent) => {
|
||||
if (evt.key.length === 1) {
|
||||
@@ -28,6 +37,38 @@ export const SearchableSelect = <T,>({
|
||||
}
|
||||
}
|
||||
|
||||
const lowerCaseFilter = filterValue.toLocaleLowerCase()
|
||||
const hasExactMatch = options.some((v) => v.value === filterValue)
|
||||
const filteredOptions = allowCustomValue
|
||||
? [
|
||||
...options
|
||||
.filter(({ text }) =>
|
||||
text.toLocaleLowerCase().includes(lowerCaseFilter),
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const aLowerCase = a.text.toLocaleLowerCase()
|
||||
if (aLowerCase === lowerCaseFilter) return -1
|
||||
const bLowerCase = b.text.toLocaleLowerCase()
|
||||
if (bLowerCase === lowerCaseFilter) return 1
|
||||
|
||||
const aScore = aLowerCase.startsWith(lowerCaseFilter) ? 1 : 0
|
||||
const bScore = bLowerCase.startsWith(lowerCaseFilter) ? 1 : 0
|
||||
if (aScore != bScore) {
|
||||
return bScore - aScore
|
||||
}
|
||||
return aLowerCase.localeCompare(bLowerCase)
|
||||
}),
|
||||
...(hasExactMatch || !filterValue
|
||||
? []
|
||||
: [
|
||||
{
|
||||
value: filterValue as T,
|
||||
text: filterValue,
|
||||
},
|
||||
]),
|
||||
]
|
||||
: options
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild onKeyDown={onTriggerKeyDown}>
|
||||
@@ -35,11 +76,15 @@ export const SearchableSelect = <T,>({
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
className="flex w-52 justify-between overflow-hidden bg-input border border-border"
|
||||
className={twMerge(
|
||||
"flex w-52 justify-between overflow-hidden bg-input border border-border",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{value ? (
|
||||
<span className="text-ellipsis overflow-hidden">
|
||||
{options.find((option) => option.value === value)?.text}
|
||||
{options.find((option) => option.value === value)?.text ??
|
||||
(allowCustomValue ? filterValue : null)}
|
||||
</span>
|
||||
) : (
|
||||
<span className="opacity-80">Select…</span>
|
||||
@@ -47,12 +92,12 @@ export const SearchableSelect = <T,>({
|
||||
<ChevronsUpDown className="opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Filter…" />
|
||||
<PopoverContent className={twMerge("w-[200px] p-0", contentClassName)}>
|
||||
<Command shouldFilter={false}>
|
||||
<CommandInput placeholder="Filter…" onValueChange={setFilterValue} />
|
||||
<CommandList>
|
||||
<CommandGroup>
|
||||
{options.map((option, i) => (
|
||||
{filteredOptions.map((option, i) => (
|
||||
<CommandItem
|
||||
key={i}
|
||||
value={option.text}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||
return (
|
||||
<textarea
|
||||
data-slot="textarea"
|
||||
className={cn(
|
||||
"border-neutral-200 placeholder:text-neutral-500 focus-visible:border-neutral-950 focus-visible:ring-neutral-950/50 aria-invalid:ring-red-500/20 dark:aria-invalid:ring-red-500/40 aria-invalid:border-red-500 dark:bg-neutral-200/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:border-neutral-800 dark:placeholder:text-neutral-400 dark:focus-visible:border-neutral-300 dark:focus-visible:ring-neutral-300/50 dark:aria-invalid:ring-red-900/20 dark:dark:aria-invalid:ring-red-900/40 dark:aria-invalid:border-red-900 dark:dark:bg-neutral-800/30",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Textarea }
|
||||
Reference in New Issue
Block a user