
⌘K adalah komponen reaksi menu perintah yang juga dapat digunakan sebagai kombo yang dapat diakses. Anda membuat item, menyaring dan mengurutkannya secara otomatis. ⌘k mendukung API yang sepenuhnya dapat dikomposisi bagaimana? , sehingga Anda dapat membungkus item dengan komponen lain atau bahkan sebagai JSX statis.
Demo dan contoh: 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 >
)
}Atau dalam 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 >
)
} Semua bagian maju alat peraga, termasuk ref , ke elemen yang sesuai. Setiap bagian memiliki atribut data tertentu (dimulai dengan cmdk- ) yang dapat digunakan untuk penataan.
[cmdk-root] Render ini untuk menampilkan menu perintah inline, atau gunakan dialog untuk diterjemahkan dalam konteks yang ditinggikan. Dapat dikendalikan dengan value dan alat peraga onValueChange .
Catatan
Nilai selalu dipangkas dengan metode 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 >
) Anda dapat memberikan fungsi filter khusus yang dipanggil untuk memberi peringkat setiap item. Perhatikan bahwa nilainya akan dipangkas.
< Command
filter = { ( value , search ) => {
if ( value . includes ( search ) ) return 1
return 0
} }
/ > Argumen ketiga, keywords , juga dapat disediakan untuk fungsi filter. Kata kunci bertindak sebagai alias untuk nilai item, dan juga dapat mempengaruhi peringkat item. Kata kunci dipangkas.
< Command
filter = { ( value , search , keywords ) => {
const extendValue = value + ' ' + keywords . join ( ' ' )
if ( extendValue . includes ( search ) ) return 1
return 0
} }
/ >Atau nonaktifkan penyaringan dan penyortiran sepenuhnya:
< Command shouldFilter = { false } >
< Command . List >
{ filteredItems . map ( ( item ) => {
return (
< Command . Item key = { item } value = { item } >
{ item }
< / Command . Item >
)
} ) }
< / Command . List >
< / Command > Anda dapat membuat tombol panah membungkus daftar (saat Anda mencapai akhir, itu kembali ke item pertama) dengan mengatur prop loop :
< Command loop / >[cmdk-dialog] [cmdk-overlay] Alat peraga diteruskan ke perintah. Menyusun komponen dialog Radix UI. Overlay selalu diterjemahkan. Lihat dokumentasi Radix untuk informasi lebih lanjut. Dapat dikendalikan dengan alat peraga open dan onOpenChange .
const [ open , setOpen ] = React . useState ( false )
return (
< Command . Dialog open = { open } onOpenChange = { setOpen } >
...
< / Command . Dialog >
) Anda dapat menyediakan alat peraga container yang menerima elemen HTML yang diteruskan ke komponen portal dialog Radix UI untuk menentukan elemen mana dialog yang harus dialog ke dalam (default ke body ). Lihat dokumentasi Radix untuk informasi lebih lanjut.
const containerElement = React . useRef ( null )
return (
< >
< Command . Dialog container = { containerElement . current } / >
< div ref = { containerElement } / >
< / >
)[cmdk-input] Semua alat peraga diteruskan ke elemen input yang mendasarinya. Dapat dikendalikan dengan value dan alat peraga onValueChange .
const [ search , setSearch ] = React . useState ( '' )
return < Command . Input value = { search } onValueChange = { setSearch } / >[cmdk-list] Berisi item dan grup. Tinggi hidup menggunakan variabel CSS- --cmdk-list-height .
[ cmdk-list ] {
min-height : 300 px ;
height : var ( --cmdk-list-height );
max-height : 500 px ;
transition : height 100 ms ease;
}Untuk menggulir item ke tampilan sebelumnya di dekat tepi viewport, gunakan gulir-padding:
[ cmdk-list ] {
scroll-padding-block-start : 8 px ;
scroll-padding-block-end : 8 px ;
}[cmdk-item] [data-disabled?] [data-selected?] Item yang menjadi aktif di pointer enter. Anda harus memberikan value unik untuk setiap item, tetapi akan secara otomatis disimpulkan dari .textContent .
< Command . Item
onSelect = { ( value ) => console . log ( 'Selected' , value ) }
// Value is implicity "apple" because of the provided text content
>
Apple
< / Command . Item > Anda juga dapat memberikan prop keywords untuk membantu penyaringan. Kata kunci dipangkas.
< 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 > Anda dapat memaksa item untuk selalu diterjemahkan, terlepas dari penyaringan, dengan melewati prop forceMount .
[cmdk-group] [hidden?] Mengelompokkan item bersama dengan heading yang diberikan ( [cmdk-group-heading] ).
< Command . Group heading = "Fruit" >
< Command . Item > Apple < / Command . Item >
< / Command . Group > Grup tidak akan membongkar dari DOM, melainkan atribut hidden diterapkan untuk menyembunyikannya dari pandangan. Ini mungkin relevan dalam gaya Anda.
Anda dapat memaksa kelompok untuk selalu merender, terlepas dari penyaringan, dengan melewati prop forceMount .
[cmdk-separator] Terlihat ketika kueri pencarian kosong atau alwaysRender menjadi benar, tersembunyi sebaliknya.
[cmdk-empty]Secara otomatis diterjemahkan ketika tidak ada hasil untuk permintaan pencarian.
[cmdk-loading] Anda harus secara kondisional membuat ini dengan progress saat memuat item asinkron.
const [ loading , setLoading ] = React . useState ( false )
return < Command . List > { loading && < Command . Loading > Hang on… < / Command . Loading > } < / Command . List >useCommandState(state => state.selectedField) Hook yang menyusun useSyncExternalStore . Lewati fungsi yang mengembalikan sepotong status menu perintah untuk menandai ulang saat irisan itu berubah. Kait ini disediakan untuk kasus penggunaan lanjutan dan tidak boleh digunakan secara umum.
Kasus penggunaan yang baik adalah membuat keadaan kosong yang lebih rinci, seperti itu:
const search = useCommandState ( ( state ) => state . search )
return < Command . Empty > No results found for " { search } ". < / Command . Empty > Cuplikan kode untuk kasus penggunaan umum.
Sering memilih satu item harus menavigasi lebih dalam, dengan satu set item yang lebih halus. Misalnya memilih "Ubah Tema ..." harus menampilkan item baru "Tema Gelap" dan "Tema Cahaya". Kami menyebut set item ini "halaman", dan mereka dapat diimplementasikan dengan keadaan sederhana:
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 >
)Jika item Anda memiliki sub-item bersarang yang hanya ingin Anda ungkapkan saat mencari, render berdasarkan keadaan pencarian:
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 >
)Membuat barang -barang saat tersedia. Penyaringan dan penyortiran akan terjadi secara otomatis.
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 >
)Kami merekomendasikan menggunakan komponen popover Radix UI. ⌘k bergantung pada komponen dialog Radix UI, jadi ini akan mengurangi ukuran bundel Anda sedikit karena dependensi bersama.
$ pnpm install @radix-ui/react-popover Render Command di dalam konten 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 >
)Anda dapat menemukan stylesheet global untuk mampir sebagai titik awal untuk gaya. Lihat Situs Web/Gaya/CMDK untuk contoh.
Dapat diakses? Ya. Pelabelan, Atribut ARIA, dan pemesanan DOM diuji dengan Voice Over dan Chrome Devtools. Dialog menyusun implementasi dialog yang dapat diakses.
Virtualisasi? Tidak. Performa yang baik hingga 2.000-3.000 item. Baca di bawah untuk membawa milik Anda sendiri.
Filter/Sortir Item secara manual? Ya. Pass shouldFilter={false} untuk memerintahkan. Penggunaan dan kinerja memori yang lebih baik. Bawa virtualisasi Anda sendiri dengan cara ini.
Bereaksi 18 aman? Ya, diperlukan. Menggunakan react 18 hooks seperti useId dan useSyncExternalStore .
Tidak tertutup? Ya, gunakan pemilih CSS yang terdaftar.
Ketidakcocokan hidrasi? Tidak, kemungkinan bug di kode Anda. Pastikan Command.Dialog Prop open Dialog false di server.
Bereaksi mode ketat aman? Ya. Buka masalah jika Anda melihat masalah.
Perilaku aneh/salah? Pastikan Command.Item Anda.Item memiliki value key dan unik.
Mode bersamaan aman? Mungkin, tetapi mode bersamaan belum nyata. Menggunakan pendekatan berisiko seperti pemesanan dom manual.
Bereaksi komponen server? Tidak, ini komponen klien.
Dengarkan ⌘K secara otomatis? Tidak, lakukan sendiri untuk memiliki kontrol penuh atas konteks Keybind.
Bereaksi asli? Tidak, dan tidak ada rencana untuk mendukungnya. Jika Anda membangun versi Native React, beri tahu kami dan kami akan menautkan repositori Anda di sini.
Ditulis pada tahun 2019 oleh Paco (@PacoCoursey) untuk melihat apakah ComboBox API yang dapat dikomposisikan dimungkinkan. Digunakan untuk menu perintah Vercel dan AutoComplete oleh Rauno (@Raunofreiberg) pada tahun 2020. Ditulis ulang secara independen pada tahun 2022 dengan pendekatan yang lebih sederhana dan lebih berkinerja. Ide dan bantuan dari shu (@shuding_).
Penggunaan-DESCENDANS diekstraksi dari versi 2019.
Pertama, instal dependensi dan peramban drama:
pnpm install
pnpm playwright installKemudian pastikan Anda telah membangun perpustakaan:
pnpm buildKemudian jalankan tes menggunakan build lokal Anda melawan mesin browser nyata:
pnpm test