diff --git a/src/lib/types/react-augment.d.ts b/src/lib/types/react-augment.d.ts new file mode 100644 index 000000000..887fdc93e --- /dev/null +++ b/src/lib/types/react-augment.d.ts @@ -0,0 +1,7 @@ +import React from 'react'; + +declare module 'react' { + function forwardRef( + render: (props: P, ref: React.ForwardedRef) => React.ReactElement | null, + ): (props: P & RefAttributes) => React.ReactElement | null; +} diff --git a/src/lib/viewers/controls/settings/SettingsCheckboxItem.tsx b/src/lib/viewers/controls/settings/SettingsCheckboxItem.tsx index 5a4e6588b..82d062ac1 100644 --- a/src/lib/viewers/controls/settings/SettingsCheckboxItem.tsx +++ b/src/lib/viewers/controls/settings/SettingsCheckboxItem.tsx @@ -8,7 +8,10 @@ export type Props = { onChange: (isChecked: boolean) => void; }; -export default function SettingsCheckboxItem({ isChecked, label, onChange }: Props): JSX.Element { +export type Ref = HTMLInputElement; + +function SettingsCheckboxItem(props: Props, ref: React.Ref): JSX.Element { + const { isChecked, label, onChange } = props; const { current: id } = React.useRef(uniqueId('bp-SettingsCheckboxItem_')); const handleChange = (event: React.ChangeEvent): void => { @@ -18,6 +21,7 @@ export default function SettingsCheckboxItem({ isChecked, label, onChange }: Pro return (
); } + +export default React.forwardRef(SettingsCheckboxItem); diff --git a/src/lib/viewers/controls/settings/SettingsDropdown.scss b/src/lib/viewers/controls/settings/SettingsDropdown.scss index 4b81c45f3..a0a1e8519 100644 --- a/src/lib/viewers/controls/settings/SettingsDropdown.scss +++ b/src/lib/viewers/controls/settings/SettingsDropdown.scss @@ -3,13 +3,19 @@ $bp-SettingsDropdown-spacing: 5px; .bp-SettingsDropdown { - position: relative; display: flex; flex: 1 1 auto; flex-direction: column; color: $bdl-gray-62; } +.bp-SettingsDropdown-content { + position: relative; + display: flex; + flex: 1 1 auto; + flex-direction: column; +} + .bp-SettingsDropdown-flyout { top: auto; right: 0; diff --git a/src/lib/viewers/controls/settings/SettingsDropdown.tsx b/src/lib/viewers/controls/settings/SettingsDropdown.tsx index 7593b3726..e487db180 100644 --- a/src/lib/viewers/controls/settings/SettingsDropdown.tsx +++ b/src/lib/viewers/controls/settings/SettingsDropdown.tsx @@ -22,17 +22,13 @@ export type Props = { value?: V; }; -export default function SettingsDropdown({ - className, - label, - listItems, - onSelect, - value, -}: Props): JSX.Element { +export type Ref = HTMLButtonElement | null; + +function SettingsDropdown(props: Props, ref: React.Ref): JSX.Element { + const { className, label, listItems, onSelect, value } = props; const { current: id } = React.useRef(uniqueId('bp-SettingsDropdown_')); const buttonElRef = React.useRef(null); const dropdownElRef = React.useRef(null); - const listElRef = React.useRef(null); const [isOpen, setIsOpen] = React.useState(false); const handleKeyDown = (event: React.KeyboardEvent): void => { @@ -74,52 +70,58 @@ export default function SettingsDropdown({ useClickOutside(dropdownElRef, () => setIsOpen(false)); + // Customize the forwarded ref to combine usage with the ref internal to this component + React.useImperativeHandle(ref, () => buttonElRef.current, []); + return ( -
+
{label}
- - - + + + + {listItems.map(({ label: itemLabel, value: itemValue }) => { + const itemValueString = itemValue.toString(); + return ( +
+ {itemLabel} +
+ ); + })} +
+
+
); } + +export default React.forwardRef(SettingsDropdown); diff --git a/src/lib/viewers/controls/settings/__tests__/SettingsDropdown-test.tsx b/src/lib/viewers/controls/settings/__tests__/SettingsDropdown-test.tsx index 31b2cb7f2..97bed2284 100644 --- a/src/lib/viewers/controls/settings/__tests__/SettingsDropdown-test.tsx +++ b/src/lib/viewers/controls/settings/__tests__/SettingsDropdown-test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { mount, ReactWrapper } from 'enzyme'; -import SettingsDropdown, { Props } from '../SettingsDropdown'; +import SettingsDropdown, { Props, Ref as SettingsDropdownRef } from '../SettingsDropdown'; import SettingsFlyout from '../SettingsFlyout'; import SettingsList from '../SettingsList'; @@ -186,6 +186,27 @@ describe('SettingsDropdown', () => { }); }); + describe('ref', () => { + const TestComponent = (): JSX.Element => { + const ref = React.useRef(null); + + React.useEffect(() => { + if (ref.current) { + ref.current.focus(); + } + }, []); + return ; + }; + + test('should be able to focus on the dropdown button', () => { + const wrapper = mount(, { + attachTo: getHostNode(), + }); + + expect(wrapper.find('.bp-SettingsDropdown-button').getDOMNode()).toHaveFocus(); + }); + }); + describe('render', () => { test('should return a valid wrapper', () => { const wrapper = getWrapper();