
⌘Kเป็นส่วนประกอบของเมนูคำสั่งที่สามารถใช้เป็น 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 props onValueChange
บันทึก
ค่าจะถูกตัดแต่งด้วยวิธีการตัดแต่ง () เสมอ
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 >
) คุณสามารถจัดเตรียมเสา container ที่ยอมรับองค์ประกอบ HTML ที่ส่งต่อไปยังส่วนประกอบพอร์ทัลกล่องโต้ตอบของ Radix UI เพื่อระบุองค์ประกอบที่กล่องโต้ตอบควรพอร์ทัลลงใน (ค่าเริ่มต้นสู่ body ) ดูเอกสาร Radix สำหรับข้อมูลเพิ่มเติม
const containerElement = React . useRef ( null )
return (
< >
< Command . Dialog container = { containerElement . current } / >
< div ref = { containerElement } / >
< / >
)[cmdk-input] อุปกรณ์ประกอบฉากทั้งหมดจะถูกส่งต่อไปยังองค์ประกอบ input พื้นฐาน สามารถควบคุมได้ด้วย value props onValueChange
const [ search , setSearch ] = React . useState ( '' )
return < Command . Input value = { search } onValueChange = { setSearch } / >[cmdk-list] มีรายการและกลุ่ม ความสูงเคลื่อนไหวโดยใช้ตัวแปร CSS-- --cmdk-list-height
[ cmdk-list ] {
min-height : 300 px ;
height : var ( --cmdk-list-height );
max-height : 500 px ;
transition : height 100 ms ease;
}หากต้องการเลื่อนรายการไปยังมุมมองก่อนหน้านี้ใกล้กับขอบของ Viewport ให้ใช้ Scroll-Padding:
[ 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 Prop เพื่อช่วยในการกรอง คำหลักถูกตัดแต่ง
< 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 > คุณสามารถบังคับให้รายการให้เรนเดอร์ได้เสมอโดยไม่คำนึงถึงการกรองโดยผ่าน Prop forceMount
[cmdk-group] [hidden?] กลุ่มรายการร่วมกับ heading ที่กำหนด ( [cmdk-group-heading] )
< Command . Group heading = "Fruit" >
< Command . Item > Apple < / Command . Item >
< / Command . Group > กลุ่มจะไม่ได้รับการปลดออกจาก DOM แทนที่จะใช้แอตทริบิวต์ hidden เพื่อซ่อนจากมุมมอง สิ่งนี้อาจเกี่ยวข้องกับสไตล์ของคุณ
คุณสามารถบังคับให้กลุ่มต้องแสดงผลได้เสมอโดยไม่คำนึงถึงการกรองโดยผ่านเสา forceMount
[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 Popover ⌘Kอาศัยส่วนประกอบกล่องโต้ตอบ Radix UI ดังนั้นสิ่งนี้จะลดขนาดชุดของคุณเล็กน้อยเนื่องจากการพึ่งพาที่ใช้ร่วมกัน
$ pnpm install @radix-ui/react-popover แสดง Command ภายในเนื้อหา 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 >
)คุณสามารถค้นหาสไตล์ชีทระดับโลกที่จะลดลงเป็นจุดเริ่มต้นสำหรับการจัดแต่งทรงผม ดูตัวอย่างเว็บไซต์/สไตล์/cmdk
เข้าถึงได้? ใช่. การติดฉลากแอตทริบิวต์ ARIA และการสั่งซื้อ DOM ทดสอบด้วย Voice Over และ Chrome Devtools กล่องโต้ตอบประกอบด้วยการใช้กล่องโต้ตอบที่เข้าถึงได้
การจำลองเสมือน? ไม่ประสิทธิภาพที่ดีถึง 2,000-3,000 รายการ อ่านด้านล่างเพื่อนำมาเอง
ตัวกรอง/เรียงลำดับด้วยตนเอง? ใช่. ผ่าน shouldFilter={false} ถึงคำสั่ง การใช้หน่วยความจำที่ดีขึ้นและประสิทธิภาพ นำการจำลองเสมือนของคุณเองด้วยวิธีนี้
ตอบสนอง 18 ปลอดภัย? ใช่จำเป็น ใช้ hook 18 react เช่น useId และ useSyncExternalStore
ไม่มีร่องรอย? ใช่ใช้ตัวเลือก CSS ที่ระบุไว้
ความชุ่มชื้นไม่ตรงกัน? ไม่น่าจะเป็นข้อผิดพลาดในรหัสของคุณ ตรวจสอบให้แน่ใจว่า open Prop to Command.Dialog เป็น false บนเซิร์ฟเวอร์
ตอบสนองโหมดที่เข้มงวดอย่างปลอดภัย? ใช่. เปิดปัญหาหากคุณสังเกตเห็นปัญหา
พฤติกรรมแปลก/ผิด? ตรวจสอบให้แน่ใจว่า Command.Item ของคุณมี key และ value ที่ไม่ซ้ำกัน
โหมดพร้อมกันปลอดภัย? บางที แต่โหมดพร้อมกันยังไม่เป็นจริง ใช้วิธีการที่มีความเสี่ยงเช่นการสั่งซื้อ DOM ด้วยตนเอง
ส่วนประกอบเซิร์ฟเวอร์ตอบสนอง? ไม่มันเป็นองค์ประกอบของลูกค้า
ฟัง⌘Kโดยอัตโนมัติ? ไม่ทำด้วยตัวเองเพื่อควบคุมบริบท Keybind อย่างเต็มที่
ตอบสนองพื้นเมือง? ไม่และไม่มีแผนที่จะสนับสนุน หากคุณสร้าง React Native Version โปรดแจ้งให้เราทราบและเราจะเชื่อมโยงที่เก็บข้อมูลของคุณที่นี่
เขียนในปี 2562 โดย PACO (@pacocoursey) เพื่อดูว่า ComboBox API เป็นไปได้หรือไม่ ใช้สำหรับเมนูคำสั่ง vercel และการเติมข้อความอัตโนมัติโดย Rauno (@raunofreiberg) ในปี 2020 เขียนใหม่อย่างอิสระในปี 2022 ด้วยวิธีการที่ง่ายกว่าและมีประสิทธิภาพมากขึ้น แนวคิดและความช่วยเหลือจาก shu (@shuding_)
ผู้ใช้-ผู้ใช้ถูกสกัดจากเวอร์ชัน 2019
ขั้นแรกให้ติดตั้งการพึ่งพาและเบราว์เซอร์นักเขียนบทละคร:
pnpm install
pnpm playwright installจากนั้นตรวจสอบให้แน่ใจว่าคุณได้สร้างห้องสมุด:
pnpm buildจากนั้นเรียกใช้การทดสอบโดยใช้งานสร้างในพื้นที่ของคุณกับเครื่องยนต์เบราว์เซอร์จริง:
pnpm test