Skip to content

Commit

Permalink
feat(Dropdown): provide React refs support with the innerRef and butt…
Browse files Browse the repository at this point in the history
…onRef props (#2665)
  • Loading branch information
tujoworker authored Sep 20, 2023
1 parent 99f2933 commit 1d8d206
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ You may check out the [DrawerList Properties](#drawerlist-properties) down below
| `label_sr_only` | _(optional)_ use `true` to make the label only readable by screen readers. |
| `suffix` | _(optional)_ text describing the content of the Dropdown more than the label. You can also send in a React component, so it gets wrapped inside the Dropdown component. |
| `trigger_element` | _(optional)_ lets you provide a custom React element as the trigger HTML element. |
| `innerRef` | _(optional)_ by providing a React.ref you can get the internally used main element (DOM). E.g. `innerRef={myRef}` by using `React.createRef()` or `React.useRef()`. |
| `buttonRef` | _(optional)_ by providing a React.ref you can get the internally used button element (DOM). E.g. `buttonRef={myRef}` by using `React.createRef()` or `React.useRef()`. |
| `skeleton` | _(optional)_ if set to `true`, an overlaying skeleton with animation will be shown. |
| [DrawerList](/uilib/components/fragments/drawer-list/properties) | _(optional)_ all DrawerList properties. |
| [Space](/uilib/components/space/properties) | _(optional)_ spacing properties like `top` or `bottom` are supported. |
Expand Down
2 changes: 2 additions & 0 deletions packages/dnb-eufemia/src/components/dropdown/Dropdown.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ export interface DropdownProps
* The <a href="/uilib/components/global-status/properties/#configuration-object">configuration</a> used for the target <a href="/uilib/components/global-status">GlobalStatus</a>.
*/
globalStatus?: GlobalStatusConfigObject;
innerRef?: React.Ref;
buttonRef?: React.Ref;
/**
* Same as `prevent_selection`, but the "selection area" (given title) will not be visible and the icon `more` (three dots) is used. Defaults to `false`.
*/
Expand Down
24 changes: 15 additions & 9 deletions packages/dnb-eufemia/src/components/dropdown/Dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ export default class Dropdown extends React.PureComponent {
PropTypes.string,
PropTypes.bool,
]),
innerRef: PropTypes.object,
buttonRef: PropTypes.object,
globalStatus: PropTypes.shape({
id: PropTypes.string,
message: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
Expand Down Expand Up @@ -178,6 +180,8 @@ export default class Dropdown extends React.PureComponent {
status_props: null,
status_no_animation: null,
globalStatus: null,
innerRef: null,
buttonRef: null,
suffix: null,
scrollable: true,
focusable: false,
Expand Down Expand Up @@ -256,9 +260,9 @@ class DropdownInstance extends React.PureComponent {
this.attributes = {}
this.state = this.state || {}

this._ref = React.createRef()
this._refShell = React.createRef()
this._refButton = React.createRef()
this._ref = props.innerRef || React.createRef()
this._refWrapper = React.createRef()
this._refButton = props.buttonRef || React.createRef()
}

componentDidMount() {
Expand All @@ -273,7 +277,7 @@ class DropdownInstance extends React.PureComponent {

setVisible = () => {
this.context.drawerList
.setWrapperElement(this._ref.current)
.setWrapperElement(this._refWrapper.current)
.setVisible()
}

Expand Down Expand Up @@ -353,7 +357,7 @@ class DropdownInstance extends React.PureComponent {
clearTimeout(this._focusTimeout)
this._focusTimeout = setTimeout(() => {
try {
const element = this._refButton.current._ref.current
const element = this._refButton.current
if (element && typeof element.focus === 'function') {
if (args.preventHideFocus !== true) {
element.focus({ preventScroll: true })
Expand Down Expand Up @@ -458,6 +462,8 @@ class DropdownInstance extends React.PureComponent {
id: _id, // eslint-disable-line
opened: _opened, // eslint-disable-line
value: _value, // eslint-disable-line
buttonRef, // eslint-disable-line
innerRef, // eslint-disable-line

...attributes
} = props
Expand Down Expand Up @@ -556,7 +562,7 @@ class DropdownInstance extends React.PureComponent {
this.attributes = validateDOMAttributes(null, attributes)

return (
<span {...mainParams}>
<span ref={this._ref} {...mainParams}>
{label && (
<FormLabel
id={id + '-label'}
Expand All @@ -570,7 +576,7 @@ class DropdownInstance extends React.PureComponent {
/>
)}

<span className="dnb-dropdown__inner" ref={this._ref}>
<span className="dnb-dropdown__inner" ref={this._refWrapper}>
<AlignmentHelper />

<FormStatus
Expand All @@ -587,15 +593,15 @@ class DropdownInstance extends React.PureComponent {
/>

<span className="dnb-dropdown__row">
<span className="dnb-dropdown__shell" ref={this._refShell}>
<span className="dnb-dropdown__shell">
{CustomTrigger ? (
<CustomTrigger {...triggerParams} />
) : (
<Button
variant={variant}
icon={false} // only to suppress the warning about the icon when tertiary variant is used
size={size === 'default' ? 'medium' : size}
ref={this._refButton}
innerRef={this._refButton}
custom_content={
<>
{!isPopupMenu && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,36 @@ describe('Dropdown component', () => {
).toHaveAttribute('disabled')
})

it('gets valid buttonRef element', () => {
let ref: React.RefObject<HTMLButtonElement>

function MockComponent() {
ref = React.useRef()
return <Dropdown {...props} buttonRef={ref} />
}

render(<MockComponent />)

expect(ref.current.id).toBe(props.id)
expect(ref.current.tagName).toBe('BUTTON')
expect(ref.current instanceof HTMLButtonElement).toBe(true)
})

it('gets valid innerRef element', () => {
let ref: React.RefObject<HTMLButtonElement>

function MockComponent() {
ref = React.useRef()
return <Dropdown {...props} innerRef={ref} />
}

render(<MockComponent />)

expect(ref.current.className).toContain('dnb-dropdown')
expect(ref.current.tagName).toBe('SPAN')
expect(ref.current instanceof HTMLSpanElement).toBe(true)
})

beforeAll(() => {
window.resizeTo = function resizeTo({
width = window.innerWidth,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,8 +375,6 @@ export default class DrawerListProvider extends React.PureComponent {
* EDS-246
*/
correctHiddenView = () => {
// console.log('this._refShell.current', this._refShell.current)
// console.log('this._refUl.current', this._refUl.current)
// We use "style.transform", because it is a independent "and quick" solution
// we could send down spaceToLeft and spaceToRight and set it with React's "style" prop in future
if (!this._refShell.current || !this._refUl.current) {
Expand Down

0 comments on commit 1d8d206

Please sign in to comment.