1
0

dropdown-menu.tsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. import { Pressable, View } from 'app/design/view';
  2. import { useBottomSheetData } from 'app/context/bottomsheet';
  3. import { NeoButton, legacyToNeoButtonProps } from 'app/design/controls'
  4. import { isLegacyButtonProps, stripRoutingKeys } from 'app/design/controls/neo-button/legacy-button-map';
  5. import { memo, useCallback, useEffect, useMemo, useRef, useState, type ComponentType, type ReactNode } from 'react';
  6. import { cn, groupDropdownItems, getDropdownSectionsLayout, dropdownLeafItems, getDropdownGridLayout, type DropdownSection, type DropdownSectionsLayout } from 'app/lib/util';
  7. import DropdownGridItem, { type DropdownGridMenuItem } from 'app/ui/atoms/dropdown-grid-item';
  8. import { FeedbackHaptics } from 'app/lib/util';
  9. import { Keyboard, Alert, Platform, type AlertButton, type StyleProp, type ViewStyle } from 'react-native'
  10. import Redirect, { type RedirectHandle } from 'app/ui/atoms/redirect';
  11. import { appSetting } from 'app/lib/util';
  12. import { SafeMenuTrigger } from 'app/ui/atoms/safe-menu-trigger';
  13. import DropdownPopup, { DropdownMenuOpenContext, type DropdownButtonProps } from 'app/ui/atoms/dropdown-popup'
  14. import emitter, { EVENTS } from 'app/context/emitter';
  15. import { components } from 'app/components/registry'
  16. import { useOpenModalByUrl, useOpenModalWithContent } from 'app/context/jotai/modal';
  17. import { sanitazeUrl, openExternalLink } from 'app/lib/util';
  18. import { useWindowSize } from 'app/context/measure';
  19. import { useTranslation } from 'react-i18next';
  20. const menuSettings = appSetting('theme', 'dropdown_menu');
  21. /** UNA menu item. `group_header` / `separator` types structure sectioned menus. */
  22. export type DropdownMenuItemData = DropdownGridMenuItem & {
  23. link?: string;
  24. /** `modal` opens `content` (or the link) in a modal; `_blank` / `blank` opens externally. */
  25. target?: string;
  26. type?: 'group_header' | 'separator' | string;
  27. /** Modal body for `target: 'modal'` (see `resolveContent`). */
  28. content?: unknown;
  29. className?: string;
  30. };
  31. export type DropdownMenuVariant = 'vertical' | 'horizontal' | 'nopad' | 'grid' | 'tabs-overflow';
  32. export type DropdownMenuProps = {
  33. items: DropdownMenuItemData[];
  34. /** Handle selection yourself; otherwise items navigate / open modals. */
  35. onSelect?: (item: DropdownMenuItemData, event?: unknown) => void;
  36. /** Trigger content when `buttonProps` is omitted. */
  37. children?: ReactNode;
  38. defaultOpen?: boolean;
  39. /** Native: `popup` (anchored), `alert` (system alert), otherwise a bottom sheet. */
  40. mode?: 'popup' | 'alert' | string;
  41. /** Native alert title. */
  42. title?: string;
  43. variant?: DropdownMenuVariant;
  44. showOnTop?: boolean;
  45. /** Rendered above the items (popup only). */
  46. header?: ReactNode;
  47. /** Rendered under the items (popup only). */
  48. footer?: ReactNode;
  49. /** Native alert: add a Cancel option. */
  50. cancelable?: boolean;
  51. tabsOverflowSize?: 'sm' | 'md' | 'lg';
  52. openOnFocus?: boolean;
  53. /** Controlled open state (popup only). */
  54. open?: boolean;
  55. onOpenChange?: (open: boolean) => void;
  56. buttonProps?: DropdownButtonProps;
  57. contentClassName?: string;
  58. /** Turn an item's `content` into modal content. */
  59. resolveContent?: (content: unknown, item: DropdownMenuItemData) => ReactNode;
  60. triggerAccessibilityLabel?: string;
  61. triggerClassName?: string;
  62. triggerStyle?: StyleProp<ViewStyle>;
  63. /** Sectioned menus: one column instead of side-by-side sections. */
  64. stacked?: boolean;
  65. /** Popup min width (px) of a plain list menu; 256 by default. */
  66. minPopupWidth?: number;
  67. };
  68. type DropdownMenuPopupProps = Omit<DropdownMenuProps, 'mode' | 'title' | 'cancelable'>;
  69. type DropdownMenuNativeProps = Pick<DropdownMenuProps,
  70. 'items' | 'onSelect' | 'children' | 'defaultOpen' | 'mode' | 'title' | 'cancelable'
  71. | 'resolveContent' | 'buttonProps' | 'triggerAccessibilityLabel' | 'variant'>;
  72. type MenuBottomSheetProps = Pick<DropdownMenuProps, 'items' | 'onSelect' | 'resolveContent' | 'variant'> & {
  73. setBottomSheetData: (data: any) => void;
  74. };
  75. /** Menu item renderer from the component registry (JS, so typed loosely here). */
  76. const getDropdownMenuItem = (): ComponentType<any> => (components as any)['menu-item']['dropdown'];
  77. /** getDropdownSectionsLayout result, or the hand-built stacked column (no width limits). */
  78. type SectionsLayout = Omit<DropdownSectionsLayout, 'minWidth' | 'maxWidth' | 'gap'>
  79. & Partial<Pick<DropdownSectionsLayout, 'minWidth' | 'maxWidth' | 'gap'>>;
  80. export { DropdownMenuOpenContext };
  81. // Mirrors the web trigger logic in dropdown-popup.tsx: legacy buttonProps
  82. // (`legacyButton`, `useNeoButton: false`, or legacy keys such as `variant`)
  83. // go through `legacyToNeoButtonProps`; either way the trigger is a NeoButton.
  84. function isNavigableDropdownItem(item: DropdownMenuItemData | undefined, onSelect: DropdownMenuProps['onSelect']) {
  85. if (typeof onSelect === 'function') return false;
  86. if (!item || typeof item.link !== 'string' || !item.link) return false;
  87. if (item.target === 'modal') return false;
  88. if (item.type === 'group_header' || item.type === 'separator') return false;
  89. return true;
  90. }
  91. const variantClassMap: Record<string, Record<string, string>> = {
  92. vertical: { item: 'item_ver', container: 'content_ver' },
  93. horizontal: { item: 'item_hor', container: 'content_hor' },
  94. nopad: { item: 'item_np', container: 'content_ver' },
  95. grid: { item: 'item_grid', container: 'content_grid' },
  96. 'tabs-overflow': { container: 'content_ver' },
  97. };
  98. function resolveTabsOverflowClasses(tabsOverflowSize?: string) {
  99. const size =
  100. tabsOverflowSize === 'sm'
  101. ? 'sm'
  102. : tabsOverflowSize === 'lg'
  103. ? 'lg'
  104. : 'md';
  105. return {
  106. container: 'content_ver',
  107. item: `item_tabs_overflow_${size}`,
  108. item_row: 'justify-start items-center w-full',
  109. item_cnt_key: 'item_cnt_tabs_overflow',
  110. item_text_key: `item_tabs_overflow_text_${size}`,
  111. };
  112. }
  113. function DropdownMenuPopup({
  114. items,
  115. onSelect,
  116. children,
  117. defaultOpen,
  118. buttonProps,
  119. variant,
  120. showOnTop,
  121. header,
  122. footer,
  123. tabsOverflowSize,
  124. openOnFocus,
  125. open: openProp,
  126. onOpenChange: onOpenChangeProp,
  127. contentClassName: contentClassNameProp,
  128. resolveContent,
  129. triggerAccessibilityLabel,
  130. triggerClassName,
  131. triggerStyle,
  132. stacked = false,
  133. minPopupWidth = 256,
  134. }: DropdownMenuPopupProps) {
  135. const DropdownMenuItem = getDropdownMenuItem();
  136. const redirectdRef = useRef<RedirectHandle | null>(null);
  137. const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
  138. const isControlled = openProp !== undefined;
  139. const isOpen = isControlled ? openProp : uncontrolledOpen;
  140. const openModalByUrl = useOpenModalByUrl();
  141. const openModalWithContent = useOpenModalWithContent();
  142. const isGridLayout = variant === 'grid';
  143. const setIsOpen = useCallback(
  144. (next: boolean) => {
  145. if (!isControlled) {
  146. setUncontrolledOpen(next);
  147. }
  148. onOpenChangeProp?.(next);
  149. },
  150. [isControlled, onOpenChangeProp]
  151. );
  152. const classes =
  153. variant === 'tabs-overflow'
  154. ? resolveTabsOverflowClasses(tabsOverflowSize)
  155. : (variant && variantClassMap[variant]) ?? variantClassMap.vertical!;
  156. const { width: windowWidth } = useWindowSize();
  157. const gridItems = useMemo(
  158. () => (isGridLayout ? dropdownLeafItems(items) : null),
  159. [isGridLayout, items]
  160. );
  161. const gridLayout = useMemo(
  162. () => (isGridLayout ? getDropdownGridLayout() : null),
  163. [isGridLayout]
  164. );
  165. const useSectionsLayout = !isGridLayout && (items || []).some((item) => item?.type === 'group_header');
  166. const sections = useMemo(
  167. () => (useSectionsLayout ? groupDropdownItems(items) : null),
  168. [items, useSectionsLayout]
  169. );
  170. const sectionsLayout = useMemo<SectionsLayout>(
  171. () => stacked
  172. ? { stacked: true, cols: 1, colWidth: null, contentWidth: null, popupWidth: null }
  173. : getDropdownSectionsLayout(sections?.length || 0, windowWidth),
  174. [sections?.length, stacked, windowWidth]
  175. );
  176. const sectionsMinPopupWidth = sectionsLayout.stacked
  177. ? 288
  178. : sectionsLayout.popupWidth ?? undefined;
  179. const sectionCount = sections?.length || 0;
  180. const sectionsWrap = sectionCount > (sectionsLayout.cols || 1);
  181. // Web uses CSS grid props (`display: 'grid'`) that RN style types don't know.
  182. const sectionsRowStyle: any = sectionsLayout.stacked
  183. ? undefined
  184. : Platform.OS === 'web'
  185. ? {
  186. display: 'grid',
  187. gridTemplateColumns: `repeat(${sectionsLayout.cols}, ${sectionsLayout.colWidth}px)`,
  188. width: sectionsLayout.contentWidth,
  189. minWidth: sectionsLayout.contentWidth,
  190. }
  191. : {
  192. flexDirection: 'row',
  193. flexWrap: sectionsWrap ? 'wrap' : 'nowrap',
  194. width: sectionsLayout.contentWidth,
  195. };
  196. const sectionColStyle: any = sectionsLayout.stacked
  197. ? { width: '100%' }
  198. : {
  199. width: sectionsLayout.colWidth,
  200. minWidth: sectionsLayout.minWidth,
  201. maxWidth: sectionsLayout.maxWidth,
  202. flexGrow: 0,
  203. flexShrink: 0,
  204. flexBasis: sectionsLayout.colWidth,
  205. };
  206. const gridRowStyle: any = !isGridLayout
  207. ? undefined
  208. : Platform.OS === 'web'
  209. ? {
  210. display: 'grid',
  211. gridTemplateColumns: `repeat(${gridLayout!.cols}, minmax(0, 1fr))`,
  212. width: gridLayout!.contentWidth,
  213. minWidth: gridLayout!.contentWidth,
  214. gap: gridLayout!.gap,
  215. }
  216. : {
  217. flexDirection: 'row',
  218. flexWrap: 'wrap',
  219. width: '100%',
  220. };
  221. const gridCellStyle: any =
  222. isGridLayout && Platform.OS !== 'web'
  223. ? { width: `${100 / (gridLayout?.cols || 3)}%` }
  224. : undefined;
  225. const handleSelect = useCallback(
  226. (event: unknown, item: DropdownMenuItemData) => {
  227. setIsOpen(false);
  228. onSelect
  229. ? onSelect(item)
  230. : item.target == 'modal' ? (item.content ? openModalWithContent({
  231. title: item.title as ReactNode,
  232. content: resolveContent ? resolveContent(item.content, item) : item.content as ReactNode,
  233. }) : openModalByUrl(item.link)) : redirectdRef.current?.redirect('' + item.link);
  234. },
  235. [onSelect, setIsOpen, resolveContent, openModalWithContent, openModalByUrl]
  236. );
  237. useEffect(() => {
  238. const subscription = emitter.addListener(EVENTS.dynamicMenu, (data: { action?: string }) => {
  239. if (data.action == 'hide') {
  240. setIsOpen(false);
  241. }
  242. });
  243. return () => {
  244. subscription.remove();
  245. };
  246. }, [setIsOpen]);
  247. return (
  248. <>
  249. <Redirect ref={redirectdRef} />
  250. <DropdownPopup
  251. buttonProps={buttonProps}
  252. triggerAccessibilityLabel={triggerAccessibilityLabel}
  253. showOnTop={showOnTop}
  254. minPopupWidth={
  255. isGridLayout
  256. ? gridLayout!.popupWidth
  257. : useSectionsLayout
  258. ? sectionsMinPopupWidth
  259. : minPopupWidth
  260. }
  261. maxPopupWidth={
  262. isGridLayout
  263. ? gridLayout!.popupWidth
  264. : useSectionsLayout && !sectionsLayout.stacked
  265. ? sectionsMinPopupWidth
  266. : undefined
  267. }
  268. openOnFocus={
  269. openOnFocus ?? variant === 'tabs-overflow'
  270. }
  271. contentClassName={cn(
  272. variant === 'tabs-overflow' && 'overflow-visible p-2',
  273. contentClassNameProp
  274. )}
  275. open={isOpen}
  276. onOpenChange={setIsOpen}
  277. triggerClassName={triggerClassName}
  278. triggerStyle={triggerStyle}
  279. trigger={
  280. buttonProps ? undefined : (
  281. <SafeMenuTrigger>{children}</SafeMenuTrigger>
  282. )
  283. }
  284. >
  285. {header}
  286. {isGridLayout ? (
  287. <View className={menuSettings.content_grid} style={gridRowStyle}>
  288. {gridItems!.map((item: DropdownMenuItemData, index: number) => {
  289. const navigable = isNavigableDropdownItem(item, onSelect);
  290. return (
  291. <View key={item.id ?? index} style={gridCellStyle}>
  292. <DropdownGridItem
  293. index={index}
  294. item={item}
  295. handleSelect={navigable ? undefined : handleSelect}
  296. link={navigable ? item.link : undefined}
  297. className={item.className}
  298. />
  299. </View>
  300. );
  301. })}
  302. </View>
  303. ) : useSectionsLayout ? (
  304. <View
  305. className={
  306. sectionsLayout.stacked
  307. ? menuSettings.content_sections_stacked
  308. : menuSettings.content_sections
  309. }
  310. style={sectionsRowStyle}
  311. >
  312. {sections!.map((section: DropdownSection, sectionIndex: number) => (
  313. <View
  314. key={section.header?.id ?? `section-${sectionIndex}`}
  315. className={menuSettings.section}
  316. style={sectionColStyle}
  317. >
  318. {section.header ? (
  319. <DropdownMenuItem
  320. index={sectionIndex}
  321. item={{ ...section.header, first: true }}
  322. handleSelect={handleSelect}
  323. classes={classes}
  324. />
  325. ) : null}
  326. <View className={menuSettings[classes.container]}>
  327. {section.items.map((item: DropdownMenuItemData, index: number) => {
  328. const navigable = isNavigableDropdownItem(item, onSelect);
  329. return (
  330. <DropdownMenuItem
  331. key={item.id ?? `${sectionIndex}-${index}`}
  332. index={index}
  333. item={item}
  334. handleSelect={navigable ? undefined : handleSelect}
  335. link={navigable ? item.link : undefined}
  336. classes={classes}
  337. className={item.className}
  338. />
  339. );
  340. })}
  341. </View>
  342. </View>
  343. ))}
  344. </View>
  345. ) : (
  346. <View className={menuSettings[classes.container]}>
  347. {items.map((item, index) => {
  348. const navigable = isNavigableDropdownItem(item, onSelect);
  349. return (
  350. <DropdownMenuItem
  351. key={item.id ?? index}
  352. index={index}
  353. item={item}
  354. handleSelect={navigable ? undefined : handleSelect}
  355. link={navigable ? item.link : undefined}
  356. classes={classes}
  357. className={item.className}
  358. />
  359. );
  360. })}
  361. </View>
  362. )}
  363. {footer}
  364. </DropdownPopup>
  365. </>
  366. );
  367. }
  368. const MenuBottomSheet = memo(({ items, onSelect, setBottomSheetData, resolveContent, variant }: MenuBottomSheetProps) => {
  369. const openModalByUrl = useOpenModalByUrl();
  370. const openModalWithContent = useOpenModalWithContent();
  371. const DropdownMenuItem = getDropdownMenuItem();
  372. const redirectdRef = useRef<RedirectHandle | null>(null);
  373. const isGridLayout = variant === 'grid';
  374. const handlePressMenu = useCallback(
  375. (item: DropdownMenuItemData) => (event?: unknown) => {
  376. FeedbackHaptics('Medium');
  377. setBottomSheetData(false);
  378. if (onSelect) {
  379. onSelect(item, event);
  380. return;
  381. }
  382. if (item?.target === 'modal') {
  383. if (item?.content) {
  384. openModalWithContent({
  385. title: item.title as ReactNode,
  386. content: resolveContent ? resolveContent(item.content, item) : item.content as ReactNode
  387. });
  388. } else {
  389. openModalByUrl(item.link);
  390. }
  391. return;
  392. }
  393. if (item?.target === '_blank' || item?.target === 'blank') {
  394. const url = sanitazeUrl(item.link);
  395. if (!url) return;
  396. openExternalLink(url);
  397. return;
  398. }
  399. redirectdRef.current?.redirect('' + item.link);
  400. },
  401. [onSelect, setBottomSheetData, openModalByUrl, openModalWithContent, resolveContent]
  402. );
  403. const classes = isGridLayout ? variantClassMap.grid : variantClassMap.vertical;
  404. const sheetItems = isGridLayout ? dropdownLeafItems(items) : items;
  405. if (isGridLayout) {
  406. return (
  407. <View className="w-full mt-0 mb-2">
  408. <Redirect ref={redirectdRef} />
  409. <View className={`${menuSettings.content_grid} flex-row flex-wrap`}>
  410. {sheetItems.map((item, index) => (
  411. <View key={item.id ?? index} style={{ width: '33.333%' }}>
  412. <DropdownGridItem
  413. index={index}
  414. item={item}
  415. handleSelect={handlePressMenu(item)}
  416. className={item.className}
  417. />
  418. </View>
  419. ))}
  420. </View>
  421. </View>
  422. );
  423. }
  424. return (
  425. <View className='w-full mt-0 mb-2'>
  426. <Redirect ref={redirectdRef} />
  427. {sheetItems.map((item, index) => (
  428. <View key={item.id ?? index} className={`${item.className || ''} ${index !== sheetItems.length - 1 ? 'py-2 border-b border-border/60' : 'py-2'}`}>
  429. <DropdownMenuItem
  430. mode="bottomsheet"
  431. key={item.id ?? index}
  432. index={index}
  433. item={item}
  434. handleSelect={handlePressMenu(item)}
  435. classes={classes}
  436. />
  437. </View>
  438. ))}
  439. </View>
  440. );
  441. });
  442. function DropdownMenuNative({
  443. items,
  444. onSelect,
  445. children,
  446. defaultOpen,
  447. mode,
  448. title,
  449. cancelable = true,
  450. resolveContent,
  451. buttonProps,
  452. triggerAccessibilityLabel,
  453. variant,
  454. }: DropdownMenuNativeProps) {
  455. const { setBottomSheetData } = useBottomSheetData();
  456. const { t } = useTranslation();
  457. const handlePress = useCallback(() => {
  458. if (mode != "alert") {
  459. FeedbackHaptics('Medium')
  460. setBottomSheetData({
  461. showClose: false,
  462. snapPoints: variant === 'grid' ? ['25%', '70%'] : ['10%', '50%'],
  463. content: (
  464. <MenuBottomSheet
  465. variant={variant}
  466. resolveContent={resolveContent}
  467. items={items}
  468. onSelect={onSelect}
  469. setBottomSheetData={setBottomSheetData}
  470. />
  471. ),
  472. });
  473. Keyboard.dismiss();
  474. }
  475. else {
  476. const alertOptions: AlertButton[] = items.map(item => ({
  477. text: item.title as string,
  478. onPress: () => {
  479. onSelect?.(item);
  480. }
  481. }));
  482. if (cancelable) {
  483. alertOptions.push({
  484. text: t('Cancel'),
  485. style: "cancel"
  486. });
  487. }
  488. Alert.alert(
  489. title ?? '',
  490. undefined,
  491. alertOptions,
  492. { cancelable: cancelable }
  493. );
  494. }
  495. }, [cancelable, items, mode, onSelect, resolveContent, setBottomSheetData, t, title, variant]);
  496. useEffect(() => {
  497. if (defaultOpen)
  498. handlePress()
  499. }, [defaultOpen, handlePress]);
  500. if (buttonProps) {
  501. const neoButtonProps = (buttonProps.useNeoButton === false || isLegacyButtonProps(buttonProps))
  502. ? legacyToNeoButtonProps(buttonProps)
  503. : stripRoutingKeys(buttonProps);
  504. // `handlePress` fires its own haptics.
  505. return <NeoButton {...neoButtonProps} haptics={false} onPress={handlePress} />;
  506. }
  507. return (
  508. <Pressable
  509. onPress={handlePress}
  510. accessibilityRole="button"
  511. accessibilityLabel={triggerAccessibilityLabel}
  512. >
  513. {children}
  514. </Pressable>
  515. );
  516. }
  517. export default function DropdownMenu({
  518. items,
  519. onSelect,
  520. children,
  521. defaultOpen,
  522. mode,
  523. title,
  524. variant,
  525. showOnTop,
  526. header,
  527. footer,
  528. cancelable = true,
  529. tabsOverflowSize,
  530. openOnFocus,
  531. open,
  532. buttonProps,
  533. onOpenChange,
  534. contentClassName,
  535. resolveContent,
  536. triggerAccessibilityLabel,
  537. triggerClassName,
  538. triggerStyle,
  539. stacked = false,
  540. minPopupWidth,
  541. }: DropdownMenuProps) {
  542. const isWeb = Platform.OS === 'web';
  543. if (isWeb || mode == 'popup') {
  544. return (
  545. <DropdownMenuPopup
  546. showOnTop={showOnTop}
  547. items={items}
  548. onSelect={onSelect}
  549. children={children}
  550. defaultOpen={defaultOpen}
  551. header={header}
  552. footer={footer}
  553. variant={variant}
  554. tabsOverflowSize={tabsOverflowSize}
  555. openOnFocus={openOnFocus}
  556. open={open}
  557. onOpenChange={onOpenChange}
  558. contentClassName={contentClassName}
  559. resolveContent={resolveContent}
  560. buttonProps={buttonProps}
  561. triggerAccessibilityLabel={triggerAccessibilityLabel}
  562. triggerClassName={triggerClassName}
  563. triggerStyle={triggerStyle}
  564. stacked={stacked}
  565. minPopupWidth={minPopupWidth}
  566. />
  567. );
  568. }
  569. return (
  570. <DropdownMenuNative
  571. items={items}
  572. onSelect={onSelect}
  573. children={children}
  574. defaultOpen={defaultOpen}
  575. mode={mode}
  576. title={title}
  577. cancelable={cancelable}
  578. resolveContent={resolveContent}
  579. buttonProps={buttonProps}
  580. triggerAccessibilityLabel={triggerAccessibilityLabel}
  581. variant={variant}
  582. />
  583. );
  584. }