mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
## What kind of change does this PR introduce? fixture on the popover + command components ## What is the current behavior? currently there is a misalignment in the command component <> searchbar icon and items + the popover menu animation in is slightly scattered ## What is the new behavior? - fixes icon alignment in command component - updates popover animation in `command` | state | preview | | -------|------| | before | <img width="881" height="542" alt="image" src="https://github.com/user-attachments/assets/e38aa22f-5eea-4b08-8a24-254e26bf90fe" /> | | after | <img width="881" height="542" alt="image" src="https://github.com/user-attachments/assets/85608e11-8415-4d47-945f-cc13772e4ad1" /> | `popover` | state | preview | | -------|------| | before | <video src="https://github.com/user-attachments/assets/cbb2da1a-6f73-4a8d-87d6-21e8b636c110" /> | | after | <video src="https://github.com/user-attachments/assets/1b0832f5-e6ac-4e93-ae78-9e0aee31205c" /> | <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Style** - Adjusted command input spacing for improved visual alignment. - Updated popover transitions with state-based fade and zoom animations, and refined their visual origin. - Set a consistent size for the AI docs command icon. - **New Features** - Added an option to customize the command menu input wrapper’s styling. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
142 lines
4.2 KiB
TypeScript
142 lines
4.2 KiB
TypeScript
'use client'
|
|
|
|
import { useBreakpoint, useDebounce } from 'common'
|
|
import { forwardRef, useCallback, useEffect, useRef, useState } from 'react'
|
|
import type React from 'react'
|
|
import { cn, CommandInput } from 'ui'
|
|
|
|
import { useQuery, useSetQuery } from './hooks/queryHooks'
|
|
import { useCommandMenuTelemetryContext } from './hooks/useCommandMenuTelemetryContext'
|
|
|
|
const INPUT_TYPED_EVENT_DEBOUNCE_TIME = 2000 // 2s
|
|
|
|
function useFocusInputOnWiderScreens(ref: React.ForwardedRef<HTMLInputElement>) {
|
|
const isBelowSm = useBreakpoint('sm')
|
|
const isBelowSmSynchronous = useRef(isBelowSm)
|
|
isBelowSmSynchronous.current = isBelowSm
|
|
|
|
const internalRef = useRef<HTMLInputElement | null>(null)
|
|
const combinedRef = (element: HTMLInputElement) => {
|
|
if (ref instanceof Function) {
|
|
ref(element)
|
|
} else if (!!ref) {
|
|
ref.current = element
|
|
}
|
|
internalRef.current = element
|
|
}
|
|
|
|
useEffect(() => {
|
|
// This will always be false in the first iteration (since isBelowSm
|
|
// switches from false -> true on narrow screens). To avoid a preemptive
|
|
// focus, we need to delay this. But then we need to access the current
|
|
// value of isBelowSm, not the stale value, which explains the business
|
|
// with syncing state into a ref above.
|
|
setTimeout(() => {
|
|
if (!isBelowSmSynchronous.current) {
|
|
internalRef.current?.focus()
|
|
}
|
|
})
|
|
}, [])
|
|
|
|
return combinedRef
|
|
}
|
|
|
|
const CommandMenuInput = forwardRef<
|
|
React.ElementRef<typeof CommandInput>,
|
|
React.ComponentPropsWithoutRef<typeof CommandInput>
|
|
>(({ className, wrapperClassName, ...props }, ref) => {
|
|
const inputRef = useFocusInputOnWiderScreens(ref)
|
|
|
|
const query = useQuery()
|
|
const setQuery = useSetQuery()
|
|
|
|
const [inputValue, setInputValue] = useState(query)
|
|
useEffect(() => {
|
|
setInputValue(query)
|
|
previousValueRef.current = query
|
|
}, [query])
|
|
|
|
// Get telemetry context
|
|
const telemetryContext = useCommandMenuTelemetryContext()
|
|
const previousValueRef = useRef<string>(inputValue)
|
|
|
|
const inputTelemetryEvent = useCallback(
|
|
(value: string) => {
|
|
if (telemetryContext?.onTelemetry) {
|
|
const event = {
|
|
action: 'command_menu_search_submitted' as const,
|
|
properties: {
|
|
value: value,
|
|
app: telemetryContext.app,
|
|
},
|
|
groups: {},
|
|
}
|
|
telemetryContext.onTelemetry(event)
|
|
}
|
|
},
|
|
[telemetryContext]
|
|
)
|
|
|
|
const debouncedTelemetry = useDebounce(
|
|
useCallback(() => {
|
|
inputTelemetryEvent(inputValue)
|
|
previousValueRef.current = inputValue
|
|
}, [inputTelemetryEvent, inputValue]),
|
|
INPUT_TYPED_EVENT_DEBOUNCE_TIME
|
|
)
|
|
|
|
const handleValueChange = useCallback(
|
|
(value: string) => {
|
|
setInputValue(value)
|
|
|
|
// Only trigger telemetry if the user is adding characters (not removing with backspace)
|
|
const isAddingCharacters = value.length > previousValueRef.current.length
|
|
|
|
if (!isAddingCharacters) {
|
|
previousValueRef.current = value
|
|
return
|
|
}
|
|
|
|
// Trigger debounced telemetry
|
|
debouncedTelemetry()
|
|
},
|
|
[debouncedTelemetry]
|
|
)
|
|
|
|
// To handle CJK input
|
|
const [imeComposing, setImeComposing] = useState(false)
|
|
useEffect(() => {
|
|
if (!imeComposing) {
|
|
setQuery(inputValue)
|
|
}
|
|
}, [inputValue, imeComposing])
|
|
|
|
return (
|
|
<div className="relative w-full" cmdk-input-wrapper="">
|
|
<CommandInput
|
|
// Focus needs to be manually handled to check breakpoint first, due to
|
|
// delays from useEffect
|
|
autoFocus={false}
|
|
ref={inputRef}
|
|
value={inputValue}
|
|
onValueChange={handleValueChange}
|
|
placeholder="Run a command or search..."
|
|
onCompositionStart={() => setImeComposing(true)}
|
|
onCompositionEnd={() => setImeComposing(false)}
|
|
wrapperClassName={cn('px-4', wrapperClassName)}
|
|
className={cn(
|
|
'flex h-11 w-full rounded-md bg-transparent px-2 y-4 md:py-7 outline-hidden',
|
|
'focus:shadow-none focus:ring-transparent',
|
|
'text-base text-foreground-light placeholder:text-foreground-muted disabled:cursor-not-allowed disabled:opacity-50 border-0',
|
|
className
|
|
)}
|
|
{...props}
|
|
/>
|
|
</div>
|
|
)
|
|
})
|
|
|
|
CommandMenuInput.displayName = 'CommandMenuInput'
|
|
|
|
export { CommandMenuInput }
|