
⌘Kは、アクセス可能なコンボボックスとしても使用できるコマンドメニューの反応コンポーネントです。アイテムをレンダリングし、フィルターとソートを自動的に並べ替えます。 ⌘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
} }
/ > 3番目の引数で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 >
) Radix UIのダイアログポータルコンポーネントに転送されたHTML要素を受け入れるcontainerプロップを提供して、ダイアログがポータル(デフォルトに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?]ポインターでアクティブになるアイテム。各アイテムに一意の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 > 一般的なユースケースのコードスニペット。
多くの場合、1つのアイテムを選択すると、より洗練されたアイテムのセットを使用して、より深くナビゲートする必要があります。たとえば、「テーマの変更…」の選択は、新しいアイテム「ダークテーマ」と「ライトテーマ」を表示する必要があります。これらのアイテムのセットを「ページ」と呼び、それらは単純な状態で実装できます。
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 >
)スタイリングの出発点としてドロップインするグローバルなスタイルシートを見つけることができます。例については、Webサイト/スタイル/CMDKを参照してください。
アクセス可能ですか?はい。ラベル付け、ARIA属性、およびボイスオーバーとChrome DevtoolsでテストされたDOM順序付け。ダイアログは、アクセス可能なダイアログの実装を構成します。
仮想化?いいえ。ただし、最大2,000〜3,000のアイテムまでの優れたパフォーマンス。以下を読んで自分のものを持ってきてください。
アイテムを手動でフィルター/ソートしますか?はい。 shouldFilter={false}を渡します。より良いメモリの使用とパフォーマンス。このように独自の仮想化をもたらします。
反応18安全ですか?はい、必要です。 useIdやuseSyncExternalStoreなどの18フックを使用します。
スタイルがありませんか?はい、リストされているCSSセレクターを使用します。
水分補給のミスマッチ?いいえ、おそらくあなたのコードのバグです。 Command.Dialogへのopenプロップがサーバー上でfalseあることを確認してください。
厳密なモードを安全に反応させますか?はい。問題に気付いた場合は問題を開きます。
奇妙な/間違った行動? Command.Itemにkeyで一意のvalueがあることを確認してください。
同時モードは安全ですか?たぶん、しかし同時モードはまだ現実ではありません。手動DOM注文などの危険なアプローチを使用します。
React Serverコンポーネント?いいえ、それはクライアントコンポーネントです。
自動的に⌘kを聞いていますか?いいえ、keybindコンテキストを完全に制御できるようにしてください。
ネイティブを反応しますか?いいえ、そしてそれをサポートする計画はありません。 React Nativeバージョンを作成する場合は、お知らせください。リポジトリをこちらにリンクします。
2019年にPACO(@PacoCoursey)によって書かれて、構成可能なコンボボックスAPIが可能かどうかを確認しました。 vercelコマンドメニューに使用され、2020年にRauno(@raunofreiberg)がオートコンプリートします。2022年に独立して、よりシンプルでパフォーマンスの高いアプローチで書き直されました。 Shu(@shuding_)のアイデアとヘルプ。
Use-Descendantsは、2019年バージョンから抽出されました。
まず、依存関係と劇作家ブラウザをインストールします。
pnpm install
pnpm playwright install次に、ライブラリを構築してください。
pnpm build次に、本物のブラウザエンジンに対してローカルビルドを使用してテストを実行します。
pnpm test