diff --git a/packages/@react-aria/menu/src/useMenuItem.ts b/packages/@react-aria/menu/src/useMenuItem.ts index 231f2ae73f6..ca84d9cb01a 100644 --- a/packages/@react-aria/menu/src/useMenuItem.ts +++ b/packages/@react-aria/menu/src/useMenuItem.ts @@ -33,6 +33,10 @@ export interface MenuItemAria { /** Props for the keyboard shortcut text element inside the item, if any. */ keyboardShortcutProps: DOMAttributes, + // TODO: this is quite specific, but description and keyboardShortcut above are also very specific + /** Props for the descriptive element in the end slot inside the menu item (e.g. info icon, chevron). */ + endSlotProps: DOMAttributes, + /** Whether the item is currently focused. */ isFocused: boolean, /** Whether the item is keyboard focused. */ @@ -170,6 +174,7 @@ export function useMenuItem(props: AriaMenuItemProps, state: TreeState, re let labelId = useSlotId(); let descriptionId = useSlotId(); let keyboardId = useSlotId(); + let endSlotId = useSlotId(); let ariaProps = { id, @@ -177,7 +182,7 @@ export function useMenuItem(props: AriaMenuItemProps, state: TreeState, re role, 'aria-label': props['aria-label'], 'aria-labelledby': labelId, - 'aria-describedby': [descriptionId, keyboardId].filter(Boolean).join(' ') || undefined, + 'aria-describedby': [descriptionId, keyboardId, endSlotId].filter(Boolean).join(' ') || undefined, 'aria-controls': props['aria-controls'], 'aria-haspopup': hasPopup, 'aria-expanded': props['aria-expanded'] @@ -342,6 +347,9 @@ export function useMenuItem(props: AriaMenuItemProps, state: TreeState, re keyboardShortcutProps: { id: keyboardId }, + endSlotProps: { + id: endSlotId + }, isFocused, isFocusVisible: isFocused && selectionManager.isFocused && isFocusVisible() && !isTriggerExpanded, isSelected, diff --git a/packages/@react-aria/menu/src/useSubmenuTrigger.ts b/packages/@react-aria/menu/src/useSubmenuTrigger.ts index 65b98a3c3ca..85ec91e8999 100644 --- a/packages/@react-aria/menu/src/useSubmenuTrigger.ts +++ b/packages/@react-aria/menu/src/useSubmenuTrigger.ts @@ -43,7 +43,7 @@ export interface AriaSubmenuTriggerProps { shouldUseVirtualFocus?: boolean } -interface SubmenuTriggerProps extends Omit { +interface SubmenuTriggerProps extends Omit { /** Whether the submenu trigger is in an expanded state. */ isOpen: boolean } diff --git a/packages/@react-spectrum/s2/chromatic/Menu.stories.tsx b/packages/@react-spectrum/s2/chromatic/Menu.stories.tsx index e8832eaf4c2..80a5e1ca1bd 100644 --- a/packages/@react-spectrum/s2/chromatic/Menu.stories.tsx +++ b/packages/@react-spectrum/s2/chromatic/Menu.stories.tsx @@ -10,7 +10,8 @@ * governing permissions and limitations under the License. */ -import {BlendModes, DynamicExample, Example, KeyboardShortcuts, PublishAndExport} from '../stories/Menu.stories'; +import {BlendModes, DynamicExample, Example, KeyboardShortcuts, PublishAndExport, UnavailableMenuItem} from '../stories/Menu.stories'; +import {expect} from '@storybook/jest'; import {Menu} from '../src'; import type {Meta, StoryObj} from '@storybook/react'; import {userEvent, within} from 'storybook/test'; @@ -56,3 +57,18 @@ export const Dynamic: Story = { ...DynamicExample, play: async (context) => await Default.play!(context) }; + +export const WithUnavailableItem: Story = { + ...UnavailableMenuItem, + play: async ({canvasElement}) => { + await userEvent.tab(); + await userEvent.keyboard('{ArrowDown}'); + let body = canvasElement.ownerDocument.body; + await within(body).findByRole('menu'); + await userEvent.keyboard('{ArrowDown}'); + await userEvent.keyboard('{ArrowDown}'); + await userEvent.keyboard('{ArrowRight}'); + let menus = await within(body).findAllByRole('dialog'); + expect(menus).toHaveLength(2); + } +}; diff --git a/packages/@react-spectrum/s2/intl/ar-AE.json b/packages/@react-spectrum/s2/intl/ar-AE.json index 8545d91acfd..a66391647e6 100644 --- a/packages/@react-spectrum/s2/intl/ar-AE.json +++ b/packages/@react-spectrum/s2/intl/ar-AE.json @@ -23,6 +23,7 @@ "label.(optional)": "(اختياري)", "label.(required)": "(مطلوب)", "menu.moreActions": "المزيد من الإجراءات", + "menu.unavailable": "غير مُتوفر، قُم بالتوسيع للحصول على التفاصيل", "notificationbadge.indicatorOnly": "نشاط جديد", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "تحديد…", diff --git a/packages/@react-spectrum/s2/intl/bg-BG.json b/packages/@react-spectrum/s2/intl/bg-BG.json index 02ecdbc3019..c70ca77f057 100644 --- a/packages/@react-spectrum/s2/intl/bg-BG.json +++ b/packages/@react-spectrum/s2/intl/bg-BG.json @@ -23,6 +23,7 @@ "label.(optional)": "(незадължително)", "label.(required)": "(задължително)", "menu.moreActions": "Повече действия", + "menu.unavailable": "Недостъпно, разгънете за подробности", "notificationbadge.indicatorOnly": "Нова дейност", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Изберете…", diff --git a/packages/@react-spectrum/s2/intl/cs-CZ.json b/packages/@react-spectrum/s2/intl/cs-CZ.json index daa436a2556..60ccac47a4c 100644 --- a/packages/@react-spectrum/s2/intl/cs-CZ.json +++ b/packages/@react-spectrum/s2/intl/cs-CZ.json @@ -23,6 +23,7 @@ "label.(optional)": "(volitelně)", "label.(required)": "(požadováno)", "menu.moreActions": "Další akce", + "menu.unavailable": "Není k dispozici, rozbalením zobrazíte podrobnosti", "notificationbadge.indicatorOnly": "Nová aktivita", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Vybrat…", diff --git a/packages/@react-spectrum/s2/intl/da-DK.json b/packages/@react-spectrum/s2/intl/da-DK.json index e6f272123a3..005336329b0 100644 --- a/packages/@react-spectrum/s2/intl/da-DK.json +++ b/packages/@react-spectrum/s2/intl/da-DK.json @@ -23,6 +23,7 @@ "label.(optional)": "(valgfrit)", "label.(required)": "(obligatorisk)", "menu.moreActions": "Flere handlinger", + "menu.unavailable": "Ikke tilgængelig, udvid for detaljer", "notificationbadge.indicatorOnly": "Ny aktivitet", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Vælg…", diff --git a/packages/@react-spectrum/s2/intl/de-DE.json b/packages/@react-spectrum/s2/intl/de-DE.json index 7256c4790e7..8e696210662 100644 --- a/packages/@react-spectrum/s2/intl/de-DE.json +++ b/packages/@react-spectrum/s2/intl/de-DE.json @@ -23,6 +23,7 @@ "label.(optional)": "(optional)", "label.(required)": "(erforderlich)", "menu.moreActions": "Mehr Aktionen", + "menu.unavailable": "Nicht verfügbar, für Details erweitern", "notificationbadge.indicatorOnly": "Neue Aktivität", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Auswählen…", diff --git a/packages/@react-spectrum/s2/intl/el-GR.json b/packages/@react-spectrum/s2/intl/el-GR.json index 90a98fb862e..f4f7d60e37a 100644 --- a/packages/@react-spectrum/s2/intl/el-GR.json +++ b/packages/@react-spectrum/s2/intl/el-GR.json @@ -23,6 +23,7 @@ "label.(optional)": "(προαιρετικό)", "label.(required)": "(απαιτείται)", "menu.moreActions": "Περισσότερες ενέργειες", + "menu.unavailable": "Μη διαθέσιμο, ανάπτυξη για λεπτομέρειες", "notificationbadge.indicatorOnly": "Νέα δραστηριότητα", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Επιλογή…", diff --git a/packages/@react-spectrum/s2/intl/en-US.json b/packages/@react-spectrum/s2/intl/en-US.json index 01be6111c20..c8375745930 100644 --- a/packages/@react-spectrum/s2/intl/en-US.json +++ b/packages/@react-spectrum/s2/intl/en-US.json @@ -23,6 +23,7 @@ "label.(optional)": "(optional)", "label.(required)": "(required)", "menu.moreActions": "More actions", + "menu.unavailable": "Unavailable, expand for details", "notificationbadge.indicatorOnly": "New activity", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Select…", diff --git a/packages/@react-spectrum/s2/intl/es-ES.json b/packages/@react-spectrum/s2/intl/es-ES.json index feffd69320b..6b6551ee497 100644 --- a/packages/@react-spectrum/s2/intl/es-ES.json +++ b/packages/@react-spectrum/s2/intl/es-ES.json @@ -23,6 +23,7 @@ "label.(optional)": "(opcional)", "label.(required)": "(obligatorio)", "menu.moreActions": "Más acciones", + "menu.unavailable": "No disponible, ampliar para más detalles", "notificationbadge.indicatorOnly": "Nueva actividad", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Seleccione…", diff --git a/packages/@react-spectrum/s2/intl/et-EE.json b/packages/@react-spectrum/s2/intl/et-EE.json index 3ee0a81aa6e..a9ac34575f6 100644 --- a/packages/@react-spectrum/s2/intl/et-EE.json +++ b/packages/@react-spectrum/s2/intl/et-EE.json @@ -23,6 +23,7 @@ "label.(optional)": "(valikuline)", "label.(required)": "(nõutav)", "menu.moreActions": "Veel toiminguid", + "menu.unavailable": "Pole kättesaadav, üksikasjade vaatamiseks laiendage", "notificationbadge.indicatorOnly": "Uus tegevus", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Valige…", diff --git a/packages/@react-spectrum/s2/intl/fi-FI.json b/packages/@react-spectrum/s2/intl/fi-FI.json index e541ada653f..2abfc9a4c84 100644 --- a/packages/@react-spectrum/s2/intl/fi-FI.json +++ b/packages/@react-spectrum/s2/intl/fi-FI.json @@ -23,6 +23,7 @@ "label.(optional)": "(valinnainen)", "label.(required)": "(pakollinen)", "menu.moreActions": "Lisää toimintoja", + "menu.unavailable": "Ei saatavilla, laajenna saadaksesi lisätietoja", "notificationbadge.indicatorOnly": "Uusi toiminta", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Valitse…", diff --git a/packages/@react-spectrum/s2/intl/fr-FR.json b/packages/@react-spectrum/s2/intl/fr-FR.json index e298543e539..afef2ad5752 100644 --- a/packages/@react-spectrum/s2/intl/fr-FR.json +++ b/packages/@react-spectrum/s2/intl/fr-FR.json @@ -23,6 +23,7 @@ "label.(optional)": "(facultatif)", "label.(required)": "(requis)", "menu.moreActions": "Autres actions", + "menu.unavailable": "Indisponible, développer pour plus de détails", "notificationbadge.indicatorOnly": "Nouvelle activité", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Sélectionner…", diff --git a/packages/@react-spectrum/s2/intl/he-IL.json b/packages/@react-spectrum/s2/intl/he-IL.json index cac33237926..9fe25ac115b 100644 --- a/packages/@react-spectrum/s2/intl/he-IL.json +++ b/packages/@react-spectrum/s2/intl/he-IL.json @@ -23,6 +23,7 @@ "label.(optional)": "(אופציונלי)", "label.(required)": "(נדרש)", "menu.moreActions": "פעולות נוספות", + "menu.unavailable": "לא זמין, הרחב לפרטים", "notificationbadge.indicatorOnly": "פעילות חדשה", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "בחר…", diff --git a/packages/@react-spectrum/s2/intl/hr-HR.json b/packages/@react-spectrum/s2/intl/hr-HR.json index d785dc1390b..c566c400924 100644 --- a/packages/@react-spectrum/s2/intl/hr-HR.json +++ b/packages/@react-spectrum/s2/intl/hr-HR.json @@ -23,6 +23,7 @@ "label.(optional)": "(opcionalno)", "label.(required)": "(obvezno)", "menu.moreActions": "Dodatne radnje", + "menu.unavailable": "Nije dostupno, proširi za detalje", "notificationbadge.indicatorOnly": "Nova aktivnost", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Odaberite…", diff --git a/packages/@react-spectrum/s2/intl/hu-HU.json b/packages/@react-spectrum/s2/intl/hu-HU.json index 6d826f1259e..f82e54bec92 100644 --- a/packages/@react-spectrum/s2/intl/hu-HU.json +++ b/packages/@react-spectrum/s2/intl/hu-HU.json @@ -23,6 +23,7 @@ "label.(optional)": "(opcionális)", "label.(required)": "(kötelező)", "menu.moreActions": "További lehetőségek", + "menu.unavailable": "Nem érhető el, a részletekért bontsa ki", "notificationbadge.indicatorOnly": "Új tevékenység", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Kiválasztás…", diff --git a/packages/@react-spectrum/s2/intl/it-IT.json b/packages/@react-spectrum/s2/intl/it-IT.json index 87cb218475e..a66e379f5af 100644 --- a/packages/@react-spectrum/s2/intl/it-IT.json +++ b/packages/@react-spectrum/s2/intl/it-IT.json @@ -23,6 +23,7 @@ "label.(optional)": "(facoltativo)", "label.(required)": "(obbligatorio)", "menu.moreActions": "Altre azioni", + "menu.unavailable": "Non disponibile, espandi per i dettagli", "notificationbadge.indicatorOnly": "Nuova attività", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Seleziona…", diff --git a/packages/@react-spectrum/s2/intl/ja-JP.json b/packages/@react-spectrum/s2/intl/ja-JP.json index 4f7c3e0e6e2..bb06130fef8 100644 --- a/packages/@react-spectrum/s2/intl/ja-JP.json +++ b/packages/@react-spectrum/s2/intl/ja-JP.json @@ -23,6 +23,7 @@ "label.(optional)": "(オプション)", "label.(required)": "(必須)", "menu.moreActions": "その他のアクション", + "menu.unavailable": "利用できません。詳しくは、展開して確認してください", "notificationbadge.indicatorOnly": "新規アクティビティ", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "選択…", diff --git a/packages/@react-spectrum/s2/intl/ko-KR.json b/packages/@react-spectrum/s2/intl/ko-KR.json index 2d5e859ba29..e010ac6591c 100644 --- a/packages/@react-spectrum/s2/intl/ko-KR.json +++ b/packages/@react-spectrum/s2/intl/ko-KR.json @@ -23,6 +23,7 @@ "label.(optional)": "(선택 사항)", "label.(required)": "(필수 사항)", "menu.moreActions": "기타 액션", + "menu.unavailable": "사용할 수 없음, 자세히 보려면 펼치기", "notificationbadge.indicatorOnly": "새로운 활동", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "선택…", diff --git a/packages/@react-spectrum/s2/intl/lt-LT.json b/packages/@react-spectrum/s2/intl/lt-LT.json index 7429827c18b..e52c74583a6 100644 --- a/packages/@react-spectrum/s2/intl/lt-LT.json +++ b/packages/@react-spectrum/s2/intl/lt-LT.json @@ -23,6 +23,7 @@ "label.(optional)": "(pasirenkama)", "label.(required)": "(privaloma)", "menu.moreActions": "Daugiau veiksmų", + "menu.unavailable": "Nepasiekiama, norėdami gauti daugiau informacijos, išskleiskite", "notificationbadge.indicatorOnly": "Nauja veikla", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Pasirinkite…", diff --git a/packages/@react-spectrum/s2/intl/lv-LV.json b/packages/@react-spectrum/s2/intl/lv-LV.json index d8ebbec139d..389ea6f8b33 100644 --- a/packages/@react-spectrum/s2/intl/lv-LV.json +++ b/packages/@react-spectrum/s2/intl/lv-LV.json @@ -23,6 +23,7 @@ "label.(optional)": "(neobligāti)", "label.(required)": "(obligāti)", "menu.moreActions": "Citas darbības", + "menu.unavailable": "Nav pieejams, izvērsiet, lai skatītu sīkāku informāciju", "notificationbadge.indicatorOnly": "Jauna aktivitāte", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Izvēlēties…", diff --git a/packages/@react-spectrum/s2/intl/nb-NO.json b/packages/@react-spectrum/s2/intl/nb-NO.json index 22f1735a94a..d53f0d8aa59 100644 --- a/packages/@react-spectrum/s2/intl/nb-NO.json +++ b/packages/@react-spectrum/s2/intl/nb-NO.json @@ -23,6 +23,7 @@ "label.(optional)": "(valgfritt)", "label.(required)": "(obligatorisk)", "menu.moreActions": "Flere handlinger", + "menu.unavailable": "Utilgjengelig, utvid for detaljer", "notificationbadge.indicatorOnly": "Ny aktivitet", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Velg …", diff --git a/packages/@react-spectrum/s2/intl/nl-NL.json b/packages/@react-spectrum/s2/intl/nl-NL.json index ecf1ac9e266..a861126de64 100644 --- a/packages/@react-spectrum/s2/intl/nl-NL.json +++ b/packages/@react-spectrum/s2/intl/nl-NL.json @@ -23,6 +23,7 @@ "label.(optional)": "(optioneel)", "label.(required)": "(vereist)", "menu.moreActions": "Meer handelingen", + "menu.unavailable": "Niet beschikbaar, uitvouwen voor meer informatie", "notificationbadge.indicatorOnly": "Nieuwe activiteit", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Selecteren…", diff --git a/packages/@react-spectrum/s2/intl/pl-PL.json b/packages/@react-spectrum/s2/intl/pl-PL.json index f32cae48324..14104c6cbbc 100644 --- a/packages/@react-spectrum/s2/intl/pl-PL.json +++ b/packages/@react-spectrum/s2/intl/pl-PL.json @@ -23,6 +23,7 @@ "label.(optional)": "(opcjonalne)", "label.(required)": "(wymagane)", "menu.moreActions": "Więcej akcji", + "menu.unavailable": "Niedostępne, rozwiń, aby zobaczyć szczegóły", "notificationbadge.indicatorOnly": "Nowa aktywność", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Zaznacz…", diff --git a/packages/@react-spectrum/s2/intl/pt-BR.json b/packages/@react-spectrum/s2/intl/pt-BR.json index cad34acb6ba..b9f826287db 100644 --- a/packages/@react-spectrum/s2/intl/pt-BR.json +++ b/packages/@react-spectrum/s2/intl/pt-BR.json @@ -23,6 +23,7 @@ "label.(optional)": "(opcional)", "label.(required)": "(obrigatório)", "menu.moreActions": "Mais ações", + "menu.unavailable": "Indisponível. Expanda para ver os detalhes", "notificationbadge.indicatorOnly": "Nova atividade", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Selecionar…", diff --git a/packages/@react-spectrum/s2/intl/pt-PT.json b/packages/@react-spectrum/s2/intl/pt-PT.json index 5d688a911fe..bb6acd6c981 100644 --- a/packages/@react-spectrum/s2/intl/pt-PT.json +++ b/packages/@react-spectrum/s2/intl/pt-PT.json @@ -23,6 +23,7 @@ "label.(optional)": "(opcional)", "label.(required)": "(obrigatório)", "menu.moreActions": "Mais ações", + "menu.unavailable": "Indisponível, expandir para mais detalhes", "notificationbadge.indicatorOnly": "Nova atividade", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Selecionar…", diff --git a/packages/@react-spectrum/s2/intl/ro-RO.json b/packages/@react-spectrum/s2/intl/ro-RO.json index 2ac95333790..050df91e413 100644 --- a/packages/@react-spectrum/s2/intl/ro-RO.json +++ b/packages/@react-spectrum/s2/intl/ro-RO.json @@ -23,6 +23,7 @@ "label.(optional)": "(opţional)", "label.(required)": "(obligatoriu)", "menu.moreActions": "Mai multe acțiuni", + "menu.unavailable": "Indisponibil, extindeți pentru detalii", "notificationbadge.indicatorOnly": "Activitate nouă", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Selectați…", diff --git a/packages/@react-spectrum/s2/intl/ru-RU.json b/packages/@react-spectrum/s2/intl/ru-RU.json index e6548526d1a..cfb6c4d1ded 100644 --- a/packages/@react-spectrum/s2/intl/ru-RU.json +++ b/packages/@react-spectrum/s2/intl/ru-RU.json @@ -23,6 +23,7 @@ "label.(optional)": "(дополнительно)", "label.(required)": "(обязательно)", "menu.moreActions": "Дополнительные действия", + "menu.unavailable": "Недоступно, разверните для подробностей", "notificationbadge.indicatorOnly": "Новая активность", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Выбрать…", diff --git a/packages/@react-spectrum/s2/intl/sk-SK.json b/packages/@react-spectrum/s2/intl/sk-SK.json index 001ccfea64d..a29590ac115 100644 --- a/packages/@react-spectrum/s2/intl/sk-SK.json +++ b/packages/@react-spectrum/s2/intl/sk-SK.json @@ -23,6 +23,7 @@ "label.(optional)": "(nepovinné)", "label.(required)": "(povinné)", "menu.moreActions": "Ďalšie akcie", + "menu.unavailable": "Nedostupné, rozbaľte podrobnosti", "notificationbadge.indicatorOnly": "Nová aktivita", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Vybrať…", diff --git a/packages/@react-spectrum/s2/intl/sl-SI.json b/packages/@react-spectrum/s2/intl/sl-SI.json index f48cd9f05fa..39656e853b5 100644 --- a/packages/@react-spectrum/s2/intl/sl-SI.json +++ b/packages/@react-spectrum/s2/intl/sl-SI.json @@ -23,6 +23,7 @@ "label.(optional)": "(opcijsko)", "label.(required)": "(obvezno)", "menu.moreActions": "Več možnosti", + "menu.unavailable": "Ni na voljo, razširite za podrobnosti", "notificationbadge.indicatorOnly": "Nova dejavnost", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Izberite…", diff --git a/packages/@react-spectrum/s2/intl/sr-SP.json b/packages/@react-spectrum/s2/intl/sr-SP.json index 478e13e07df..d6e89eb94fb 100644 --- a/packages/@react-spectrum/s2/intl/sr-SP.json +++ b/packages/@react-spectrum/s2/intl/sr-SP.json @@ -23,6 +23,7 @@ "label.(optional)": "(opciono)", "label.(required)": "(obavezno)", "menu.moreActions": "Dodatne radnje", + "menu.unavailable": "Nije dostupno, proširite za detalje", "notificationbadge.indicatorOnly": "Nova aktivnost", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Izaberite...", diff --git a/packages/@react-spectrum/s2/intl/sv-SE.json b/packages/@react-spectrum/s2/intl/sv-SE.json index 2caf584ba88..12026081606 100644 --- a/packages/@react-spectrum/s2/intl/sv-SE.json +++ b/packages/@react-spectrum/s2/intl/sv-SE.json @@ -23,6 +23,7 @@ "label.(optional)": "(valfritt)", "label.(required)": "(krävs)", "menu.moreActions": "Fler åtgärder", + "menu.unavailable": "Ej tillgänglig, expandera för mer information", "notificationbadge.indicatorOnly": "Ny aktivitet", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Välj…", diff --git a/packages/@react-spectrum/s2/intl/tr-TR.json b/packages/@react-spectrum/s2/intl/tr-TR.json index cbb84153287..ee8f9b014a6 100644 --- a/packages/@react-spectrum/s2/intl/tr-TR.json +++ b/packages/@react-spectrum/s2/intl/tr-TR.json @@ -23,6 +23,7 @@ "label.(optional)": "(isteğe bağlı)", "label.(required)": "(gerekli)", "menu.moreActions": "Daha fazla eylem", + "menu.unavailable": "Kullanılamıyor, ayrıntıları görmek için genişletin", "notificationbadge.indicatorOnly": "Yeni etkinlik", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Seçin…", diff --git a/packages/@react-spectrum/s2/intl/uk-UA.json b/packages/@react-spectrum/s2/intl/uk-UA.json index ff7025bd7da..1446a24e72e 100644 --- a/packages/@react-spectrum/s2/intl/uk-UA.json +++ b/packages/@react-spectrum/s2/intl/uk-UA.json @@ -23,6 +23,7 @@ "label.(optional)": "(необов’язково)", "label.(required)": "(обов’язково)", "menu.moreActions": "Більше дій", + "menu.unavailable": "Недоступно, розгорніть для докладнішої інформації", "notificationbadge.indicatorOnly": "Нова активність", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "Вибрати…", diff --git a/packages/@react-spectrum/s2/intl/zh-CN.json b/packages/@react-spectrum/s2/intl/zh-CN.json index 9c94e3a820a..d2d266cbc94 100644 --- a/packages/@react-spectrum/s2/intl/zh-CN.json +++ b/packages/@react-spectrum/s2/intl/zh-CN.json @@ -23,6 +23,7 @@ "label.(optional)": "(可选)", "label.(required)": "(必填)", "menu.moreActions": "更多操作", + "menu.unavailable": "不可用,展开以查看详细信息", "notificationbadge.indicatorOnly": "新活动", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "选择...", diff --git a/packages/@react-spectrum/s2/intl/zh-TW.json b/packages/@react-spectrum/s2/intl/zh-TW.json index 8496c287826..ed50a588af8 100644 --- a/packages/@react-spectrum/s2/intl/zh-TW.json +++ b/packages/@react-spectrum/s2/intl/zh-TW.json @@ -23,6 +23,7 @@ "label.(optional)": "(選填)", "label.(required)": "(必填)", "menu.moreActions": "更多動作", + "menu.unavailable": "無法使用,展開以取得詳細資料", "notificationbadge.indicatorOnly": "新活動", "notificationbadge.plus": "{notifications}+", "picker.placeholder": "選取…", diff --git a/packages/@react-spectrum/s2/src/Menu.tsx b/packages/@react-spectrum/s2/src/Menu.tsx index bb0d8a9fe25..adf8e75e2f8 100644 --- a/packages/@react-spectrum/s2/src/Menu.tsx +++ b/packages/@react-spectrum/s2/src/Menu.tsx @@ -21,12 +21,16 @@ import { MenuTriggerProps as AriaMenuTriggerProps, SubmenuTrigger as AriaSubmenuTrigger, SubmenuTriggerProps as AriaSubmenuTriggerProps, + UnavailableMenuItemTrigger as AriaUnavailableMenuItemTrigger, + UnavailableMenuItemTriggerProps as AriaUnavailableMenuItemTriggerProps, ContextValue, DEFAULT_SLOT, + EndSlotContext, MenuItemRenderProps, Provider, Separator, - SeparatorProps + SeparatorProps, + useSlottedContext } from 'react-aria-components'; import {baseColor, focusRing, fontRelative, size, space, style} from '../style' with {type: 'macro'}; import {box, iconStyles} from './Checkbox'; @@ -40,15 +44,19 @@ import {DOMRef, DOMRefValue, GlobalDOMAttributes, PressEvent} from '@react-types import {edgeToText} from '../style/spectrum-theme' with {type: 'macro'}; import {forwardRefType} from './types'; import {HeaderContext, HeadingContext, KeyboardContext, Text, TextContext} from './Content'; -import {IconContext} from './Icon'; // chevron right removed?? -import {ImageContext} from './Image'; +import {IconContext} from './Icon'; +import {ImageContext} from './Image'; // chevron right removed?? +import InfoCircleIcon from '../s2wf-icons/S2_Icon_InfoCircle_20_N.svg'; import {InPopoverContext, Popover, PopoverContext} from './Popover'; +// @ts-ignore +import intlMessages from '../intl/*.json'; import LinkOutIcon from '../ui-icons/LinkOut'; import {mergeStyles} from '../style/runtime'; import {Placement, useLocale} from 'react-aria'; import {PressResponder} from '@react-aria/interactions'; import {pressScale} from './pressScale'; import {useGlobalListeners} from '@react-aria/utils'; +import {useLocalizedStringFormatter} from '@react-aria/i18n'; import {useSpectrumContextProps} from './useSpectrumContextProps'; // viewbox on LinkOut is super weird just because i copied the icon from designs... // need to strip id's from icons @@ -317,6 +325,7 @@ let keyboard = style<{size: 'S' | 'M' | 'L' | 'XL', isDisabled: boolean}>({ let descriptor = style({ gridArea: 'descriptor', + placeSelf: 'end', marginStart: 8, '--iconPrimary': { type: 'fill', @@ -324,6 +333,19 @@ let descriptor = style({ } }); +let descriptorIcon = style<{size: 'S' | 'M' | 'L' | 'XL'}>({ + marginEnd: 0, + display: 'block', + size: { + size: { + S: 16, + M: 20, + L: 24, + XL: 26 + } + } +}); + let InternalMenuContext = createContext<{size: 'S' | 'M' | 'L' | 'XL', isSubmenu: boolean, hideLinkOutIcon: boolean}>({ size: 'M', isSubmenu: false, @@ -459,6 +481,33 @@ const linkIconSize = { XL: 'XL' } as const; +interface UnavailableIconWrapperProps { + direction: 'ltr' | 'rtl', + size: 'S' | 'M' | 'L' | 'XL' +} + +function UnavailableIconWrapper(props: UnavailableIconWrapperProps) { + let endSlotProps = useSlottedContext(EndSlotContext); + let {direction, size} = props; + let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2'); + + return ( +
+ + + +
+ ); +} + export function MenuItem(props: MenuItemProps): ReactNode { let ref = useRef(null); let isLink = props.href != null; @@ -466,6 +515,7 @@ export function MenuItem(props: MenuItemProps): ReactNode { let {size, hideLinkOutIcon} = useContext(InternalMenuContext); let textValue = props.textValue || (typeof props.children === 'string' ? props.children : undefined); let {direction} = useLocale(); + return ( )} + {renderProps.isUnavailable && ( + + )} {renderProps.hasSubmenu && (
+ {props.children[0]} + + {props.children[1]} + + + ); +} + +export {MenuTrigger, SubmenuTrigger, UnavailableMenuItemTrigger}; // This is purely so that storybook generates the types for both Menu and MenuTrigger interface ICombined extends MenuProps, Omit {} diff --git a/packages/@react-spectrum/s2/src/index.ts b/packages/@react-spectrum/s2/src/index.ts index 0865c16c0cb..aac8e0ea709 100644 --- a/packages/@react-spectrum/s2/src/index.ts +++ b/packages/@react-spectrum/s2/src/index.ts @@ -57,7 +57,7 @@ export {Image, ImageContext} from './Image'; export {ImageCoordinator} from './ImageCoordinator'; export {InlineAlert, InlineAlertContext} from './InlineAlert'; export {Link, LinkContext} from './Link'; -export {MenuItem, MenuTrigger, Menu, MenuSection, SubmenuTrigger, MenuContext} from './Menu'; +export {MenuItem, MenuTrigger, Menu, MenuSection, SubmenuTrigger, UnavailableMenuItemTrigger, MenuContext} from './Menu'; export {Meter, MeterContext} from './Meter'; export {NotificationBadge, NotificationBadgeContext} from './NotificationBadge'; export {NumberField, NumberFieldContext} from './NumberField'; @@ -136,7 +136,7 @@ export type {InlineAlertProps} from './InlineAlert'; export type {ImageProps} from './Image'; export type {ImageCoordinatorProps} from './ImageCoordinator'; export type {LinkProps} from './Link'; -export type {MenuTriggerProps, MenuProps, MenuItemProps, MenuSectionProps, SubmenuTriggerProps} from './Menu'; +export type {MenuTriggerProps, MenuProps, MenuItemProps, MenuSectionProps, SubmenuTriggerProps, UnavailableMenuItemTriggerProps} from './Menu'; export type {MeterProps} from './Meter'; export type {NotificationBadgeProps} from './NotificationBadge'; export type {PickerProps, PickerItemProps, PickerSectionProps} from './Picker'; diff --git a/packages/@react-spectrum/s2/stories/Menu.stories.tsx b/packages/@react-spectrum/s2/stories/Menu.stories.tsx index ffe495dbf39..b4ec9190f56 100644 --- a/packages/@react-spectrum/s2/stories/Menu.stories.tsx +++ b/packages/@react-spectrum/s2/stories/Menu.stories.tsx @@ -14,7 +14,7 @@ import AlignLeft from '../s2wf-icons/S2_Icon_TextAlignLeft_20_N.svg'; import AlignMiddle from '../s2wf-icons/S2_Icon_TextAlignCenter_20_N.svg'; import AlignRight from '../s2wf-icons/S2_Icon_TextAlignRight_20_N.svg'; import Bold from '../s2wf-icons/S2_Icon_TextBold_20_N.svg'; -import {Button, Header, Heading, Image, Keyboard, Menu, MenuItem, MenuProps, MenuSection, MenuTrigger, SubmenuTrigger, Text} from '../src'; +import {Button, Header, Heading, Image, Keyboard, Menu, MenuItem, MenuProps, MenuSection, MenuTrigger, Popover, SubmenuTrigger, Text, UnavailableMenuItemTrigger} from '../src'; import {categorizeArgTypes, getActionArgs} from './utils'; import ClockPendingIcon from '../s2wf-icons/S2_Icon_ClockPending_20_N.svg'; import {CombinedMenu} from '../src/Menu'; @@ -32,6 +32,7 @@ import NewIcon from '../s2wf-icons/S2_Icon_New_20_N.svg'; import Paste from '../s2wf-icons/S2_Icon_Paste_20_N.svg'; import {ReactElement, useState} from 'react'; import {Selection} from 'react-aria-components'; +import {style} from '../style' with {type: 'macro'}; import TextIcon from '../s2wf-icons/S2_Icon_Text_20_N.svg'; import Underline from '../s2wf-icons/S2_Icon_TextUnderline_20_N.svg'; @@ -315,3 +316,39 @@ export const SelectionGroups: StoryObj = { layout: 'padded' } }; + +export const UnavailableMenuItem: Story = { + render: (args) => { + return ( + + + + Favorite + + Edit + +
+ Contact your administrator for permissions to edit this item. +
+
+
+ + Delete + +
+ Contact your administrator for permissions to delete this item. +
+
+
+ + Share + + SMS + Email + + +
+
+ ); + } +}; diff --git a/packages/@react-spectrum/s2/test/Menu.test.tsx b/packages/@react-spectrum/s2/test/Menu.test.tsx index 32bfae1ee75..e4426cc2998 100644 --- a/packages/@react-spectrum/s2/test/Menu.test.tsx +++ b/packages/@react-spectrum/s2/test/Menu.test.tsx @@ -11,10 +11,12 @@ */ import {AriaMenuTests} from '../../../react-aria-components/test/AriaMenu.test-util'; -import {Button, Collection, Header, Heading, Menu, MenuItem, MenuSection, MenuTrigger, SubmenuTrigger} from '../src'; +import {Button, Collection, Header, Heading, Menu, MenuItem, MenuSection, MenuTrigger, Popover, SubmenuTrigger, UnavailableMenuItemTrigger} from '../src'; +import {pointerMap} from '@react-aria/test-utils'; import React from 'react'; import {render} from '@react-spectrum/test-utils-internal'; import {Selection} from '@react-types/shared'; +import userEvent from '@testing-library/user-event'; // better to accept items from the test? or just have the test have a requirement that you render a certain-ish structure? // what about the button label? @@ -56,6 +58,65 @@ function SelectionStatic(props) { ); } +describe('Menu unavailable', () => { + let user; + + beforeAll(() => { + user = userEvent.setup({delay: null, pointerMap}); + }); + + it('should open popover if isUnavailable is true', async () => { + let onAction = jest.fn(); + let {getByRole, getAllByRole, findByText} = render( + + + + + Delete + +
Contact your administrator for permissions to delete.
+
+
+
+
+ ); + + await user.click(getByRole('button')); + let items = getAllByRole('menuitem'); + expect(items[0]).toHaveAttribute('data-unavailable'); + await user.click(items[0]); + expect(await findByText('Contact your administrator for permissions to delete.')).toBeInTheDocument(); + expect(onAction).not.toHaveBeenCalled(); + + }); + + it('should not open popover when isUnavailable is false and item acts as normal', async () => { + let onAction = jest.fn(); + let {getByRole, getAllByRole, queryByText, queryAllByRole} = render( + + + + + Delete + +
Contact your administrator for permissions to delete.
+
+
+
+
+ ); + + await user.click(getByRole('button')); + let items = getAllByRole('menuitem'); + expect(items[0]).not.toHaveAttribute('data-unavailable'); + await user.click(items[0]); + expect(onAction).toHaveBeenCalled(); + let menus = queryAllByRole('dialog'); + expect(menus).toHaveLength(0); + expect(queryByText('Contact your administrator for permissions to delete.')).toBeNull(); + }); +}); + AriaMenuTests({ prefix: 'spectrum2-static', renderers: { diff --git a/packages/dev/s2-docs/pages/react-aria/Menu.mdx b/packages/dev/s2-docs/pages/react-aria/Menu.mdx index 44a1ed04df8..cf6d76c1b34 100644 --- a/packages/dev/s2-docs/pages/react-aria/Menu.mdx +++ b/packages/dev/s2-docs/pages/react-aria/Menu.mdx @@ -5,6 +5,7 @@ import docs from 'docs:react-aria-components'; import '../../tailwind/tailwind.css'; import Anatomy from 'react-aria-components/docs/MenuAnatomy.svg'; import {InlineAlert, Heading, Content} from '@react-spectrum/s2' +import {VersionBadge} from '../../src/VersionBadge'; export const tags = ['dropdown']; export const relatedPages = [ @@ -21,7 +22,8 @@ export const description = 'Displays a list of actions or options that a user ca ```tsx render type="vanilla" files={["starters/docs/src/Menu.tsx", "starters/docs/src/Menu.css"]} "use client"; import {MenuTrigger, SubmenuTrigger, Menu, MenuItem, MenuSection} from 'vanilla-starter/Menu'; - import {Separator, Text, Keyboard} from 'react-aria-components'; + import {Popover} from 'vanilla-starter/Popover'; + import {Separator, Text, Keyboard, UnavailableMenuItemTrigger} from 'react-aria-components'; import {Button} from 'vanilla-starter/Button'; import {Ellipsis, FolderOpen, Pencil, Copy, Trash, Share, Mail, Smartphone, Instagram} from 'lucide-react'; @@ -46,11 +48,17 @@ export const description = 'Displays a list of actions or options that a user ca Duplicate ⌘D - alert('delete')}> - - Delete… - ⌘⌫ - + + + + Delete… + + +
+ Contact your administrator for permissions to delete. +
+
+
@@ -84,8 +92,10 @@ export const description = 'Displays a list of actions or options that a user ca ```tsx render type="tailwind" files={["starters/tailwind/src/Menu.tsx"]} "use client"; import {MenuTrigger, SubmenuTrigger, Menu, MenuItem, MenuSection, MenuSeparator} from 'tailwind-starter/Menu'; + import {Popover} from 'tailwind-starter/Popover'; import {Button} from 'tailwind-starter/Button'; import {MoreHorizontal} from 'lucide-react'; + import {UnavailableMenuItemTrigger} from 'react-aria-components'; + alert(`Triggering ${action}`)}> + Favorite + + Delete + +
+ Contact your administrator for permissions to delete. +
+
+
+
+
+ ); +} +``` + ### Separators Separators may be added between menu items or sections in order to create non-labeled groupings. @@ -511,7 +559,7 @@ import {ChevronDown} from 'lucide-react'; -```tsx links={{MenuTrigger: '#menutrigger', Button: 'Button', Popover: 'Popover', Menu: '#menu', MenuItem: '#menuitem', Separator: 'Separator', MenuSection: '#menusection', SubmenuTrigger: '#submenutrigger', SelectionIndicator: 'selection#animated-selectionindicator'}} +```tsx links={{MenuTrigger: '#menutrigger', Button: 'Button', Popover: 'Popover', Menu: '#menu', MenuItem: '#menuitem', Separator: 'Separator', MenuSection: '#menusection', SubmenuTrigger: '#submenutrigger', UnavailableMenuItemTrigger: '#unavailablemenuitemtrigger', SelectionIndicator: 'selection#animated-selectionindicator'}} + + + Favorite + + Edit + +
+ Contact your administrator for permissions to edit this item. +
+
+
+ + Delete + +
+ Contact your administrator for permissions to delete this item. +
+
+
+ + Share + + + SMS + Email + + + +
+
+
+); diff --git a/packages/react-aria-components/test/Menu.test.tsx b/packages/react-aria-components/test/Menu.test.tsx index b46ac184faf..42d76526f86 100644 --- a/packages/react-aria-components/test/Menu.test.tsx +++ b/packages/react-aria-components/test/Menu.test.tsx @@ -12,7 +12,7 @@ import {act, fireEvent, mockClickDefault, pointerMap, render, within} from '@react-spectrum/test-utils-internal'; import {AriaMenuTests} from './AriaMenu.test-util'; -import {Button, Collection, Header, Heading, Input, Keyboard, Label, Menu, MenuContext, MenuItem, MenuSection, MenuTrigger, Popover, Pressable, Separator, SubmenuTrigger, Text, TextField} from '..'; +import {Button, Collection, Header, Heading, Input, Keyboard, Label, Menu, MenuContext, MenuItem, MenuSection, MenuTrigger, Popover, Pressable, Separator, SubmenuTrigger, Text, TextField, UnavailableMenuItemTrigger} from '..'; import React, {useState} from 'react'; import {Selection, SelectionMode} from '@react-types/shared'; import {UNSAFE_PortalProvider} from '@react-aria/overlays'; @@ -1737,6 +1737,76 @@ describe('Menu', () => { expect(onPress).toHaveBeenCalledTimes(1); expect(onClick).toHaveBeenCalledTimes(1); }); + + describe('unavailable', () => { + it('should open popover when isUnavailable is true and item is activated', async () => { + let onAction = jest.fn(); + let {getByRole, getAllByRole, findByText} = render( + + + + + + Delete + +
Contact your administrator for permissions to delete.
+
+
+
+
+
+ ); + + await user.click(getByRole('button')); + let items = getAllByRole('menuitem'); + expect(items[0]).toHaveAttribute('data-unavailable'); + expect(items[0]).not.toHaveAttribute('data-has-submenu'); + + await user.click(items[0]); + expect(await findByText('Contact your administrator for permissions to delete.')).toBeInTheDocument(); + expect(onAction).not.toHaveBeenCalled(); + + // Make sure the dialog behavior instead of submenu behavior is being applied here + let dialogs = getAllByRole('dialog'); + expect(document.activeElement).toBe(dialogs[1]); + await user.keyboard('{ArrowLeft}'); + expect(document.activeElement).toBe(dialogs[1]); + await user.keyboard('{Escape}'); + act(() => {jest.runAllTimers();}); + expect(document.activeElement).toBe(items[0]); + }); + + it('should not open popover when isUnavailable is false and item acts as normal', async () => { + let onAction = jest.fn(); + let {getByRole, getAllByRole, queryByText, queryAllByRole} = render( + + + + + + Delete + +
Contact your administrator for permissions to delete.
+
+
+
+
+
+ ); + + await user.click(getByRole('button')); + let items = getAllByRole('menuitem'); + expect(items[0]).not.toHaveAttribute('data-unavailable'); + expect(items[0]).not.toHaveAttribute('data-has-submenu'); + expect(items[0]).not.toHaveAttribute('aria-haspopup'); + await user.click(items[0]); + expect(onAction).toHaveBeenCalled(); + act(() => {jest.runAllTimers();}); + let menus = queryAllByRole('menu'); + expect(menus).toHaveLength(0); + expect(queryByText('Contact your administrator for permissions to delete.')).toBeNull(); + }); + }); }); // better to accept items from the test? or just have the test have a requirement that you render a certain-ish structure? diff --git a/starters/docs/src/Menu.css b/starters/docs/src/Menu.css index e2c867663fa..997cd4b11ac 100644 --- a/starters/docs/src/Menu.css +++ b/starters/docs/src/Menu.css @@ -46,7 +46,7 @@ forced-color-adjust: none; -webkit-tap-highlight-color: transparent; - > svg:not(.lucide-check, .lucide-dot, .lucide-chevron-right) { + > svg:not(.lucide-check, .lucide-dot, .lucide-chevron-right, .lucide-info) { grid-area: icon; width: var(--spacing-4); height: var(--spacing-4); @@ -127,7 +127,8 @@ border-color: rgb(255 255 255 / 0.2); } - .lucide-chevron-right { + .lucide-chevron-right, + &[data-unavailable] .lucide-info { grid-area: end; margin-left: var(--spacing-1); justify-self: end; diff --git a/starters/docs/src/Menu.tsx b/starters/docs/src/Menu.tsx index c66a45faad0..df9981b7540 100644 --- a/starters/docs/src/Menu.tsx +++ b/starters/docs/src/Menu.tsx @@ -1,5 +1,5 @@ 'use client'; -import {Check, ChevronRight, Dot} from 'lucide-react'; +import {Check, ChevronRight, Dot, Info} from 'lucide-react'; import { Menu as AriaMenu, MenuItem as AriaMenuItem, @@ -43,7 +43,7 @@ export function MenuItem(props: Omit & { children?: R return ( ( - {({ hasSubmenu, isSelected, selectionMode }) => ( + {({ hasSubmenu, isSelected, selectionMode, isUnavailable }) => ( <> {isSelected && selectionMode === 'multiple' ? : null} {isSelected && selectionMode === 'single' ? : null} @@ -51,6 +51,7 @@ export function MenuItem(props: Omit & { children?: R {hasSubmenu && ( )} + {isUnavailable && } )} diff --git a/starters/docs/src/Popover.css b/starters/docs/src/Popover.css index 31918bcb35c..3560c349273 100644 --- a/starters/docs/src/Popover.css +++ b/starters/docs/src/Popover.css @@ -18,6 +18,10 @@ padding: 0; } + &[data-trigger=UnavailableMenuItemTrigger] { + font-weight: 500; + } + .react-aria-OverlayArrow svg { display: block; fill: var(--background-color); diff --git a/starters/docs/src/Popover.tsx b/starters/docs/src/Popover.tsx index f3d6d341fb0..6c435a2926b 100644 --- a/starters/docs/src/Popover.tsx +++ b/starters/docs/src/Popover.tsx @@ -18,7 +18,7 @@ export function Popover({ children, hideArrow, ...props }: PopoverProps) { ( {({trigger}) => <> - {!hideArrow && trigger !== 'MenuTrigger' && trigger !== 'SubmenuTrigger' && ( + {!hideArrow && trigger !== 'MenuTrigger' && trigger !== 'SubmenuTrigger' && trigger !== 'UnavailableMenuItemTrigger' && ( diff --git a/starters/tailwind/src/Menu.tsx b/starters/tailwind/src/Menu.tsx index 01dfcd08b81..3121062ba2f 100644 --- a/starters/tailwind/src/Menu.tsx +++ b/starters/tailwind/src/Menu.tsx @@ -1,5 +1,5 @@ 'use client'; -import { Check, ChevronRight } from 'lucide-react'; +import { Check, ChevronRight, Info } from 'lucide-react'; import React from 'react'; import { Menu as AriaMenu, @@ -31,7 +31,7 @@ export function MenuItem(props: MenuItemProps) { let textValue = props.textValue || (typeof props.children === 'string' ? props.children : undefined); return ( - {composeRenderProps(props.children, (children, {selectionMode, isSelected, hasSubmenu}) => <> + {composeRenderProps(props.children, (children, {selectionMode, isSelected, hasSubmenu, isUnavailable}) => <> {selectionMode !== 'none' && ( {isSelected && } @@ -43,6 +43,9 @@ export function MenuItem(props: MenuItemProps) { {hasSubmenu && ( )} + {isUnavailable && ( + + )} )} );