docs: add AI Assistant (#5249)

* added components

* added ai assistant button

* change styling

* improve AI assistant

* change to a drawer

* added command support into search

* add AiAssistant to all projects

* remove usage of Text component

* added error handling

* use recaptcha

* fix new configurations

* fix background color

* change suggested questions
This commit is contained in:
Shahed Nasser
2023-10-05 11:10:44 +03:00
committed by GitHub
parent b6bea74914
commit b3f75d8f21
45 changed files with 1864 additions and 437 deletions
+2
View File
@@ -1,4 +1,6 @@
export * from "./use-copy"
export * from "./use-keyboard-shortcut"
export * from "./use-scroll-utils"
export * from "./use-search-navigation"
export * from "./use-select"
export * from "./use-tabs"
@@ -0,0 +1,26 @@
"use client"
import { useCallback, useEffect, useRef, useState } from "react"
import copy from "copy-text-to-clipboard"
type useCopyReturnType = {
isCopied: boolean
handleCopy: () => void
}
export const useCopy = (text: string): useCopyReturnType => {
const [isCopied, setIsCopied] = useState(false)
const copyTimeout = useRef<number>(0)
const handleCopy = useCallback(() => {
copy(text)
setIsCopied(true)
copyTimeout.current = window.setTimeout(() => {
setIsCopied(false)
}, 1000)
}, [text])
useEffect(() => () => window.clearTimeout(copyTimeout.current), [])
return { isCopied, handleCopy }
}
@@ -0,0 +1,144 @@
"use client"
import { useCallback, useEffect, useMemo } from "react"
import { useSearch } from "@/providers"
import { findNextSibling, findPrevSibling } from "@/utils"
import {
useKeyboardShortcut,
type useKeyboardShortcutOptions,
} from "../use-keyboard-shortcut"
export type useSearchNavigationProps = {
getInputElm: () => HTMLInputElement | null
focusInput: () => void
handleSubmit?: () => void
keyboardProps?: Partial<useKeyboardShortcutOptions>
}
export const useSearchNavigation = ({
getInputElm,
focusInput,
handleSubmit,
keyboardProps,
}: useSearchNavigationProps) => {
const shortcutKeys = useMemo(() => ["ArrowUp", "ArrowDown", "Enter"], [])
const { modalRef, isOpen } = useSearch()
const handleKeyAction = (e: KeyboardEvent) => {
if (!isOpen) {
return
}
e.preventDefault()
const focusedItem = modalRef.current?.querySelector(":focus") as HTMLElement
if (!focusedItem) {
// focus the first data-hit
const nextItem = modalRef.current?.querySelector(
"[data-hit]"
) as HTMLElement
nextItem?.focus()
return
}
const isHit = focusedItem.hasAttribute("data-hit")
const isInput = focusedItem.tagName.toLowerCase() === "input"
if (!isHit && !isInput) {
// ignore if focused items aren't input/data-hit
return
}
const lowerPressedKey = e.key.toLowerCase()
if (lowerPressedKey === "enter") {
if (isHit) {
// trigger click event of the focused element
focusedItem.click()
} else {
handleSubmit?.()
}
return
}
if (lowerPressedKey === "arrowup") {
// only hit items has action on arrow up
if (isHit) {
// find if there's a data-hit item before this one
const beforeItem = findPrevSibling(focusedItem, "[data-hit]")
if (!beforeItem) {
// focus the input
focusInput()
} else {
// focus the previous item
beforeItem.focus()
}
}
} else if (lowerPressedKey === "arrowdown") {
// check if item is input or hit
if (isInput) {
// go to the first data-hit item
const nextItem = modalRef.current?.querySelector(
"[data-hit]"
) as HTMLElement
nextItem?.focus()
} else {
// handle go down for hit items
// find if there's a data-hit item after this one
const afterItem = findNextSibling(focusedItem, "[data-hit]")
if (afterItem) {
// focus the next item
afterItem.focus()
}
}
}
}
/** Handles starting to type which focuses the input */
const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (!isOpen) {
return
}
// check if shortcut keys were pressed
const lowerPressedKey = e.key.toLowerCase()
const pressedShortcut = [...shortcutKeys, "Escape"].some(
(s) => s.toLowerCase() === lowerPressedKey
)
if (pressedShortcut) {
return
}
const focusedItem = modalRef.current?.querySelector(
":focus"
) as HTMLElement
const inputElm = getInputElm()
if (inputElm && focusedItem !== inputElm) {
focusInput()
}
},
[
shortcutKeys,
isOpen,
modalRef.current,
shortcutKeys,
getInputElm,
focusInput,
]
)
useEffect(() => {
window.addEventListener("keydown", handleKeyDown)
return () => {
window.removeEventListener("keydown", handleKeyDown)
}
}, [handleKeyDown])
useKeyboardShortcut({
metakey: false,
shortcutKeys: shortcutKeys,
checkEditing: false,
isLoading: false,
action: handleKeyAction,
...keyboardProps,
})
}