globall key press

This commit is contained in:
Nate Kelley 2025-10-06 15:36:49 -06:00
parent fd2e61808c
commit 7787a72061
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
3 changed files with 62 additions and 30 deletions

View File

@ -33,7 +33,6 @@ export const MentionInputSuggestionsMentionsInput = forwardRef<
autoFocus={false} autoFocus={false}
disabled={props.disabled} disabled={props.disabled}
className="sr-only hidden h-0 border-0 p-0 pointer-events-none w-full" className="sr-only hidden h-0 border-0 p-0 pointer-events-none w-full"
// className="absolute -top-1 left-0 w-full h-full border border-red-500"
aria-hidden="true" aria-hidden="true"
/> />
</React.Fragment> </React.Fragment>

View File

@ -1,10 +1,11 @@
import { Command } from 'cmdk'; import { Command } from 'cmdk';
import React, { useState } from 'react'; import React, { useState, useRef } from 'react';
import { createContext, useContextSelector } from 'use-context-selector';
import { SearchEmptyState } from './SearchEmptyState'; import { SearchEmptyState } from './SearchEmptyState';
import { SearchFooter } from './SearchFooter'; import { SearchFooter } from './SearchFooter';
import { SearchInput } from './SearchInput'; import { SearchInput } from './SearchInput';
import { SearchModalContentItems } from './SearchModalContentItems'; import { SearchModalContentItems } from './SearchModalContentItems';
import type { SearchModalContentProps } from './search-modal.types'; import type { SearchItem, SearchModalContentProps } from './search-modal.types';
import { useViewSearchItem } from './useViewSearchItem'; import { useViewSearchItem } from './useViewSearchItem';
export const SearchModalContent = <M, T extends string>({ export const SearchModalContent = <M, T extends string>({
@ -26,18 +27,40 @@ export const SearchModalContent = <M, T extends string>({
onViewSearchItem, onViewSearchItem,
}); });
const [searchValue, setSearchValue] = useState<string>(defaulSearchValue); const [searchValue, setSearchValue] = useState<string>(defaulSearchValue);
const isCommandKeyPressedRef = useRef(false);
const onSearchChangePreflight = (searchValue: string) => { const onSearchChangePreflight = (searchValue: string) => {
setSearchValue(searchValue); setSearchValue(searchValue);
onSearchChange(searchValue); onSearchChange(searchValue);
}; };
const handleKeyDownGlobal = (e: React.KeyboardEvent) => {
if (e.metaKey || e.ctrlKey) {
isCommandKeyPressedRef.current = true;
}
handleKeyDown(e);
};
const handleKeyUpGlobal = (e: React.KeyboardEvent) => {
if (!e.metaKey && !e.ctrlKey) {
isCommandKeyPressedRef.current = false;
}
};
const onSelectGlobal = (item: SearchItem<M, T>) => {
if (item?.onSelect) {
item.onSelect();
}
onSelect(item, isCommandKeyPressedRef.current ? 'navigate' : 'select');
};
return ( return (
<Command <Command
className="min-w-[650px] min-h-[450px] max-h-[75vh] bg-background flex flex-col" className="min-w-[650px] min-h-[450px] max-h-[75vh] bg-background flex flex-col"
value={focusedValue} value={focusedValue}
onValueChange={setFocusedValue} onValueChange={setFocusedValue}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDownGlobal}
onKeyUp={handleKeyUpGlobal}
> >
<SearchInput <SearchInput
searchValue={searchValue} searchValue={searchValue}
@ -48,7 +71,7 @@ export const SearchModalContent = <M, T extends string>({
<div className="border-b" /> <div className="border-b" />
<SearchModalContentItems <SearchModalContentItems
searchItems={searchItems} searchItems={searchItems}
onSelect={onSelect} onSelectGlobal={onSelectGlobal}
onViewSearchItem={onViewSearchItem} onViewSearchItem={onViewSearchItem}
/> />
<SearchEmptyState emptyState={emptyState} /> <SearchEmptyState emptyState={emptyState} />

View File

@ -1,4 +1,4 @@
import { Command } from 'cmdk'; import { Command, useCommandState } from 'cmdk';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import type { import type {
SearchItem, SearchItem,
@ -8,15 +8,24 @@ import type {
SearchModalContentProps, SearchModalContentProps,
} from './search-modal.types'; } from './search-modal.types';
type CommonProps<M, T extends string> = {
onSelectGlobal: (d: SearchItem<M, T>) => void;
};
export const SearchModalContentItems = <M, T extends string>({ export const SearchModalContentItems = <M, T extends string>({
searchItems, searchItems,
onSelect, onSelectGlobal,
onViewSearchItem, }: Pick<SearchModalContentProps<M, T>, 'searchItems' | 'onViewSearchItem'> & CommonProps<M, T>) => {
}: Pick<SearchModalContentProps<M, T>, 'searchItems' | 'onSelect' | 'onViewSearchItem'>) => { const hasResults = useCommandState((x) => x.filtered.count) > 0;
return ( return (
<Command.List className="flex flex-col overflow-y-auto flex-1"> <Command.List className={cn('flex flex-col overflow-y-auto flex-1', !hasResults && 'hidden')}>
{searchItems.map((item, index) => ( {searchItems.map((item, index) => (
<ItemsSelecter key={keyExtractor(item, index)} item={item} /> <ItemsSelecter
key={keyExtractor(item, index)}
item={item}
onSelectGlobal={onSelectGlobal}
/>
))} ))}
</Command.List> </Command.List>
); );
@ -29,14 +38,20 @@ const keyExtractor = <M, T extends string>(item: SearchItems<M, T>, index: numbe
return item.type + index; return item.type + index;
}; };
const ItemsSelecter = <M, T extends string>({ item }: { item: SearchItems<M, T> }) => { const ItemsSelecter = <M, T extends string>({
item,
onSelectGlobal,
}: {
item: SearchItems<M, T>;
onSelectGlobal: (d: SearchItem<M, T>) => void;
}) => {
const type = item.type; const type = item.type;
if (type === 'item') { if (type === 'item') {
return <SearchItemComponent {...item} />; return <SearchItemComponent {...item} onSelectGlobal={onSelectGlobal} />;
} }
if (type === 'group') { if (type === 'group') {
return <SearchItemGroupComponent item={item} />; return <SearchItemGroupComponent item={item} onSelectGlobal={onSelectGlobal} />;
} }
if (type === 'seperator') { if (type === 'seperator') {
@ -48,16 +63,8 @@ const ItemsSelecter = <M, T extends string>({ item }: { item: SearchItems<M, T>
return null; return null;
}; };
const SearchItemComponent = <M, T extends string>({ const SearchItemComponent = <M, T extends string>(item: SearchItem<M, T> & CommonProps<M, T>) => {
value, const { value, label, secondaryLabel, tertiaryLabel, icon, disabled, onSelectGlobal } = item;
label,
secondaryLabel,
tertiaryLabel,
icon,
onSelect,
loading,
disabled,
}: SearchItem<M, T>) => {
return ( return (
<Command.Item <Command.Item
className={cn( className={cn(
@ -68,10 +75,7 @@ const SearchItemComponent = <M, T extends string>({
)} )}
value={value} value={value}
disabled={disabled} disabled={disabled}
onSelect={() => { onSelect={() => onSelectGlobal(item)}
console.log('onSelect', value);
onSelect?.();
}}
> >
{label} {label}
</Command.Item> </Command.Item>
@ -80,18 +84,24 @@ const SearchItemComponent = <M, T extends string>({
const SearchItemGroupComponent = <M, T extends string>({ const SearchItemGroupComponent = <M, T extends string>({
item, item,
onSelectGlobal,
}: { }: {
item: SearchItemGroup<M, T>; item: SearchItemGroup<M, T>;
onSelectGlobal: (d: SearchItem<M, T>) => void;
}) => { }) => {
return ( return (
<Command.Group> <Command.Group>
{item.items.map((item, index) => ( {item.items.map((item, index) => (
<ItemsSelecter key={keyExtractor(item, index)} item={item} /> <ItemsSelecter
key={keyExtractor(item, index)}
item={item}
onSelectGlobal={onSelectGlobal}
/>
))} ))}
</Command.Group> </Command.Group>
); );
}; };
const SearchItemSeperatorComponent = ({ item }: { item: SearchItemSeperator }) => { const SearchItemSeperatorComponent = ({ item: _item }: { item: SearchItemSeperator }) => {
return <Command.Separator className="border-t w-full" />; return <Command.Separator className="border-t w-full" />;
}; };