@openmrs/esm-styleguide 8.0.1-pre.3758 → 8.0.1-pre.3762

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,6 @@
13
13
  [0] │ You can limit the size of your bundles by using import() to lazy load some parts of your application.
14
14
  [0] │ For more info visit https://www.rspack.dev/guide/optimization/code-splitting
15
15
  [0]
16
- [0] Rspack compiled with 3 warnings in 9.66 s
16
+ [0] Rspack compiled with 3 warnings in 9.39 s
17
17
  [0] rspack --mode=production exited with code 0
18
18
  [1] tsc --project tsconfig.build.json exited with code 0
@@ -9,6 +9,12 @@ interface CustomOverflowMenuProps {
9
9
  menuTitle: React.ReactNode;
10
10
  children: React.ReactNode;
11
11
  }
12
+ /**
13
+ * A custom overflow menu that supports a text/icon trigger button instead of
14
+ * Carbon's icon-only OverflowMenu trigger. Uses CSS-based show/hide rather
15
+ * than Carbon's FloatingMenu portal, so keyboard behavior (Escape, arrow keys,
16
+ * auto-focus) is implemented here to match the WAI-ARIA menu button pattern.
17
+ */
12
18
  export declare function CustomOverflowMenu({ menuTitle, children }: CustomOverflowMenuProps): React.JSX.Element;
13
19
  type OverflowMenuItemProps = ComponentProps<typeof OverflowMenuItem>;
14
20
  export declare function CustomOverflowMenuItem(props: Omit<OverflowMenuItemProps, 'closeMenu'>): React.JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"custom-overflow-menu.component.d.ts","sourceRoot":"","sources":["../../src/custom-overflow-menu/custom-overflow-menu.component.tsx"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,OAAO,KAAK,EAAE,EAAmD,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AAEpG,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAIjD,UAAU,8BAA8B;IACtC,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAID,wBAAgB,qBAAqB,mCAMpC;AAED,UAAU,uBAAuB;IAC/B,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,uBAAuB,qBA6ClF;AAED,KAAK,qBAAqB,GAAG,cAAc,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAErE,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,IAAI,CAAC,qBAAqB,EAAE,WAAW,CAAC,qBAGrF"}
1
+ {"version":3,"file":"custom-overflow-menu.component.d.ts","sourceRoot":"","sources":["../../src/custom-overflow-menu/custom-overflow-menu.component.tsx"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,OAAO,KAAK,EAAE,EAWZ,KAAK,cAAc,EACpB,MAAM,OAAO,CAAC;AAEf,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAIjD,UAAU,8BAA8B;IACtC,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAID,wBAAgB,qBAAqB,mCAMpC;AAED,UAAU,uBAAuB;IAC/B,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,uBAAuB,qBAiHlF;AAED,KAAK,qBAAqB,GAAG,cAAc,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAErE,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,IAAI,CAAC,qBAAqB,EAAE,WAAW,CAAC,qBAGrF"}
@@ -10,5 +10,11 @@ export interface PatientBannerActionsMenuProps {
10
10
  */
11
11
  additionalActionsSlotState?: object;
12
12
  }
13
- export declare function PatientBannerActionsMenu({ patient, patientUuid, actionsSlotName, additionalActionsSlotState, }: PatientBannerActionsMenuProps): React.JSX.Element;
13
+ /**
14
+ * Overflow menu for the patient banner whose items come from an ExtensionSlot
15
+ * rather than direct React children. Because cloneElement cannot inject props
16
+ * into extension-rendered components, arrow key navigation is handled at the
17
+ * container level via onKeyDown instead of delegating to Carbon's OverflowMenuItem.
18
+ */
19
+ export declare function PatientBannerActionsMenu({ patient, patientUuid, actionsSlotName, additionalActionsSlotState, }: PatientBannerActionsMenuProps): React.JSX.Element | null;
14
20
  //# sourceMappingURL=patient-banner-actions-menu.component.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"patient-banner-actions-menu.component.d.ts","sourceRoot":"","sources":["../../../src/patient-banner/actions-menu/patient-banner-actions-menu.component.tsx"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,OAAO,KAAyC,MAAM,OAAO,CAAC;AAQ9D,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAED,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,WAAW,EACX,eAAe,EACf,0BAA0B,GAC3B,EAAE,6BAA6B,qBAqE/B"}
1
+ {"version":3,"file":"patient-banner-actions-menu.component.d.ts","sourceRoot":"","sources":["../../../src/patient-banner/actions-menu/patient-banner-actions-menu.component.tsx"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,OAAO,KAAmE,MAAM,OAAO,CAAC;AAQxF,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,EACvC,OAAO,EACP,WAAW,EACX,eAAe,EACf,0BAA0B,GAC3B,EAAE,6BAA6B,4BA0H/B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-styleguide",
3
- "version": "8.0.1-pre.3758",
3
+ "version": "8.0.1-pre.3762",
4
4
  "license": "MPL-2.0",
5
5
  "description": "The styleguide for OpenMRS SPA",
6
6
  "main": "dist/openmrs-esm-styleguide.js",
@@ -98,17 +98,17 @@
98
98
  "swr": "2.x"
99
99
  },
100
100
  "devDependencies": {
101
- "@openmrs/esm-api": "8.0.1-pre.3758",
102
- "@openmrs/esm-config": "8.0.1-pre.3758",
103
- "@openmrs/esm-emr-api": "8.0.1-pre.3758",
104
- "@openmrs/esm-error-handling": "8.0.1-pre.3758",
105
- "@openmrs/esm-extensions": "8.0.1-pre.3758",
106
- "@openmrs/esm-globals": "8.0.1-pre.3758",
107
- "@openmrs/esm-navigation": "8.0.1-pre.3758",
108
- "@openmrs/esm-react-utils": "8.0.1-pre.3758",
109
- "@openmrs/esm-state": "8.0.1-pre.3758",
110
- "@openmrs/esm-translations": "8.0.1-pre.3758",
111
- "@openmrs/esm-utils": "8.0.1-pre.3758",
101
+ "@openmrs/esm-api": "8.0.1-pre.3762",
102
+ "@openmrs/esm-config": "8.0.1-pre.3762",
103
+ "@openmrs/esm-emr-api": "8.0.1-pre.3762",
104
+ "@openmrs/esm-error-handling": "8.0.1-pre.3762",
105
+ "@openmrs/esm-extensions": "8.0.1-pre.3762",
106
+ "@openmrs/esm-globals": "8.0.1-pre.3762",
107
+ "@openmrs/esm-navigation": "8.0.1-pre.3762",
108
+ "@openmrs/esm-react-utils": "8.0.1-pre.3762",
109
+ "@openmrs/esm-state": "8.0.1-pre.3762",
110
+ "@openmrs/esm-translations": "8.0.1-pre.3762",
111
+ "@openmrs/esm-utils": "8.0.1-pre.3762",
112
112
  "@rspack/cli": "^1.3.11",
113
113
  "@rspack/core": "^1.3.11",
114
114
  "@types/geopattern": "^1.2.9",
@@ -1,5 +1,17 @@
1
1
  /** @module @category UI */
2
- import React, { createContext, useCallback, useContext, useMemo, type ComponentProps } from 'react';
2
+ import React, {
3
+ Children,
4
+ cloneElement,
5
+ createContext,
6
+ isValidElement,
7
+ useCallback,
8
+ useContext,
9
+ useEffect,
10
+ useId,
11
+ useMemo,
12
+ useRef,
13
+ type ComponentProps,
14
+ } from 'react';
3
15
  import classNames from 'classnames';
4
16
  import { OverflowMenuItem } from '@carbon/react';
5
17
  import { useLayoutType, useOnClickOutside } from '@openmrs/esm-react-utils';
@@ -24,13 +36,81 @@ interface CustomOverflowMenuProps {
24
36
  children: React.ReactNode;
25
37
  }
26
38
 
39
+ /**
40
+ * A custom overflow menu that supports a text/icon trigger button instead of
41
+ * Carbon's icon-only OverflowMenu trigger. Uses CSS-based show/hide rather
42
+ * than Carbon's FloatingMenu portal, so keyboard behavior (Escape, arrow keys,
43
+ * auto-focus) is implemented here to match the WAI-ARIA menu button pattern.
44
+ */
27
45
  export function CustomOverflowMenu({ menuTitle, children }: CustomOverflowMenuProps) {
28
46
  const [menuIsOpen, setMenuIsOpen] = React.useState(false);
29
47
  const ref = useOnClickOutside<HTMLDivElement>(() => setMenuIsOpen(false), menuIsOpen);
30
48
  const isTablet = useLayoutType() === 'tablet';
31
49
  const toggleShowMenu = useCallback(() => setMenuIsOpen((state) => !state), []);
32
- const closeMenu = useCallback(() => setMenuIsOpen(false), []);
33
- const contextValue = useMemo(() => ({ closeMenu }), [closeMenu]);
50
+ const triggerRef = useRef<HTMLButtonElement>(null);
51
+ const menuRef = useRef<HTMLDivElement>(null);
52
+ const uniqueId = useId();
53
+ const triggerId = `custom-overflow-menu-trigger-${uniqueId}`;
54
+ const menuId = `custom-overflow-menu-${uniqueId}`;
55
+
56
+ const closeMenuAndFocusTrigger = useCallback(() => {
57
+ setMenuIsOpen(false);
58
+ triggerRef.current?.focus();
59
+ }, []);
60
+
61
+ const contextValue = useMemo(() => ({ closeMenu: closeMenuAndFocusTrigger }), [closeMenuAndFocusTrigger]);
62
+
63
+ const handleEscapeKey = useCallback(
64
+ (e: React.KeyboardEvent) => {
65
+ if (e.key === 'Escape' && menuIsOpen) {
66
+ e.stopPropagation();
67
+ closeMenuAndFocusTrigger();
68
+ }
69
+ },
70
+ [closeMenuAndFocusTrigger, menuIsOpen],
71
+ );
72
+
73
+ const childArray = Children.toArray(children);
74
+
75
+ useEffect(() => {
76
+ if (menuIsOpen && menuRef.current) {
77
+ const firstItem = menuRef.current.querySelector<HTMLElement>('[role="menuitem"]:not([disabled])');
78
+ firstItem?.focus();
79
+ }
80
+ }, [menuIsOpen]);
81
+
82
+ const handleOverflowMenuItemFocus = useCallback(
83
+ ({ currentIndex, direction }: { currentIndex?: number; direction: number }) => {
84
+ const enabledItems = menuRef.current?.querySelectorAll<HTMLElement>('[role="menuitem"]:not([disabled])');
85
+ if (!enabledItems?.length) {
86
+ return;
87
+ }
88
+
89
+ const activeItem =
90
+ (document.activeElement?.closest?.('[role="menuitem"]') as HTMLElement) ?? document.activeElement;
91
+ const currentPos = Array.from(enabledItems).indexOf(activeItem as HTMLElement);
92
+ if (currentPos === -1) {
93
+ enabledItems[direction > 0 ? 0 : enabledItems.length - 1]?.focus();
94
+ return;
95
+ }
96
+
97
+ const nextPos = currentPos + direction;
98
+ const wrappedPos = nextPos < 0 ? enabledItems.length - 1 : nextPos >= enabledItems.length ? 0 : nextPos;
99
+ enabledItems[wrappedPos]?.focus();
100
+ },
101
+ [],
102
+ );
103
+
104
+ const enrichedChildren = childArray.map((child, index) => {
105
+ if (isValidElement(child)) {
106
+ return cloneElement(child as React.ReactElement<any>, {
107
+ closeMenu: closeMenuAndFocusTrigger,
108
+ handleOverflowMenuItemFocus,
109
+ index,
110
+ });
111
+ }
112
+ return child;
113
+ });
34
114
 
35
115
  return (
36
116
  <div data-overflow-menu className={classNames('cds--overflow-menu', styles.container)} ref={ref}>
@@ -42,11 +122,13 @@ export function CustomOverflowMenu({ menuTitle, children }: CustomOverflowMenuPr
42
122
  { 'cds--overflow-menu--open': menuIsOpen },
43
123
  styles.overflowMenuButton,
44
124
  )}
45
- aria-haspopup="true"
125
+ aria-controls={menuId}
46
126
  aria-expanded={menuIsOpen}
47
- id="custom-actions-overflow-menu-trigger"
48
- aria-controls="custom-actions-overflow-menu"
127
+ aria-haspopup="true"
128
+ id={triggerId}
49
129
  onClick={toggleShowMenu}
130
+ onKeyDown={handleEscapeKey}
131
+ ref={triggerRef}
50
132
  >
51
133
  {menuTitle}
52
134
  </button>
@@ -54,16 +136,20 @@ export function CustomOverflowMenu({ menuTitle, children }: CustomOverflowMenuPr
54
136
  className={classNames('cds--overflow-menu-options', 'cds--overflow-menu--flip', styles.menu, {
55
137
  [styles.show]: menuIsOpen,
56
138
  })}
57
- tabIndex={0}
139
+ aria-labelledby={triggerId}
58
140
  data-floating-menu-direction="bottom"
141
+ id={menuId}
142
+ onKeyDown={handleEscapeKey}
143
+ ref={menuRef}
59
144
  role="menu"
60
- aria-labelledby="custom-actions-overflow-menu-trigger"
61
- id="custom-actions-overflow-menu"
145
+ tabIndex={-1}
62
146
  >
63
147
  <ul
64
148
  className={classNames('cds--overflow-menu-options__content', { 'cds--overflow-menu-options--lg': isTablet })}
65
149
  >
66
- <CustomOverflowMenuContext.Provider value={contextValue}>{children}</CustomOverflowMenuContext.Provider>
150
+ <CustomOverflowMenuContext.Provider value={contextValue}>
151
+ {enrichedChildren}
152
+ </CustomOverflowMenuContext.Provider>
67
153
  </ul>
68
154
  <span />
69
155
  </div>
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { beforeEach, describe, expect, it, vi } from 'vitest';
3
3
  import '@testing-library/jest-dom/vitest';
4
4
  import userEvent from '@testing-library/user-event';
5
- import { render, screen } from '@testing-library/react';
5
+ import { render, screen, waitFor } from '@testing-library/react';
6
6
  import { useLayoutType } from '@openmrs/esm-react-utils';
7
7
  import { CustomOverflowMenu, CustomOverflowMenuItem, useCustomOverflowMenu } from './custom-overflow-menu.component';
8
8
 
@@ -79,8 +79,61 @@ describe('CustomOverflowMenu', () => {
79
79
 
80
80
  expect(trigger).toHaveAttribute('aria-haspopup', 'true');
81
81
  expect(trigger).toHaveAttribute('aria-expanded', 'false');
82
- expect(trigger).toHaveAttribute('aria-controls', 'custom-actions-overflow-menu');
83
- expect(menu).toHaveAttribute('aria-labelledby', 'custom-actions-overflow-menu-trigger');
82
+
83
+ const menuId = trigger.getAttribute('aria-controls');
84
+ expect(menuId).toBeTruthy();
85
+ expect(menu).toHaveAttribute('id', menuId);
86
+ expect(menu).toHaveAttribute('aria-labelledby', trigger.id);
87
+ });
88
+
89
+ it('should generate unique IDs for multiple instances', () => {
90
+ render(
91
+ <>
92
+ <CustomOverflowMenu menuTitle="Menu A">
93
+ <li>Option 1</li>
94
+ </CustomOverflowMenu>
95
+ <CustomOverflowMenu menuTitle="Menu B">
96
+ <li>Option 2</li>
97
+ </CustomOverflowMenu>
98
+ </>,
99
+ );
100
+
101
+ const buttons = screen.getAllByRole('button');
102
+ expect(buttons[0].id).not.toBe(buttons[1].id);
103
+ });
104
+
105
+ it('should close menu and return focus to trigger on Escape key', async () => {
106
+ const user = userEvent.setup();
107
+
108
+ render(
109
+ <CustomOverflowMenu menuTitle="Menu">
110
+ <CustomOverflowMenuItem itemText="Option 1" />
111
+ </CustomOverflowMenu>,
112
+ );
113
+
114
+ const trigger = screen.getByRole('button', { name: /menu/i });
115
+ await user.click(trigger);
116
+ expect(trigger).toHaveAttribute('aria-expanded', 'true');
117
+
118
+ await user.keyboard('{Escape}');
119
+ expect(trigger).toHaveAttribute('aria-expanded', 'false');
120
+ expect(trigger).toHaveFocus();
121
+ });
122
+
123
+ it('should focus the first enabled menu item when opened', async () => {
124
+ const user = userEvent.setup();
125
+
126
+ render(
127
+ <CustomOverflowMenu menuTitle="Menu">
128
+ <CustomOverflowMenuItem itemText="Disabled Item" disabled />
129
+ <CustomOverflowMenuItem itemText="Enabled Item" />
130
+ </CustomOverflowMenu>,
131
+ );
132
+
133
+ await user.click(screen.getByRole('button', { name: /menu/i }));
134
+
135
+ const menuItems = screen.getAllByRole('menuitem');
136
+ await waitFor(() => expect(menuItems[1]).toHaveFocus());
84
137
  });
85
138
 
86
139
  it('should have correct menu positioning attributes', () => {
@@ -1,5 +1,5 @@
1
1
  /** @module @category UI */
2
- import React, { useCallback, useMemo, useState } from 'react';
2
+ import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
3
3
  import classNames from 'classnames';
4
4
  import { OverflowMenuVertical } from '@carbon/react/icons';
5
5
  import { ExtensionSlot, useExtensionSlot, useLayoutType, useOnClickOutside } from '@openmrs/esm-react-utils';
@@ -18,6 +18,12 @@ export interface PatientBannerActionsMenuProps {
18
18
  additionalActionsSlotState?: object;
19
19
  }
20
20
 
21
+ /**
22
+ * Overflow menu for the patient banner whose items come from an ExtensionSlot
23
+ * rather than direct React children. Because cloneElement cannot inject props
24
+ * into extension-rendered components, arrow key navigation is handled at the
25
+ * container level via onKeyDown instead of delegating to Carbon's OverflowMenuItem.
26
+ */
21
27
  export function PatientBannerActionsMenu({
22
28
  patient,
23
29
  patientUuid,
@@ -28,68 +34,121 @@ export function PatientBannerActionsMenu({
28
34
  const { extensions: patientActions } = useExtensionSlot(actionsSlotName);
29
35
  const isTablet = useLayoutType() === 'tablet';
30
36
  const ref = useOnClickOutside<HTMLDivElement>(() => setMenuIsOpen(false), menuIsOpen);
37
+ const triggerRef = useRef<HTMLButtonElement>(null);
38
+ const menuRef = useRef<HTMLDivElement>(null);
39
+ const uniqueId = useId();
40
+ const triggerId = `patient-actions-menu-trigger-${uniqueId}`;
41
+ const menuId = `patient-actions-menu-${uniqueId}`;
31
42
 
32
43
  const toggleShowMenu = useCallback(() => setMenuIsOpen((state) => !state), []);
33
- const closeMenu = useCallback(() => setMenuIsOpen(false), []);
44
+
45
+ const closeMenuAndFocusTrigger = useCallback(() => {
46
+ setMenuIsOpen(false);
47
+ triggerRef.current?.focus();
48
+ }, []);
49
+
50
+ const handleMenuKeyDown = useCallback(
51
+ (e: React.KeyboardEvent) => {
52
+ if (e.key === 'Escape' && menuIsOpen) {
53
+ e.stopPropagation();
54
+ closeMenuAndFocusTrigger();
55
+ return;
56
+ }
57
+
58
+ if ((e.key === 'ArrowDown' || e.key === 'ArrowUp') && menuIsOpen) {
59
+ e.preventDefault();
60
+ const enabledItems = menuRef.current?.querySelectorAll<HTMLElement>('[role="menuitem"]:not([disabled])');
61
+ if (!enabledItems?.length) {
62
+ return;
63
+ }
64
+
65
+ const activeItem =
66
+ (document.activeElement?.closest?.('[role="menuitem"]') as HTMLElement) ?? document.activeElement;
67
+ const currentPos = Array.from(enabledItems).indexOf(activeItem as HTMLElement);
68
+
69
+ if (currentPos === -1) {
70
+ enabledItems[e.key === 'ArrowDown' ? 0 : enabledItems.length - 1]?.focus();
71
+ return;
72
+ }
73
+
74
+ const direction = e.key === 'ArrowDown' ? 1 : -1;
75
+ const nextPos = currentPos + direction;
76
+ const wrappedPos = nextPos < 0 ? enabledItems.length - 1 : nextPos >= enabledItems.length ? 0 : nextPos;
77
+ enabledItems[wrappedPos]?.focus();
78
+ }
79
+ },
80
+ [closeMenuAndFocusTrigger, menuIsOpen],
81
+ );
82
+
83
+ useEffect(() => {
84
+ if (menuIsOpen && menuRef.current) {
85
+ const firstItem = menuRef.current.querySelector<HTMLElement>('[role="menuitem"]:not([disabled])');
86
+ firstItem?.focus();
87
+ }
88
+ }, [menuIsOpen]);
34
89
 
35
90
  const patientActionsSlotState = useMemo(
36
- () => ({ patientUuid, patient, closeMenu, ...additionalActionsSlotState }),
37
- [patientUuid, patient, closeMenu, additionalActionsSlotState],
91
+ () => ({ patientUuid, patient, closeMenu: closeMenuAndFocusTrigger, ...additionalActionsSlotState }),
92
+ [patientUuid, patient, closeMenuAndFocusTrigger, additionalActionsSlotState],
38
93
  );
39
94
 
95
+ if (patientActions.length === 0) {
96
+ return null;
97
+ }
98
+
40
99
  return (
41
- <>
42
- {patientActions.length > 0 ? (
43
- <div className={styles.overflowMenuContainer}>
44
- <div
45
- data-overflow-menu
46
- className={classNames('cds--overflow-menu', customOverflowMenuStyles.container)}
47
- ref={ref}
100
+ <div className={styles.overflowMenuContainer}>
101
+ <div
102
+ data-overflow-menu
103
+ className={classNames('cds--overflow-menu', customOverflowMenuStyles.container)}
104
+ ref={ref}
105
+ >
106
+ <button
107
+ className={classNames(
108
+ 'cds--btn',
109
+ 'cds--btn--ghost',
110
+ 'cds--overflow-menu__trigger',
111
+ { 'cds--overflow-menu--open': menuIsOpen },
112
+ customOverflowMenuStyles.overflowMenuButton,
113
+ )}
114
+ aria-controls={menuId}
115
+ aria-expanded={menuIsOpen}
116
+ aria-haspopup="true"
117
+ id={triggerId}
118
+ onClick={toggleShowMenu}
119
+ onKeyDown={handleMenuKeyDown}
120
+ ref={triggerRef}
121
+ >
122
+ <span className={styles.actionsButtonText}>{getCoreTranslation('actions', 'Actions')}</span>{' '}
123
+ <OverflowMenuVertical size={16} style={{ marginLeft: '0.5rem', fill: '#78A9FF' }} />
124
+ </button>
125
+ <div
126
+ className={classNames(
127
+ 'cds--overflow-menu-options',
128
+ 'cds--overflow-menu--flip',
129
+ customOverflowMenuStyles.menu,
130
+ {
131
+ [customOverflowMenuStyles.show]: menuIsOpen,
132
+ },
133
+ )}
134
+ aria-labelledby={triggerId}
135
+ data-floating-menu-direction="bottom"
136
+ id={menuId}
137
+ onKeyDown={handleMenuKeyDown}
138
+ ref={menuRef}
139
+ role="menu"
140
+ tabIndex={-1}
141
+ >
142
+ <ul
143
+ className={classNames('cds--overflow-menu-options__content', {
144
+ 'cds--overflow-menu-options--lg': isTablet,
145
+ })}
48
146
  >
49
- <button
50
- className={classNames(
51
- 'cds--btn',
52
- 'cds--btn--ghost',
53
- 'cds--overflow-menu__trigger',
54
- { 'cds--overflow-menu--open': menuIsOpen },
55
- customOverflowMenuStyles.overflowMenuButton,
56
- )}
57
- aria-haspopup="true"
58
- aria-expanded={menuIsOpen}
59
- id="custom-actions-overflow-menu-trigger"
60
- aria-controls="custom-actions-overflow-menu"
61
- onClick={toggleShowMenu}
62
- >
63
- <span className={styles.actionsButtonText}>{getCoreTranslation('actions', 'Actions')}</span>{' '}
64
- <OverflowMenuVertical size={16} style={{ marginLeft: '0.5rem', fill: '#78A9FF' }} />
65
- </button>
66
- <div
67
- className={classNames(
68
- 'cds--overflow-menu-options',
69
- 'cds--overflow-menu--flip',
70
- customOverflowMenuStyles.menu,
71
- {
72
- [customOverflowMenuStyles.show]: menuIsOpen,
73
- },
74
- )}
75
- tabIndex={0}
76
- data-floating-menu-direction="bottom"
77
- role="menu"
78
- aria-labelledby="custom-actions-overflow-menu-trigger"
79
- id="custom-actions-overflow-menu"
80
- >
81
- <ul
82
- className={classNames('cds--overflow-menu-options__content', {
83
- 'cds--overflow-menu-options--lg': isTablet,
84
- })}
85
- >
86
- <ExtensionSlot name={actionsSlotName} key={actionsSlotName} state={patientActionsSlotState} />
87
- </ul>
88
- <span />
89
- </div>
90
- </div>
147
+ <ExtensionSlot name={actionsSlotName} key={actionsSlotName} state={patientActionsSlotState} />
148
+ </ul>
149
+ <span />
91
150
  </div>
92
- ) : null}
93
- </>
151
+ </div>
152
+ </div>
94
153
  );
95
154
  }