
⌘K是命令菜單React組件,也可以用作可訪問的ComboBox。您渲染項目,它會自動過濾和分類。 ⌘K支持完全合併的API ? ,因此您可以將項目包裹在其他組件中,甚至可以作為靜態JSX。
演示和示例: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 >
)
}或在對話框中:
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 >
)
} 所有零件向前的道具(包括ref )到適當的元素。每個部分都有一個特定的數據屬性(從cmdk-開始),可用於樣式。
[cmdk-root]渲染以顯示內聯命令菜單,或使用對話框在高架上下文中渲染。可以用value和onValueChange道具來控制。
筆記
始終使用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 >
)您可以提供一個自定義filter功能來對每個項目進行排名。請注意,該值將被修剪。
< Command
filter = { ( value , search ) => {
if ( value . includes ( search ) ) return 1
return 0
} }
/ >第三個參數, keywords ,也可以向過濾器函數提供。關鍵字充當項目值的別名,也可能影響項目的排名。關鍵字被修剪。
< Command
filter = { ( value , search , keywords ) => {
const extendValue = value + ' ' + keywords . join ( ' ' )
if ( extendValue . includes ( search ) ) return 1
return 0
} }
/ >或完全禁用過濾和分類:
< Command shouldFilter = { false } >
< Command . List >
{ filteredItems . map ( ( item ) => {
return (
< Command . Item key = { item } value = { item } >
{ item }
< / Command . Item >
)
} ) }
< / Command . List >
< / Command >您可以通過設置loop道具將箭頭鍵包裹在列表周圍(當您到達末端時,它將返回到第一個項目):
< Command loop / >[cmdk-dialog] [cmdk-overlay]道具被轉發為指揮。組成Radix UI的對話框組件。覆蓋層總是呈現的。有關更多信息,請參見Radix文檔。可以通過open和onOpenChange道具來控制。
const [ open , setOpen ] = React . useState ( false )
return (
< Command . Dialog open = { open } onOpenChange = { setOpen } >
...
< / Command . Dialog >
)您可以提供一個接受HTML元素的container支架,該元素被轉發到Radix UI的對話框門戶網站組件,以指定對話框應將其輸入到哪個元素(默認為body )。有關更多信息,請參見Radix文檔。
const containerElement = React . useRef ( null )
return (
< >
< Command . Dialog container = { containerElement . current } / >
< div ref = { containerElement } / >
< / >
)[cmdk-input]所有道具都轉發到基礎input元素。可以用value和onValueChange道具來控制。
const [ search , setSearch ] = React . useState ( '' )
return < Command . Input value = { search } onValueChange = { setSearch } / >[cmdk-list]包含項目和組。使用--cmdk-list-height CSS變量進行動畫高度。
[ cmdk-list ] {
min-height : 300 px ;
height : var ( --cmdk-list-height );
max-height : 500 px ;
transition : height 100 ms ease;
}要在視口邊緣附近滾動項目滾動到視圖中,請使用滾動填充:
[ cmdk-list ] {
scroll-padding-block-start : 8 px ;
scroll-padding-block-end : 8 px ;
}[cmdk-item] [data-disabled?] [data-selected?]在指針Enter上活躍的項目。您應該為每個項目提供一個唯一的value ,但會自動從.textContent推斷出來。
< Command . Item
onSelect = { ( value ) => console . log ( 'Selected' , value ) }
// Value is implicity "apple" because of the provided text content
>
Apple
< / Command . Item >您還可以提供一個keywords道具來幫助過濾。關鍵字被修剪。
< 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 >您可以通過傳遞forceMount Prop強迫物品始終渲染,無論其過濾如何。
[cmdk-group] [hidden?]將項目與給定的heading ( [cmdk-group-heading] )組合在一起。
< Command . Group heading = "Fruit" >
< Command . Item > Apple < / Command . Item >
< / Command . Group >組不會從DOM卸載,而是應用hidden屬性將其隱藏在視圖中。這可能與您的樣式有關。
您可以通過通過forceMount Prop迫使團隊始終渲染,無論其過濾如何。
[cmdk-separator]當搜索查詢為空或alwaysRender時可見,否則會隱藏。
[cmdk-empty]當搜索查詢沒有結果時,會自動渲染。
[cmdk-loading]在加載異步項目時,您應該有條件地以progress渲染這一點。
const [ loading , setLoading ] = React . useState ( false )
return < Command . List > { loading && < Command . Loading > Hang on… < / Command . Loading > } < / Command . List >useCommandState(state => state.selectedField)組成useSyncExternalStore鉤子。傳遞一個函數,該函數將命令菜單狀態的切片返回以重新渲染時,切片更改。此掛鉤是為高級用例提供的,不應常用。
一個好的用例是渲染一個更詳細的空狀態,這樣的用例:
const search = useCommandState ( ( state ) => state . search )
return < Command . Empty > No results found for " { search } ". < / Command . Empty > 常見用例的代碼片段。
通常,選擇一個項目應更深入地導航,並具有更精緻的項目。例如,選擇“更改主題…”應該顯示新項目“深色主題”和“光主題”。我們將這些項目稱為“頁面”,可以以簡單的狀態實現:
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 >
)如果您的項目具有僅在搜索時要揭示的嵌套子項目,請根據搜索狀態進行渲染:
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 >
)渲染物品可用。過濾和排序將自動發生。
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 >
)我們建議使用Radix UI彈出組件。 ⌘K依賴於Radix UI對話框組件,因此由於共享依賴關係,這將稍微降低您的捆綁包大小。
$ pnpm install @radix-ui/react-popover彈出內容內部的渲染Command :
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 >
)您可以找到可以放置的全球樣式表作為樣式的起點。有關示例,請參見網站/樣式/CMDK。
可以訪問?是的。標記,ARIA屬性和DOM排序用語音和Chrome DevTools測試。對話框組成了可訪問的對話框實現。
虛擬化?不。不過,良好的性能高達2,000-3,000件。在下面閱讀以攜帶自己的。
過濾/分類項目?是的。通過shouldFilter={false}命令。更好的內存使用和性能。以這種方式帶上自己的虛擬化。
反應18安全嗎?是的,需要。使用React 18掛鉤,例如useId和useSyncExternalStore 。
不風格?是的,使用列出的CSS選擇器。
水合不匹配?不,您的代碼可能是一個錯誤。確保服務器上的Command.Dialog的open prop.dialog是false 。
反應嚴格模式安全嗎?是的。如果您注意到一個問題,請打開問題。
怪異/錯誤的行為?確保您的Command.Item具有key和獨特的value 。
並發模式安全?也許,但是並發模式尚不真實。使用諸如手動DOM排序之類的風險方法。
React服務器組件?不,這是客戶端組件。
自動聽⌘K?不,請自己完全控制鑰匙扣上下文。
反應天然?不,也沒有支持它的計劃。如果您構建了React Native版本,請告訴我們,我們將在此處鏈接您的存儲庫。
Paco(@pacocoursey)在2019年撰寫,以查看是否有可能組合組合API。用於Vercel命令菜單,並於2020年由Rauno(@raunofreiberg)自動完成。在2022年獨立重寫,採用更簡單,更具性能的方法。 Shu(@shuding_)的想法和幫助。
從2019年版中提取了使用降低者。
首先,安裝依賴項和劇作家瀏覽器:
pnpm install
pnpm playwright install然後確保您已經構建了庫:
pnpm build然後使用您的本地構建對實際瀏覽器引擎進行測試:
pnpm test