
⌘K ist ein Befehlsmenü -React -Komponente, der auch als zugänglicher Combobox verwendet werden kann. Sie rendern Gegenstände, filtern und sortiert sie automatisch. ⌘K unterstützt eine vollständig komponierbare API wie? So können Sie Elemente in andere Komponenten oder sogar als statisches JSX einwickeln.
Demo und Beispiele: 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 >
)
}Oder in einem Dialog:
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 >
)
} Alle Teile leiten Requisiten, einschließlich ref , zu einem geeigneten Element weiter. Jedes Teil hat eine bestimmte Datenattribute (beginnend mit cmdk- ), die zum Styling verwendet werden kann.
[cmdk-root] Rendern Sie dies, um das Befehlsmenü inline anzuzeigen, oder verwenden Sie den Dialog, um in einem erhöhten Kontext zu rendern. Kann mit dem value und den Requisiten onValueChange gesteuert werden.
Notiz
Die Werte werden immer mit der TRIM () -Methode getrimmt.
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 >
) Sie können eine benutzerdefinierte filter bereitstellen, die auf jeden Element aufgerufen wird. Beachten Sie, dass der Wert abgeschnitten wird.
< Command
filter = { ( value , search ) => {
if ( value . includes ( search ) ) return 1
return 0
} }
/ > Ein drittes Argument, keywords , kann auch der Filterfunktion zur Verfügung gestellt werden. Schlüsselwörter wirken als Aliase für den Artikelwert und können auch den Rang des Elements beeinflussen. Schlüsselwörter sind beschnitten.
< Command
filter = { ( value , search , keywords ) => {
const extendValue = value + ' ' + keywords . join ( ' ' )
if ( extendValue . includes ( search ) ) return 1
return 0
} }
/ >Oder Filterung und Sortierung vollständig deaktivieren:
< Command shouldFilter = { false } >
< Command . List >
{ filteredItems . map ( ( item ) => {
return (
< Command . Item key = { item } value = { item } >
{ item }
< / Command . Item >
)
} ) }
< / Command . List >
< / Command > Sie können die Pfeiltasten um die Liste einwickeln (wenn Sie das Ende erreichen, reicht sie zum ersten Element zurück), indem Sie die loop -Requisite einstellen:
< Command loop / >[cmdk-dialog] [cmdk-overlay] Requisiten werden an den Befehl weitergeleitet. Komponiert die Dialogkomponente der Radix UI. Die Overlay wird immer gerendert. Weitere Informationen finden Sie in der Radix -Dokumentation. Kann mit open und onOpenChange Requisiten gesteuert werden.
const [ open , setOpen ] = React . useState ( false )
return (
< Command . Dialog open = { open } onOpenChange = { setOpen } >
...
< / Command . Dialog >
) Sie können eine container -Requisite bereitstellen, die ein HTML -Element akzeptiert, das an die Dialog -Portal -Komponente von Radix UI weitergeleitet wird, um festzulegen, in welches Element das Dialogfeld portal ist (Standardeinstellungen zum body ). Weitere Informationen finden Sie in der Radix -Dokumentation.
const containerElement = React . useRef ( null )
return (
< >
< Command . Dialog container = { containerElement . current } / >
< div ref = { containerElement } / >
< / >
)[cmdk-input] Alle Requisiten werden an das zugrunde liegende input weitergeleitet. Kann mit dem value und den Requisiten onValueChange gesteuert werden.
const [ search , setSearch ] = React . useState ( '' )
return < Command . Input value = { search } onValueChange = { setSearch } / >[cmdk-list] Enthält Elemente und Gruppen. Animalhöhe mit der CSS --cmdk-list-height .
[ cmdk-list ] {
min-height : 300 px ;
height : var ( --cmdk-list-height );
max-height : 500 px ;
transition : height 100 ms ease;
}Um Artikel früher in der Nähe der Kanten des Ansichtsfensters zu sehen, verwenden Sie Scroll-Padding:
[ cmdk-list ] {
scroll-padding-block-start : 8 px ;
scroll-padding-block-end : 8 px ;
}[cmdk-item] [data-disabled?] [data-selected?] Element, das auf Zeigereinzug aktiv wird. Sie sollten für jedes Element einen eindeutigen value bereitstellen, der jedoch automatisch aus dem .textContent abgeleitet wird.
< Command . Item
onSelect = { ( value ) => console . log ( 'Selected' , value ) }
// Value is implicity "apple" because of the provided text content
>
Apple
< / Command . Item > Sie können auch eine keywords -Requisite bereitstellen, um die Filterung zu unterstützen. Schlüsselwörter sind beschnitten.
< 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 > Sie können einen Gegenstand zwingen, unabhängig von der Filterung immer wieder zu rendern, indem Sie die forceMount -Propie übergeben.
[cmdk-group] [hidden?] Gruppiert Elemente zusammen mit der angegebenen heading ( [cmdk-group-heading] ).
< Command . Group heading = "Fruit" >
< Command . Item > Apple < / Command . Item >
< / Command . Group > Gruppen werden sich nicht von der DOM abmunden, sondern das hidden Attribut wird angewendet, um es vor der Sicht zu verbergen. Dies kann für Ihr Styling relevant sein.
Sie können eine Gruppe zwingen, unabhängig von der Filterung immer wieder zu rendern, indem Sie die forceMount -Propie verabschieden.
[cmdk-separator] Es ist sichtbar, wenn die Suchabfrage leer oder alwaysRender ist.
[cmdk-empty]Rendern automatisch, wenn es keine Ergebnisse für die Suchabfrage gibt.
[cmdk-loading] Sie sollten dies bedingt mit progress machen, während Sie asynchrone Elemente laden.
const [ loading , setLoading ] = React . useState ( false )
return < Command . List > { loading && < Command . Loading > Hang on… < / Command . Loading > } < / Command . List >useCommandState(state => state.selectedField) Haken, der komponiert useSyncExternalStore . Geben Sie eine Funktion über, die eine Scheibe des Befehlsmenüs zurückgibt, um erneut zu rendern, wenn sich dieses Stück ändert. Dieser Haken ist für fortschrittliche Anwendungsfälle bereitgestellt und sollte nicht häufig verwendet werden.
Ein guter Anwendungsfall wäre, einen detaillierteren leeren Zustand wie so zu machen:
const search = useCommandState ( ( state ) => state . search )
return < Command . Empty > No results found for " { search } ". < / Command . Empty > Code -Snippets für gemeinsame Anwendungsfälle.
Oft sollte die Auswahl eines Elements mit einem raffinierteren Satz von Elementen tiefer navigieren. Zum Beispiel sollte die Auswahl von "Änderungsthema ..." neue Elemente "Dark Thema" und "Lichtthema" anzeigen. Wir nennen diese Sätze von Elementen "Seiten" und können mit einfachem Zustand implementiert werden:
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 >
)Wenn Ihre Elemente verschachtelte Sub-Items haben, die Sie bei der Suche nur preisgeben möchten, rendern Sie basierend auf dem Suchstatus:
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 >
)Rendern Sie die Gegenstände, sobald sie verfügbar sind. Filterung und Sortierung erfolgt automatisch.
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 >
)Wir empfehlen die Verwendung der Radix UI Popover -Komponente. ⌘K ist auf die Dialogkomponente der Radix UI angewiesen, sodass dies Ihre Bündelgröße aufgrund gemeinsamer Abhängigkeiten ein wenig verringert.
$ pnpm install @radix-ui/react-popover Command in den Popover -Inhalt rendern:
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 >
)Sie finden globale Stylesheets als Ausgangspunkt für das Styling. Beispiele finden Sie auf Website/Styles/CMDK.
Zugänglich? Ja. Kennzeichnung, ARIA -Attribute und DOM -Bestellung mit Voice Over und Chrome Devtools getestet. Das Dialogfeld umfasst eine zugängliche Dialoginterimplementierung.
Virtualisierung? Nein, gute Leistung von bis zu 2.000-3.000 Artikeln. Lesen Sie unten, um Ihre eigenen zu bringen.
Gegenstände filtern/sortieren? Ja. Pass shouldFilter={false} zum Befehl. Bessere Speicherverwendung und Leistung. Bringen Sie Ihre eigene Virtualisierung auf diese Weise mit.
18 sicher reagieren? Ja, erforderlich. Verwendet React 18 Hooks wie useId und useSyncExternalStore .
Ungehörig? Ja, verwenden Sie die aufgelisteten CSS -Selektoren.
Feuchtigkeitsfeuchter nicht übereinstimmen? Nein, wahrscheinlich ein Fehler in Ihrem Code. Stellen Sie sicher, dass die open -Prop -to Command.Dialog auf dem Server false ist.
Strikter Modus sicher reagieren? Ja. Öffnen Sie ein Problem, wenn Sie ein Problem bemerken.
Seltsames/falsches Verhalten? Stellen Sie sicher, dass Ihr Command.Item einen key und ein einzigartiger value hat.
Gleichzeitiger Modus sicher? Vielleicht, aber der gleichzeitige Modus ist noch nicht real. Verwendet riskante Ansätze wie die manuelle DOM -Bestellung.
Serverkomponente reagieren? Nein, es ist eine Client -Komponente.
Hören Sie automatisch auf ⌘k? Nein, tun Sie es selbst, um die volle Kontrolle über den Keybind -Kontext zu haben.
Native reagieren? Nein, und keine Pläne, es zu unterstützen. Wenn Sie eine native React -Version erstellen, lassen Sie es uns wissen und wir werden Ihr Repository hier verknüpfen.
Geschrieben im Jahr 2019 von Paco (@pacocoursey), um festzustellen, ob eine komponierbare Combobox -API möglich war. Wird für das Befehlsmenü von Vercel verwendet und automatisch von Rauno (@Arunoberg) im Jahr 2020 vervollständigt. Im Jahr 2022 wurden im Jahr 2022 mit einem einfacheren und leistungsfähigeren Ansatz neu geschrieben. Ideen und Hilfe von Shu (@shuding_).
Verwendungsdatenschaft wurden aus der Version 2019 extrahiert.
Installieren Sie zunächst Abhängigkeiten und Dramatiker -Browser:
pnpm install
pnpm playwright installStellen Sie dann sicher, dass Sie die Bibliothek erstellt haben:
pnpm buildFühren Sie dann die Tests mit Ihrem lokalen Build mit echten Browser -Motoren aus:
pnpm test