
⌘K هو مكون رد فعل قائمة الأوامر يمكن استخدامه أيضًا كجهاز comboBox الذي يمكن الوصول إليه. يمكنك تقديم العناصر ، وتصفيةها وفرزها تلقائيًا. ⌘ ⌘ KO. يدعم واجهة برمجة تطبيقات قابلة للتأليف بالكامل كيف؟ ، حتى تتمكن من لف العناصر في مكونات أخرى أو حتى 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 >
) يمكنك توفير دعامة container تقبل عنصر HTML الذي يتم إعادة توجيهه إلى مكون Portal Portal الخاص بـ 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] يحتوي على عناصر ومجموعات. تحريك الارتفاع باستخدام متغير CSS- --cmdk-list-height .
[ 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 .
[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 للحصول على أمثلة.
يمكن الوصول إليه؟ نعم. وضع العلامات ، سمات الأريا ، وترتيب DOM تم اختباره مع الصوت و chrome devtools. يتكون مربع الحوار لتنفيذ مربع الحوار الذي يمكن الوصول إليه.
المحاكاة الافتراضية؟ لا. الأداء الجيد يصل إلى 2000 إلى 3000 عنصر. اقرأ أدناه لإحضار بنفسك.
تصفية/فرز العناصر يدويًا؟ نعم. يجب أن يمر shouldFilter={false} إلى الأمر. أفضل استخدام الذاكرة والأداء. إحضار المحاكاة الافتراضية الخاصة بك بهذه الطريقة.
رد فعل 18 آمنة؟ نعم ، مطلوب. يستخدم React 18 خطاف مثل useId و useSyncExternalStore .
غير معلن؟ نعم ، استخدم محددات CSS المدرجة.
عدم تطابق الترطيب؟ لا ، على الأرجح خطأ في الكود الخاص بك. تأكد من أن الدعامة open إلى Command.Dialog false على الخادم.
رد فعل الصارم آمن؟ نعم. افتح مشكلة إذا لاحظت مشكلة.
سلوك غريب/خاطئ؟ تأكد من أن Command.Item الخاص بك لديه key value فريدة.
الوضع المتزامن آمن؟ ربما ، ولكن الوضع المتزامن ليس حقيقيا بعد. يستخدم أساليب محفوفة بالمخاطر مثل ترتيب DOM اليدوي.
رد فعل مكون خادم؟ لا ، إنه مكون عميل.
استمع إلى ⌘K تلقائيًا؟ لا ، افعل ذلك بنفسك للتحكم الكامل في سياق KeyBind.
رد فعل مواطن؟ لا ، ولا توجد خطط لدعمها. إذا قمت بإنشاء إصدار React Native ، فأخبرنا وسنربط مستودعك هنا.
كتب في عام 2019 من قبل Paco (pacocoursey) لمعرفة ما إذا كان من الممكن أن يكون API combox composable. تستخدم لقائمة الأوامر Vercel والإكمال التلقائي بواسطة Rauno (raunofreiberg) في عام 2020. إعادة كتابة بشكل مستقل في عام 2022 مع نهج أبسط وأكثر أداء. الأفكار والمساعدة من Shu (shuding_).
تم استخلاص أدوات الاستخدام من إصدار 2019.
أولاً ، تثبيت التبعيات ومتصفحات الكاتب المسرحي:
pnpm install
pnpm playwright installثم تأكد من قيامك ببناء المكتبة:
pnpm buildثم قم بتشغيل الاختبارات باستخدام بنيتك المحلية ضد محركات المتصفح الحقيقي:
pnpm test