
⌘k é um componente do menu de comando que reage que também pode ser usado como um ComboBox acessível. Você renderiza itens, ele filtra e os classifica automaticamente. ⌘k suporta uma API totalmente composta como? , para que você possa envolver itens em outros componentes ou mesmo como JSX estático.
Demo e exemplos: cmdk.paco.me
pnpm install cmdk import { Command } from 'cmdk'
const CommandMenu = ( ) => {
return (
< Command label = "Command Menu" >
< Command . Input / >
< Command . List >
< Command . Empty > No results found. < / Command . Empty >
< Command . Group heading = "Letters" >
< Command . Item > a < / Command . Item >
< Command . Item > b < / Command . Item >
< Command . Separator / >
< Command . Item > c < / Command . Item >
< / Command . Group >
< Command . Item > Apple < / Command . Item >
< / Command . List >
< / Command >
)
}Ou em uma caixa de diálogo:
import { Command } from 'cmdk'
const CommandMenu = ( ) => {
const [ open , setOpen ] = React . useState ( false )
// Toggle the menu when ⌘K is pressed
React . useEffect ( ( ) => {
const down = ( e ) => {
if ( e . key === 'k' && ( e . metaKey || e . ctrlKey ) ) {
e . preventDefault ( )
setOpen ( ( open ) => ! open )
}
}
document . addEventListener ( 'keydown' , down )
return ( ) => document . removeEventListener ( 'keydown' , down )
} , [ ] )
return (
< Command . Dialog open = { open } onOpenChange = { setOpen } label = "Global Command Menu" >
< Command . Input / >
< Command . List >
< Command . Empty > No results found. < / Command . Empty >
< Command . Group heading = "Letters" >
< Command . Item > a < / Command . Item >
< Command . Item > b < / Command . Item >
< Command . Separator / >
< Command . Item > c < / Command . Item >
< / Command . Group >
< Command . Item > Apple < / Command . Item >
< / Command . List >
< / Command . Dialog >
)
} Todas as peças encaminham adereços, incluindo ref , para um elemento apropriado. Cada parte possui um atributo de dados específico (começando com cmdk- ) que pode ser usado para estilo.
[cmdk-root] Renderize isso para mostrar o menu de comando embutido ou usar diálogo para renderizar em um contexto elevado. Pode ser controlado com os adereços value e onValueChange .
Observação
Os valores são sempre aparados com o método TRIM ().
const [ value , setValue ] = React . useState ( 'apple' )
return (
< Command value = { value } onValueChange = { setValue } >
< Command . Input / >
< Command . List >
< Command . Item > Orange < / Command . Item >
< Command . Item > Apple < / Command . Item >
< / Command . List >
< / Command >
) Você pode fornecer uma função filter personalizada chamada para classificar cada item. Observe que o valor será aparado.
< Command
filter = { ( value , search ) => {
if ( value . includes ( search ) ) return 1
return 0
} }
/ > Um terceiro argumento, keywords , também pode ser fornecido à função de filtro. As palavras -chave atuam como aliases para o valor do item e também podem afetar a classificação do item. Palavras -chave são aparadas.
< Command
filter = { ( value , search , keywords ) => {
const extendValue = value + ' ' + keywords . join ( ' ' )
if ( extendValue . includes ( search ) ) return 1
return 0
} }
/ >Ou desativar a filtragem e a classificação inteiramente:
< Command shouldFilter = { false } >
< Command . List >
{ filteredItems . map ( ( item ) => {
return (
< Command . Item key = { item } value = { item } >
{ item }
< / Command . Item >
)
} ) }
< / Command . List >
< / Command > Você pode fazer as teclas de seta envolver a lista (quando chegar ao fim, ela remonta ao primeiro item) definindo o suporte loop :
< Command loop / >[cmdk-dialog] [cmdk-overlay] Os adereços são encaminhados para o comando. Componente de diálogo da Radix UI. A sobreposição é sempre renderizada. Consulte a documentação do Radix para obter mais informações. Pode ser controlado com os adereços open e onOpenChange .
const [ open , setOpen ] = React . useState ( false )
return (
< Command . Dialog open = { open } onOpenChange = { setOpen } >
...
< / Command . Dialog >
) Você pode fornecer um suporte container que aceite um elemento HTML que é encaminhado para o componente do portal de diálogo da Radix UI para especificar em qual elemento a caixa de diálogo deve portal para (padrão para o body ). Consulte a documentação do Radix para obter mais informações.
const containerElement = React . useRef ( null )
return (
< >
< Command . Dialog container = { containerElement . current } / >
< div ref = { containerElement } / >
< / >
)[cmdk-input] Todos os adereços são encaminhados para o elemento input subjacente. Pode ser controlado com os adereços value e onValueChange .
const [ search , setSearch ] = React . useState ( '' )
return < Command . Input value = { search } onValueChange = { setSearch } / >[cmdk-list] Contém itens e grupos. Anima a altura usando a variável CSS --cmdk-list-height .
[ cmdk-list ] {
min-height : 300 px ;
height : var ( --cmdk-list-height );
max-height : 500 px ;
transition : height 100 ms ease;
}Para rolar o item para exibir mais cedo nas bordas da viewport, use o scroll-acadding:
[ cmdk-list ] {
scroll-padding-block-start : 8 px ;
scroll-padding-block-end : 8 px ;
}[cmdk-item] [data-disabled?] [data-selected?] Item que se torna ativo no ponteiro Enter. Você deve fornecer um value exclusivo para cada item, mas ele será inferido automaticamente do .textContent .
< Command . Item
onSelect = { ( value ) => console . log ( 'Selected' , value ) }
// Value is implicity "apple" because of the provided text content
>
Apple
< / Command . Item > Você também pode fornecer um suporte keywords para ajudar na filtragem. Palavras -chave são aparadas.
< Command . Item keywords = { [ 'fruit' , 'apple' ] } > Apple < / Command . Item > < Command . Item
onSelect = { ( value ) => console . log ( 'Selected' , value ) }
// Value is implicity "apple" because of the provided text content
>
Apple
< / Command . Item > Você pode forçar um item a ser sempre renderizado, independentemente da filtragem, passando o forceMount Prop.
[cmdk-group] [hidden?] Grupos de itens juntamente com o heading fornecido ( [cmdk-group-heading] ).
< Command . Group heading = "Fruit" >
< Command . Item > Apple < / Command . Item >
< / Command . Group > Os grupos não desmontam do DOM, mas o atributo hidden é aplicado para escondê -lo da vista. Isso pode ser relevante em seu estilo.
Você pode forçar um grupo a sempre renderizar, independentemente da filtragem, passando o forceMount Prop.
[cmdk-separator] Visível quando a consulta de pesquisa está vazia ou alwaysRender é verdadeira, oculta de outra forma.
[cmdk-empty]Renderiza automaticamente quando não há resultados para a consulta de pesquisa.
[cmdk-loading] Você deve renderizá -lo condicionalmente com progress ao carregar itens assíncronos.
const [ loading , setLoading ] = React . useState ( false )
return < Command . List > { loading && < Command . Loading > Hang on… < / Command . Loading > } < / Command . List >useCommandState(state => state.selectedField) Gancho que compõe useSyncExternalStore . Passe uma função que retorna uma fatia do estado do menu de comando para renderizar quando essa fatia mudar. Este gancho é fornecido para casos de uso avançado e não deve ser comumente usado.
Um bom caso de uso seria tornar um estado vazio mais detalhado, assim:
const search = useCommandState ( ( state ) => state . search )
return < Command . Empty > No results found for " { search } ". < / Command . Empty > Trechos de código para casos de uso comuns.
Muitas vezes, a seleção de um item deve navegar mais profundamente, com um conjunto mais refinado de itens. Por exemplo, selecionar "Alterar tema ..." deve mostrar novos itens "Dark Theme" e "Light Theme". Chamamos esses conjuntos de itens de "páginas" e eles podem ser implementados com um estado simples:
const ref = React . useRef ( null )
const [ open , setOpen ] = React . useState ( false )
const [ search , setSearch ] = React . useState ( '' )
const [ pages , setPages ] = React . useState ( [ ] )
const page = pages [ pages . length - 1 ]
return (
< Command
onKeyDown = { ( e ) => {
// Escape goes to previous page
// Backspace goes to previous page when search is empty
if ( e . key === 'Escape' || ( e . key === 'Backspace' && ! search ) ) {
e . preventDefault ( )
setPages ( ( pages ) => pages . slice ( 0 , - 1 ) )
}
} }
>
< Command . Input value = { search } onValueChange = { setSearch } / >
< Command . List >
{ ! page && (
< >
< Command . Item onSelect = { ( ) => setPages ( [ ... pages , 'projects' ] ) } > Search projects… < / Command . Item >
< Command . Item onSelect = { ( ) => setPages ( [ ... pages , 'teams' ] ) } > Join a team… < / Command . Item >
< / >
) }
{ page === 'projects' && (
< >
< Command . Item > Project A < / Command . Item >
< Command . Item > Project B < / Command . Item >
< / >
) }
{ page === 'teams' && (
< >
< Command . Item > Team 1 < / Command . Item >
< Command . Item > Team 2 < / Command . Item >
< / >
) }
< / Command . List >
< / Command >
)Se seus itens terem aninhado sub-itens que você deseja revelar apenas ao pesquisar, renderize com base no estado de pesquisa:
const SubItem = ( props ) => {
const search = useCommandState ( ( state ) => state . search )
if ( ! search ) return null
return < Command . Item { ... props } / >
}
return (
< Command >
< Command . Input / >
< Command . List >
< Command . Item > Change theme… < / Command . Item >
< SubItem > Change theme to dark < / SubItem >
< SubItem > Change theme to light < / SubItem >
< / Command . List >
< / Command >
)Renderizar os itens à medida que estiverem disponíveis. Filtragem e classificação acontecerão automaticamente.
const [ loading , setLoading ] = React . useState ( false )
const [ items , setItems ] = React . useState ( [ ] )
React . useEffect ( ( ) => {
async function getItems ( ) {
setLoading ( true )
const res = await api . get ( '/dictionary' )
setItems ( res )
setLoading ( false )
}
getItems ( )
} , [ ] )
return (
< Command >
< Command . Input / >
< Command . List >
{ loading && < Command . Loading > Fetching words… < / Command . Loading > }
{ items . map ( ( item ) => {
return (
< Command . Item key = { `word- ${ item } ` } value = { item } >
{ item }
< / Command . Item >
)
} ) }
< / Command . List >
< / Command >
)Recomendamos o uso do componente Radix UI Popover. O ⌘K conta com o componente de diálogo Radix UI, portanto, isso reduzirá um pouco o tamanho do seu pacote devido a dependências compartilhadas.
$ pnpm install @radix-ui/react-popover Renderizar Command dentro do conteúdo do popover:
import * as Popover from '@radix-ui/react-popover'
return (
< Popover . Root >
< Popover . Trigger > Toggle popover < / Popover . Trigger >
< Popover . Content >
< Command >
< Command . Input / >
< Command . List >
< Command . Item > Apple < / Command . Item >
< / Command . List >
< / Command >
< / Popover . Content >
< / Popover . Root >
)Você pode encontrar folhas de estilo globais para aparecer como ponto de partida para o estilo. Consulte o site/styles/cmdk para exemplos.
Acessível? Sim. Rotulagem, atributos ARIA e ordem de DOM testados com voz e Chrome Devtools. A caixa de diálogo compõe uma implementação de diálogo acessível.
Virtualização? Não. Bom desempenho de até 2.000-3.000 itens, no entanto. Leia abaixo para trazer o seu próprio.
Filtrar/classificar itens manualmente? Sim. Pass shouldFilter={false} para comando. Melhor uso de memória e desempenho. Traga sua própria virtualização dessa maneira.
Reaja 18 seguro? Sim, necessário. Usa o React 18 ganchos como useId e useSyncExternalStore .
Sem seleção? Sim, use os seletores CSS listados.
Hidration Indatatch? Não, provavelmente um bug no seu código. Verifique se o suporte open para Command.Dialog é false no servidor.
Reaja o modo rigoroso seguro? Sim. Abra um problema se você notar um problema.
Comportamento estranho/errado? Verifique se o seu Command.Item possui um value key e exclusivo.
Modo simultâneo seguro? Talvez, mas o modo simultâneo ainda não seja real. Usa abordagens de risco, como a ordem manual da DOM.
Componente do servidor react? Não, é um componente cliente.
Escute para ⌘k automaticamente? Não, faça você mesmo ter controle total sobre o contexto de Keybind.
Reaja nativo? Não, e sem planos de apoiá -lo. Se você criar uma versão nativa do React, informe -nos e vincularemos seu repositório aqui.
Escrito em 2019 por Paco (@Pacocuryy) para ver se uma API de Compostable ComboBox era possível. Usado para o menu de comando vercel e preenchimento automático por Rauno (@raunofreiberg) em 2020. Reescrito de forma independente em 2022 com uma abordagem mais simples e com desempenho. Idéias e ajuda de shu (@shuding_).
Os descendentes de uso foram extraídos da versão de 2019.
Primeiro, instale dependências e navegadores dramaturgos:
pnpm install
pnpm playwright installEm seguida, verifique se você construiu a biblioteca:
pnpm buildEm seguida, execute os testes usando sua construção local contra motores de navegador reais:
pnpm test