Skip to content

Commit

Permalink
feat(HelpButton): remove modal_props in favour of render (#2333)
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker committed May 31, 2023
1 parent 46f1ef7 commit 98fa52e
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ The Anchor was moved form `/elements` to `/components`.

### [HelpButton](/uilib/components/help-button)

1. property `modal_props` does not longer support the `mode` property. From a UX perspective it's only desirable to display a `Dialog` when using `HelpButton`, and not a `Drawer`.
1. The properties `modal_props` and `modal_content` where removed. You may replace these props with the new `render` property. See [this example](/uilib/components/help-button/properties/).

## Element changes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,29 @@ showTabs: true

## Properties

| Properties | Description |
| --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `children` or `modal_content` | _(optional)_ the content to show. |
| `title` | _(optional)_ the content title. Defaults to `Hjelpetekst` (HelpButton.title). |
| `icon` | _(optional)_ the icon defaults to `question`. |
| `modal_content` | _(optional)_ the content which will appear when triggering the modal. |
| `modal_props` | _(optional)_ accepts all [Modal properties](/uilib/components/modal/properties) as an object, if `children` is given. |
| [Button](/uilib/components/button/properties) | _(optional)_ accepts all Button properties, if `children` is not given. |
| Properties | Description |
| --------------------------------------------- | -------------------------------------------------------------------------------------- |
| `children` or `modal_content` | _(optional)_ the content to show. |
| `title` | _(optional)_ the content title. Defaults to `Hjelpetekst` (HelpButton.title). |
| `icon` | _(optional)_ the icon defaults to `question`. |
| `render` | _(optional)_ accepts a function that returns a valid React Element. See example below. |
| [Button](/uilib/components/button/properties) | _(optional)_ accepts all Button properties, if `children` is not given. |

## How to use `render`

```jsx
import { HelpButton, Dialog } from '@dnb/eufemia'

render(
<HelpButton
title="Title"
render={(children, props) => (
<Dialog triggerAttributes={props} className="your-class">
{children}
</Dialog>
)}
>
Help text
</HelpButton>
)
```
36 changes: 11 additions & 25 deletions packages/dnb-eufemia/src/components/help-button/HelpButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import Context from '../../shared/Context'
import Dialog from '../dialog/Dialog'
import HelpButtonInstance from './HelpButtonInstance'
import type { ButtonProps } from '../button/Button'
import { ModalProps } from '../modal/types'
import { extendPropsWithContext } from '../../shared/component-helper'

const defaultProps = {
Expand All @@ -17,45 +16,32 @@ const defaultProps = {
}

export type HelpButtonProps = {
modal_content?: React.ReactNode
modal_props?: ModalProps
render?: (
children: React.ReactNode,
props: ButtonProps
) => React.ReactElement
} & ButtonProps

export default function HelpButton(localProps: HelpButtonProps) {
const getContent = (props: HelpButtonProps) => {
if (props.modal_content) {
return props.modal_content
}
return typeof props.children === 'function'
? props.children(props)
: props.children
}

const context = React.useContext(Context)
const props = extendPropsWithContext(localProps, defaultProps)
const content = getContent(props)

const {
modal_content, // eslint-disable-line
children, // eslint-disable-line
modal_props,
...params
} = props
const { children, render, ...params } = props

if (params.icon === null) {
params.icon = 'question'
}

if (content) {
if (children) {
if (!params.title) {
params.title = context.getTranslation(props).HelpButton.title
}

return (
<Dialog triggerAttributes={params} {...modal_props}>
{content}
</Dialog>
)
if (typeof render === 'function') {
return render(children, params)
}

return <Dialog triggerAttributes={params}>{children}</Dialog>
}

return <HelpButtonInstance {...params} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,133 +5,170 @@

import React from 'react'
import {
mount,
fakeProps,
axeComponent,
loadScss,
} from '../../../core/jest/jestSetup'
import Component, { HelpButtonProps } from '../HelpButton'
import Dialog from '../../dialog/Dialog'
import HelpButton, { HelpButtonProps } from '../HelpButton'
import {
question as QuestionIcon,
information_medium as InformationIcon,
} from '../../../icons'
import { ModalProps } from '../../modal/types'
import { fireEvent, render } from '@testing-library/react'

const snapshotProps = fakeProps(require.resolve('../HelpButton'), {})
snapshotProps.id = 'help-button'

const modal_props: ModalProps = {}
modal_props.content_id = null
modal_props.no_animation = true

const props: HelpButtonProps = { modal_props }
const props: HelpButtonProps = {}
props.id = 'help-button'

describe('HelpButton component', () => {
describe('HelpButton', () => {
it('should have question icon by default', () => {
const Comp = mount(<Component {...props} />)
render(<HelpButton {...props} />)
expect(
Comp.find('.dnb-icon').instance().getAttribute('data-testid')
document.querySelector('.dnb-icon').getAttribute('data-testid')
).toBe('question icon')
expect(Comp.find('svg').html()).toBe(mount(<QuestionIcon />).html())
expect(Comp.find('.dnb-button').text().trim()).toBe('‌')
expect(document.querySelector('svg').outerHTML).toBe(
render(<QuestionIcon />).container.innerHTML
)
expect(document.querySelector('.dnb-button').textContent.trim()).toBe(
'‌'
)
})

it('should use "information" icon when set', () => {
const Comp = mount(<Component {...props} icon="information" />)
render(<HelpButton {...props} icon="information" />)
expect(
Comp.find('.dnb-icon').instance().getAttribute('data-testid')
document.querySelector('.dnb-icon').getAttribute('data-testid')
).toBe('information icon')
expect(Comp.find('svg').html()).toBe(mount(<InformationIcon />).html())
expect(Comp.find('.dnb-button').text().trim()).toBe('‌')
expect(document.querySelector('svg').outerHTML).toBe(
render(<InformationIcon />).container.innerHTML
)
expect(document.querySelector('.dnb-button').textContent.trim()).toBe(
'‌'
)
})

it('should use given icon', () => {
const Comp = mount(<Component {...props} icon={InformationIcon} />)
render(<HelpButton {...props} icon={InformationIcon} />)
expect(
Comp.find('.dnb-icon').instance().getAttribute('data-testid')
document.querySelector('.dnb-icon').getAttribute('data-testid')
).toBe('information medium icon')
expect(Comp.find('svg').html()).toBe(mount(<InformationIcon />).html())
expect(Comp.find('.dnb-button').text().trim()).toBe('‌')
expect(document.querySelector('svg').outerHTML).toBe(
render(<InformationIcon />).container.innerHTML
)
expect(document.querySelector('.dnb-button').textContent.trim()).toBe(
'‌'
)
})

it('should have correct role description', () => {
const Comp = mount(<Component {...props} />)
render(<HelpButton {...props} />)
expect(
Comp.find('.dnb-button')
.instance()
document
.querySelector('.dnb-button')

.getAttribute('aria-roledescription')
).toBe('Hjelp-knapp')
})

describe('with bell icon', () => {
it('should have correct aria-label', () => {
const Comp = mount(<Component {...props} icon="bell" />)
render(<HelpButton {...props} icon="bell" />)
expect(
Comp.find('.dnb-button').instance().getAttribute('aria-label')
document.querySelector('.dnb-button').getAttribute('aria-label')
).toBe('Hjelpetekst')
})

it('should have not aria-label if text is given', () => {
const Comp = mount(
<Component {...props} icon="bell" text="button text" />
)
render(<HelpButton {...props} icon="bell" text="button text" />)
expect(
Comp.find('.dnb-button').instance().hasAttribute('aria-label')
document.querySelector('.dnb-button').hasAttribute('aria-label')
).toBe(false)
expect(Comp.find('.dnb-button').text().trim()).toBe('‌button text')
expect(
document.querySelector('.dnb-button').textContent.trim()
).toBe('‌button text')
})

it('should have aria-label if title is given, but no text', () => {
const Comp = mount(
<Component {...props} icon="bell" title="button title" />
)
render(<HelpButton {...props} icon="bell" title="button title" />)
expect(
Comp.find('.dnb-button').instance().getAttribute('aria-label')
document.querySelector('.dnb-button').getAttribute('aria-label')
).toBe('button title')
expect(Comp.find('.dnb-button').text().trim()).toBe('‌')
expect(
document.querySelector('.dnb-button').textContent.trim()
).toBe('‌')
})

it('should use given aria-label if title is given, but no text', () => {
const Comp = mount(
<Component
render(
<HelpButton
{...props}
icon="bell"
title="button title"
aria-label="custom aria-label"
/>
)
expect(
Comp.find('.dnb-button').instance().getAttribute('aria-label')
document.querySelector('.dnb-button').getAttribute('aria-label')
).toBe('custom aria-label')
expect(Comp.find('.dnb-button').text().trim()).toBe('‌')
expect(
document.querySelector('.dnb-button').textContent.trim()
).toBe('‌')
})

it('should validate with ARIA rules', async () => {
const Comp = mount(<Component {...props} icon="bell" />)
expect(await axeComponent(Comp)).toHaveNoViolations()
const Component = render(<HelpButton {...props} icon="bell" />)
expect(await axeComponent(Component)).toHaveNoViolations()
})
})

it('should open a modal if children are given', () => {
const modalContent = 'Modal Content'
const Comp = mount(<Component {...props}>{modalContent}</Component>)
it('should open a dialog if children are given', () => {
const dialogContent = 'Dialog Content'
render(<HelpButton {...props}>{dialogContent}</HelpButton>)

Comp.find('button.dnb-modal__trigger').simulate('click')
fireEvent.click(document.querySelector('button.dnb-modal__trigger'))

const id = `dnb-modal-${props.id}`
const modalElem = document.getElementById(id)
const textContent = String(modalElem.textContent).replace(
const dialogElem = document.getElementById(id)
const textContent = String(dialogElem.textContent).replace(
/\u200C/g,
''
)

expect(textContent).toContain(modalContent)
expect(textContent).toContain(dialogContent)
})

it('should return given render element', () => {
render(
<HelpButton
title="Title"
render={(children, props) => (
<Dialog triggerAttributes={props} className="custom-class">
{children}
</Dialog>
)}
>
Help text
</HelpButton>
)

fireEvent.click(document.querySelector('button.dnb-modal__trigger'))

const dialogElem = document.querySelector('.custom-class')

expect(
dialogElem.querySelector('.dnb-dialog__header').textContent
).toBe('Title')
expect(
dialogElem.querySelector('.dnb-dialog__content').textContent
).toBe('Help text')
})

it('should validate with ARIA rules', async () => {
const Comp = mount(<Component {...props} />)
expect(await axeComponent(Comp)).toHaveNoViolations()
const Component = render(<HelpButton {...props} />)
expect(await axeComponent(Component)).toHaveNoViolations()
})
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import React from 'react'
import { Wrapper, Box } from 'storybook-utils/helpers'

import { HelpButton, Dialog, Button, Section, Input } from '../..'
import { HelpButton, Dialog, Drawer, Button, Section, Input } from '../..'

export default {
title: 'Eufemia/Components/HelpButton',
Expand All @@ -16,11 +16,13 @@ export const HelpButtonSandbox = () => (
<Wrapper>
<Box>
<HelpButton
text="Help"
on_click={(e) => {
console.log(e)
}}
/>
title="Tittel"
render={(children, props) => (
<Drawer triggerAttributes={props}>{children}</Drawer>
)}
>
Helpe text
</HelpButton>
</Box>

<Box>
Expand Down

0 comments on commit 98fa52e

Please sign in to comment.