Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for functional refs to various form components #2946

Merged
merged 15 commits into from
Dec 1, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,20 @@ describe('Anchor element', () => {
expect(ref.current).toBe(element)
})

it('gets valid element when ref is function', () => {
const ref: React.MutableRefObject<HTMLAnchorElement> =
React.createRef()

const refFn = (elem: HTMLAnchorElement) => {
ref.current = elem
}

render(<Anchor id="unique" ref={refFn} />)

expect(ref.current.getAttribute('id')).toBe('unique')
expect(ref.current.tagName).toBe('A')
})

it('has aria-describedby when target is blank', () => {
const { rerender } = render(
<Anchor href="/url" target="_blank" lang="en-GB">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3085,6 +3085,19 @@ describe('Autocomplete component', () => {
})
})
})

it('gets valid element when input_ref is function', () => {
const ref: React.MutableRefObject<HTMLInputElement> = React.createRef()

const refFn = (elem: HTMLInputElement) => {
ref.current = elem
}

render(<Autocomplete id="unique" input_ref={refFn} />)

expect(ref.current.getAttribute('id')).toBe('unique')
expect(ref.current.tagName).toBe('INPUT')
})
})

describe('Autocomplete markup', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,19 @@ describe('Button component', () => {
expect(typeof ref.current).toBe('object')
})

it('gets valid element when innerRef is function', () => {
const ref: React.MutableRefObject<HTMLButtonElement> =
React.createRef()

const refFn = (elem: HTMLButtonElement) => {
ref.current = elem
}
render(<Button id="unique" innerRef={refFn} />)

expect(ref.current.getAttribute('id')).toBe('unique')
expect(ref.current.tagName).toBe('BUTTON')
})

it('has type of button', () => {
render(<Button />)
const button = document.querySelector('button')
Expand Down
14 changes: 12 additions & 2 deletions packages/dnb-eufemia/src/components/checkbox/Checkbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export default class Checkbox extends React.PureComponent {
readOnly: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
skeleton: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
class: PropTypes.string,
innerRef: PropTypes.object,
innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),

...spacingPropTypes,

Expand Down Expand Up @@ -116,6 +116,8 @@ export default class Checkbox extends React.PureComponent {

on_change: null,
on_state_update: null,

innerRef: null,
}

static parseChecked = (state) => /true|on/.test(String(state))
Expand All @@ -142,14 +144,22 @@ export default class Checkbox extends React.PureComponent {

constructor(props) {
super(props)
this._refInput = props.innerRef || React.createRef()
this._refInput = React.createRef()

this._id = props.id || makeUniqueId() // cause we need an id anyway
this.state = {
_listenForPropChanges: true,
}
}

componentDidMount() {
if (this.props.innerRef) {
typeof this.props.innerRef === 'function'
? this.props.innerRef(this._refInput.current)
: (this.props.innerRef.current = this._refInput.current)
}
}

onKeyDownHandler = (event) => {
switch (keycode(event)) {
case 'enter':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,28 @@ describe('Checkbox component', () => {

function MockComponent() {
ref = React.useRef()
return <Checkbox {...props} innerRef={ref} />
return <Checkbox id="unique" innerRef={ref} />
}

render(<MockComponent />)

expect(ref.current.getAttribute('id')).toBe('unique')
expect(ref.current.classList).toContain('dnb-checkbox__input')
expect(ref.current.tagName).toBe('INPUT')
})

it('gets valid element when ref is function', () => {
const ref: React.MutableRefObject<HTMLInputElement> = React.createRef()

const refFn = (elem: HTMLInputElement) => {
ref.current = elem
}

render(<Checkbox id="unique" innerRef={refFn} />)

expect(ref.current.getAttribute('id')).toBe('unique')
expect(ref.current.classList).toContain('dnb-checkbox__input')
expect(ref.current.tagName).toBe('INPUT')
})
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1179,6 +1179,21 @@ describe('Dropdown component', () => {
expect(ref.current instanceof HTMLButtonElement).toBe(true)
})

it('gets valid buttonRef element when ref is function', () => {
const ref: React.MutableRefObject<HTMLButtonElement> =
React.createRef()

function refFuntion(instance: HTMLButtonElement) {
joakbjerk marked this conversation as resolved.
Show resolved Hide resolved
ref.current = instance
}

render(<Dropdown {...props} buttonRef={refFuntion} />)
joakbjerk marked this conversation as resolved.
Show resolved Hide resolved

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>

Expand All @@ -1194,6 +1209,21 @@ describe('Dropdown component', () => {
expect(ref.current instanceof HTMLSpanElement).toBe(true)
})

it('gets valid innerRef element when ref is function', () => {
const ref: React.MutableRefObject<HTMLButtonElement> =
React.createRef()

function refFuntion(instance: HTMLButtonElement) {
joakbjerk marked this conversation as resolved.
Show resolved Hide resolved
ref.current = instance
}

render(<Dropdown {...props} innerRef={refFuntion} />)
joakbjerk marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -53,11 +53,9 @@ describe('InputMasked component', () => {
})

it('gets valid element when ref is function', () => {
const ref = React.createRef<HTMLInputElement | null>()
const ref: React.MutableRefObject<HTMLInputElement> = React.createRef()

const refFn = (elem: HTMLInputElement) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
ref.current = elem
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,9 @@ describe('Input component', () => {
})

it('gets valid element when ref is function', () => {
const ref = React.createRef<HTMLInputElement | null>()
const ref: React.MutableRefObject<HTMLInputElement> = React.createRef()

const refFn = (elem: HTMLInputElement) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
ref.current = elem
}

Expand Down
14 changes: 12 additions & 2 deletions packages/dnb-eufemia/src/components/radio/Radio.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export default class Radio extends React.PureComponent {
attributes: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
skeleton: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
readOnly: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
innerRef: PropTypes.object,
innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),

...spacingPropTypes,

Expand Down Expand Up @@ -118,6 +118,8 @@ export default class Radio extends React.PureComponent {

on_change: null,
on_state_update: null,

innerRef: null,
}

static Group = RadioGroup
Expand Down Expand Up @@ -146,13 +148,21 @@ export default class Radio extends React.PureComponent {

constructor(props) {
super(props)
this._refInput = props.innerRef || React.createRef()
this._refInput = React.createRef()
this._id = props.id || makeUniqueId() // cause we need an id anyway
this.state = {
_listenForPropChanges: true,
}
}

componentDidMount() {
if (this.props.innerRef) {
typeof this.props.innerRef === 'function'
? this.props.innerRef(this._refInput.current)
: (this.props.innerRef.current = this._refInput.current)
}
}

onKeyDownHandler = (event) => {
const key = keycode(event)
// only have key support if there is only a single radio
Expand Down
13 changes: 13 additions & 0 deletions packages/dnb-eufemia/src/components/radio/__tests__/Radio.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,19 @@ describe('Radio ARIA', () => {

expect(ref.current.classList).toContain('dnb-radio__input')
})

it('gets valid element when ref is function', () => {
const ref: React.MutableRefObject<HTMLInputElement> = React.createRef()

const refFn = (elem: HTMLInputElement) => {
ref.current = elem
}

render(<Radio id="unique" innerRef={refFn} />)

expect(ref.current.getAttribute('id')).toBe('unique')
expect(ref.current.classList).toContain('dnb-radio__input')
})
})

describe('Radio scss', () => {
Expand Down
14 changes: 12 additions & 2 deletions packages/dnb-eufemia/src/components/switch/Switch.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export default class Switch extends React.PureComponent {
attributes: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
readOnly: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
skeleton: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
innerRef: PropTypes.object,
innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),

...spacingPropTypes,

Expand Down Expand Up @@ -116,6 +116,8 @@ export default class Switch extends React.PureComponent {
on_change: null,
on_change_end: null,
on_state_update: null,

innerRef: null,
}

static parseChecked = (state) => /true|on/.test(String(state))
Expand All @@ -142,14 +144,22 @@ export default class Switch extends React.PureComponent {

constructor(props) {
super(props)
this._refInput = props.innerRef || React.createRef()
this._refInput = React.createRef()
this._id = props.id || makeUniqueId() // cause we need an id anyway
this.state = {
_listenForPropChanges: true,
}
this.helperParams = { onMouseDown: (e) => e.preventDefault() }
}

componentDidMount() {
if (this.props.innerRef) {
typeof this.props.innerRef === 'function'
? this.props.innerRef(this._refInput.current)
: (this.props.innerRef.current = this._refInput.current)
}
}

componentWillUnmount() {
clearTimeout(this._onChangeEndId)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,19 @@ describe('Switch component', () => {

expect(ref.current.classList).toContain('dnb-switch__input')
})

it('gets valid element when ref is function', () => {
const ref: React.MutableRefObject<HTMLInputElement> = React.createRef()

const refFn = (elem: HTMLInputElement) => {
ref.current = elem
}

render(<Switch id="unique" innerRef={refFn} />)

expect(ref.current.getAttribute('id')).toBe('unique')
expect(ref.current.classList).toContain('dnb-switch__input')
})
})

describe('Switch scss', () => {
Expand Down
8 changes: 7 additions & 1 deletion packages/dnb-eufemia/src/components/textarea/Textarea.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ export default class Textarea extends React.PureComponent {
constructor(props) {
super(props)

this._ref = props.inner_ref || React.createRef()
this._ref = React.createRef()
this._id = props.id || makeUniqueId() // cause we need an id anyway

// make sure we don't trigger getDerivedStateFromProps on startup
Expand All @@ -206,6 +206,12 @@ export default class Textarea extends React.PureComponent {
this.state._value = props.value
}
componentDidMount() {
if (this.props.inner_ref) {
typeof this.props.inner_ref === 'function'
? this.props.inner_ref(this._ref.current)
: (this.props.inner_ref.current = this._ref.current)
}

if (isTrue(this.props.autoresize) && typeof window !== 'undefined') {
this.setAutosize()
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,37 @@ describe('Textarea component', () => {

expect(await axeComponent(Comp)).toHaveNoViolations()
})

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

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

render(<MockComponent />)

expect(ref.current.classList).toContain('dnb-textarea__textarea')
expect(ref.current.tagName).toBe('TEXTAREA')
expect(ref.current).toBeInstanceOf(HTMLTextAreaElement)
})

it('gets valid element when ref is function', () => {
const ref: React.MutableRefObject<HTMLTextAreaElement> =
React.createRef()

const refFn = (elem: HTMLTextAreaElement) => {
ref.current = elem
}

render(<Textarea id="unique" inner_ref={refFn} />)

expect(ref.current.getAttribute('id')).toBe('unique')
expect(ref.current.classList).toContain('dnb-textarea__textarea')
expect(ref.current.tagName).toBe('TEXTAREA')
expect(ref.current).toBeInstanceOf(HTMLTextAreaElement)
})
})

describe('Textarea scss', () => {
Expand Down
Loading