
⌘K est un composant de réaction de menu de commande qui peut également être utilisé comme un combobox accessible. Vous rendez les articles, il les filtre et les trie automatiquement. ⌘K prend en charge une API entièrement composable comment? , afin que vous puissiez envelopper des éléments dans d'autres composants ou même en tant que JSX statique.
Démo et exemples: 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 dans une boîte de dialogue:
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 >
)
} Toutes les pièces transmettent des accessoires, y compris ref , à un élément approprié. Chaque pièce a un attribution de données spécifique (à commencer par cmdk- ) qui peut être utilisé pour le style.
[cmdk-root] Rendez-vous pour afficher le menu de commande en ligne ou utiliser la boîte de dialogue pour rendre dans un contexte élevé. Peut être contrôlé avec la value et les accessoires onValueChange .
Note
Les valeurs sont toujours garnies de la méthode 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 >
) Vous pouvez fournir une fonction filter personnalisée appelée pour classer chaque élément. Notez que la valeur sera coupée.
< Command
filter = { ( value , search ) => {
if ( value . includes ( search ) ) return 1
return 0
} }
/ > Un troisième argument, keywords , peut également être fourni à la fonction filtrante. Les mots clés agissent comme des alias pour la valeur de l'élément et peuvent également affecter le rang de l'élément. Les mots clés sont coupés.
< Command
filter = { ( value , search , keywords ) => {
const extendValue = value + ' ' + keywords . join ( ' ' )
if ( extendValue . includes ( search ) ) return 1
return 0
} }
/ >Ou désactiver le filtrage et le tri entièrement:
< Command shouldFilter = { false } >
< Command . List >
{ filteredItems . map ( ( item ) => {
return (
< Command . Item key = { item } value = { item } >
{ item }
< / Command . Item >
)
} ) }
< / Command . List >
< / Command > Vous pouvez faire en sorte que les touches de flèche enroulent la liste (lorsque vous atteignez la fin, il revient au premier élément) en définissant l'hélice loop :
< Command loop / >[cmdk-dialog] [cmdk-overlay] Les accessoires sont transmis à la commande. Compose le composant de dialogue de Radix UI. La superposition est toujours rendue. Voir la documentation RADIX pour plus d'informations. Peut être contrôlé avec les accessoires open et onOpenChange .
const [ open , setOpen ] = React . useState ( false )
return (
< Command . Dialog open = { open } onOpenChange = { setOpen } >
...
< / Command . Dialog >
) Vous pouvez fournir un accessoire container qui accepte un élément HTML qui est transmis au composant de la boîte de dialogue de Radix UI pour spécifier quel élément la boîte de dialogue doit le portail (par défaut vers body ). Voir la documentation RADIX pour plus d'informations.
const containerElement = React . useRef ( null )
return (
< >
< Command . Dialog container = { containerElement . current } / >
< div ref = { containerElement } / >
< / >
)[cmdk-input] Tous les accessoires sont transmis à l'élément input sous-jacent. Peut être contrôlé avec la value et les accessoires onValueChange .
const [ search , setSearch ] = React . useState ( '' )
return < Command . Input value = { search } onValueChange = { setSearch } / >[cmdk-list] Contient des articles et des groupes. Animer la hauteur à l'aide de la variable CSS --cmdk-list-height .
[ cmdk-list ] {
min-height : 300 px ;
height : var ( --cmdk-list-height );
max-height : 500 px ;
transition : height 100 ms ease;
}Pour faire défiler l'article en vue plus tôt près des bords de la fenêtre, utilisez un défilement de défilement:
[ cmdk-list ] {
scroll-padding-block-start : 8 px ;
scroll-padding-block-end : 8 px ;
}[cmdk-item] [data-disabled?] [data-selected?] Élément qui devient actif sur le pointeur entre. Vous devez fournir une value unique pour chaque élément, mais il sera automatiquement déduit du .textContent .
< Command . Item
onSelect = { ( value ) => console . log ( 'Selected' , value ) }
// Value is implicity "apple" because of the provided text content
>
Apple
< / Command . Item > Vous pouvez également fournir un accessoire keywords pour aider à le filtrage. Les mots clés sont coupés.
< 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 > Vous pouvez forcer un élément à toujours rendu, quel que soit le filtrage, en passant l'hélice forceMount .
[cmdk-group] [hidden?] Groupe les éléments avec l' heading donné ( [cmdk-group-heading] ).
< Command . Group heading = "Fruit" >
< Command . Item > Apple < / Command . Item >
< / Command . Group > Les groupes ne dénonceront pas le DOM, mais l'attribut hidden est appliqué pour le masquer à la vue. Cela peut être pertinent dans votre style.
Vous pouvez forcer un groupe à toujours rendu, quel que soit le filtrage, en passant l'hélice forceMount .
[cmdk-separator] Visible lorsque la requête de recherche est vide ou alwaysRender est vrai, cachée autrement.
[cmdk-empty]Rend automatiquement lorsqu'il n'y a aucun résultat pour la requête de recherche.
[cmdk-loading] Vous devez le rendre conditionnellement avec progress tout en chargeant des éléments asynchrones.
const [ loading , setLoading ] = React . useState ( false )
return < Command . List > { loading && < Command . Loading > Hang on… < / Command . Loading > } < / Command . List >useCommandState(state => state.selectedField) Hook qui compose useSyncExternalStore . Passez une fonction qui renvoie une tranche de l'état du menu de commande pour renvoyer lorsque cette tranche change. Ce crochet est fourni pour les cas d'utilisation avancés et ne doit pas être couramment utilisé.
Un bon cas d'utilisation serait de rendre un état vide plus détaillé, comme ainsi:
const search = useCommandState ( ( state ) => state . search )
return < Command . Empty > No results found for " { search } ". < / Command . Empty > Extraits de code pour les cas d'utilisation courants.
Souvent, la sélection d'un élément devrait naviguer plus profondément, avec un ensemble plus raffiné d'éléments. Par exemple, la sélection de "Changer le thème…" devrait montrer de nouveaux éléments "Dark Theme" et "Light Theme". Nous appelons ces ensembles d'éléments "pages", et ils peuvent être implémentés avec un état simple:
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 >
)Si vos articles ont des sous-éléments imbriqués que vous souhaitez révéler que lors de la recherche, rendez-vous en fonction de l'état de recherche:
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 >
)Rendez les articles à mesure qu'ils deviennent disponibles. Le filtrage et le tri se produiront automatiquement.
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 >
)Nous vous recommandons d'utiliser le composant Radix UI Popover. ⌘K s'appuie sur le composant de dialogue Radix UI, ce qui réduira un peu la taille de votre bundle en raison de dépendances partagées.
$ pnpm install @radix-ui/react-popover Rendez Command à l'intérieur du contenu de la 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 >
)Vous pouvez trouver des feuilles de styles globales à passer comme point de départ pour le style. Voir Site Web / Styles / CMDK pour des exemples.
Accessible? Oui. L'étiquetage, les attributs ARIA et l'ordre DOM testé avec la voix off et le chrome Devtools. La boîte de dialogue compose une implémentation de dialogue accessible.
Virtualisation? Non. Cependant, de bonnes performances jusqu'à 2 000 à 3 000 articles. Lisez ci-dessous pour apporter le vôtre.
Filtre / trier les articles manuellement? Oui. Pass shouldFilter={false} pour commander. Meilleure utilisation et performances de la mémoire. Apportez votre propre virtualisation de cette façon.
Réagir 18 coffre-fort? Oui, requis. Utilise React 18 Crochets comme useId et useSyncExternalStore .
Non style? Oui, utilisez les sélecteurs CSS répertoriés.
L'inadéquation d'hydratation? Non, probablement un bogue dans votre code. Assurez-vous l'hélice open à Command.Dialog est false sur le serveur.
Réagir en mode strict en toute sécurité? Oui. Ouvrez un problème si vous remarquez un problème.
Un comportement étrange / mauvais? Assurez-vous que votre Command.Item a une value key et unique.
En mode simultanée, le mode concurrent? Peut-être, mais le mode simultané n'est pas encore réel. Utilise des approches risquées comme l'ordre de domaine.
Composant React Server? Non, c'est un composant client.
Écoutez automatiquement ⌘k? Non, faites-le vous-même pour avoir un contrôle total sur le contexte de Keybind.
React natif? Non, et aucun prévoit de le soutenir. Si vous créez une version native React, faites-le nous savoir et nous lierons votre référentiel ici.
Écrit en 2019 par PACO (@PacocEcoursey) pour voir si une API composable Combobox était possible. Utilisé pour le menu de commande Vercel et la saisie semi-automatique par Rauno (@raunofreiberg) en 2020. Re-écrit indépendamment en 2022 avec une approche plus simple et plus performante. Idées et aide de Shu (@shuding_).
Utilisation-descendants a été extrait de la version 2019.
Tout d'abord, installez les dépendances et les navigateurs de dramaturges:
pnpm install
pnpm playwright installAssurez-vous ensuite que vous avez construit la bibliothèque:
pnpm buildEnsuite, exécutez les tests en utilisant votre construction locale contre les moteurs de navigateur réels:
pnpm test