Skip to content

Commit

Permalink
feat(Textarea): add characterCounter
Browse files Browse the repository at this point in the history
  • Loading branch information
tujoworker committed Jan 15, 2024
1 parent d12fbce commit 532a1aa
Show file tree
Hide file tree
Showing 13 changed files with 124 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,22 @@ export const TextareaExampleVertical = () => (
</Wrapper>
)

export const TextareaCharacterCounter = () => (
<Wrapper>
<ComponentBox data-visual-test="textarea-character-counter">
<Textarea
label="Count characters"
label_direction="vertical"
autoresize
value="Textarea value\nNewline"
status="Message to the user"
characterCounter
maxLength={40}
/>
</ComponentBox>
</Wrapper>
)

export const TextareaExampleStretched = () => (
<Wrapper>
<ComponentBox data-visual-test="textarea-stretch">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
TextareaExampleStretched,
TextareaExampleAutoresize,
TextareaExampleMaxLength,
TextareaCharacterCounter,
TextareaExampleFormStatus,
TextareaExampleDisabled,
TextareaExampleSuffix,
Expand Down Expand Up @@ -40,6 +41,10 @@ import {

<TextareaExampleMaxLength />

### Character counter

<TextareaCharacterCounter />

### With FormStatus failure message

<TextareaExampleFormStatus />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ showTabs: true
| `label_sr_only` | _(optional)_ use `true` to make the label only readable by screen readers. |
| `autoresize` | _(optional)_ use `true` to make the Textarea grow and shrink depending on how many lines the user has filled. |
| `autoresize_max_rows` | _(optional)_ set a number to define how many rows the Textarea can auto grow. |
| `characterCounter` | _(optional)_ use `true` to show a character counter. You need to set a `maxLength={number}` in order to show the counter. |
| `status` | _(optional)_ text with a status message. The style defaults to an error message. You can use `true` to only get the status color, without a message. |
| `status_state` | _(optional)_ defines the state of the status. Currently, there are two statuses `[error, info]`. Defaults to `error`. |
| `status_props` | _(optional)_ use an object to define additional FormStatus properties. |
Expand Down
4 changes: 4 additions & 0 deletions packages/dnb-eufemia/src/components/textarea/Textarea.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ export interface TextareaProps
* Use `true` to make the Textarea grow and shrink depending on how many lines the user has filled.
*/
autoresize?: boolean;
/**
* use `true` to show a character counter.
*/
characterCounter?: boolean;
/**
* Set a number to define how many rows the Textarea can auto grow.
*/
Expand Down
35 changes: 33 additions & 2 deletions packages/dnb-eufemia/src/components/textarea/Textarea.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import PropTypes from 'prop-types'
import classnames from 'classnames'
import FormLabel from '../form-label/FormLabel'
import FormStatus from '../form-status/FormStatus'
import AriaLive from '../aria-live/AriaLive'
import {
isTrue,
makeUniqueId,
Expand Down Expand Up @@ -76,6 +77,7 @@ export default class Textarea extends React.PureComponent {
stretch: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
disabled: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
skeleton: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
characterCounter: PropTypes.bool,
autoresize: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
autoresize_max_rows: PropTypes.oneOfType([
PropTypes.string,
Expand Down Expand Up @@ -128,6 +130,7 @@ export default class Textarea extends React.PureComponent {
skeleton: null,
autoresize: null,
autoresize_max_rows: null,
characterCounter: null,
textarea_class: null,
class: null,
textarea_attributes: null,
Expand Down Expand Up @@ -240,7 +243,6 @@ export default class Textarea extends React.PureComponent {
const { value } = event.target
this.setState({
value,

textareaState: Textarea.hasValue(value) ? 'dirty' : 'initial',
})
dispatchCustomElementEvent(this, 'on_blur', { value, event })
Expand Down Expand Up @@ -334,6 +336,33 @@ export default class Textarea extends React.PureComponent {
getLineHeight() {
return parseFloat(getComputedStyle(this._ref.current).lineHeight) || 0
}
getCounter = () => {
const { characterCounter, maxLength } = this.props

if (characterCounter !== true || !maxLength) {
return null
}

const { value, textareaState } = this.state
const count = (value || '').length

const message = this.context
.getTranslation(this.props)
.Textarea.characterCounter.replace('%count', count)
.replace('%max', maxLength)

return (
<>
<span className="dnb-textarea__counter">{message}</span>
<AriaLive
disabled={count > 0 && textareaState === 'virgin'}
delay={2000}
>
{message}
</AriaLive>
</>
)
}
render() {
// use only the props from context, who are available here anyway
const props = extendPropsWithContextInClassComponent(
Expand Down Expand Up @@ -367,9 +396,9 @@ export default class Textarea extends React.PureComponent {
textarea_attributes,
class: _className,
className,

autoresize,
autoresize_max_rows, //eslint-disable-line
characterCounter, //eslint-disable-line
id: _id, //eslint-disable-line
children, //eslint-disable-line
value: _value, //eslint-disable-line
Expand Down Expand Up @@ -539,6 +568,8 @@ export default class Textarea extends React.PureComponent {
</Suffix>
)}
</span>

{this.getCounter()}
</span>
</span>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ describe.each(['ui', 'sbanken'])('Textarea for %s', (themeName) => {
expect(screenshot).toMatchImageSnapshot()
})

it('have to match character counter', async () => {
const screenshot = await makeScreenshot({
style,
selector: '[data-visual-test="textarea-character-counter"]',
// Only for screenshot testing - make textarea having same width on linux chromium
styleSelector: '[data-visual-test="textarea-default"] textarea',
})
expect(screenshot).toMatchImageSnapshot()
})

it('have to match the default error textarea style', async () => {
const screenshot = await makeScreenshot({
style,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,34 @@ describe('Textarea component', () => {
expect(ref.current.tagName).toBe('TEXTAREA')
expect(ref.current).toBeInstanceOf(HTMLTextAreaElement)
})

it('should render characterCounter', async () => {
const { rerender } = render(
<Textarea maxLength={8} characterCounter value="foo" />
)

const counter = document.querySelector('.dnb-textarea__counter')
const textarea = document.querySelector('textarea')
const ariaLive = document.querySelector('.dnb-aria-live')

expect(counter).toHaveTextContent('3 av 8 gjenstående tegn')
expect(ariaLive).toHaveTextContent('')

await userEvent.type(textarea, 'bar')

expect(counter).toHaveTextContent('6 av 8 gjenstående tegn')
expect(ariaLive).toHaveTextContent('6 av 8 gjenstående tegn')

rerender(
<Textarea maxLength={8} characterCounter value="foo" lang="en-GB" />
)

expect(counter).toHaveTextContent('6 of 8 characters remaining')

await userEvent.type(textarea, 'baz')

expect(ariaLive).toHaveTextContent('8 of 8 characters remaining')
})
})

describe('Textarea scss', () => {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ button .dnb-form-status__text {
.dnb-textarea__inner {
display: inline-flex;
flex-direction: column;
margin: var(--textarea-padding-width);
margin: 0 var(--textarea-padding-width);
}
.dnb-textarea__shell {
display: inline-flex;
Expand All @@ -175,10 +175,17 @@ button .dnb-form-status__text {
}
.dnb-textarea__row {
display: inline-flex;
margin: var(--textarea-padding-width) 0;
}
.dnb-textarea__suffix.dnb-suffix {
padding-left: 1rem;
}
.dnb-textarea__counter {
margin-top: 0.5rem;
margin-left: -0.5rem;
font-size: var(--font-size-small);
color: var(--color-black-55);
}
.dnb-textarea__textarea {
position: relative;
z-index: 2;
Expand Down Expand Up @@ -265,7 +272,7 @@ html:not([data-visual-test]) .dnb-textarea__textarea {
}
.dnb-textarea__inner > .dnb-form-status {
order: 2;
margin: 1rem 0 0 calc(0px - var(--textarea-padding-width));
margin: 0.5rem 0 0 calc(0px - var(--textarea-padding-width));
}
.dnb-textarea:not(.dnb-textarea--vertical) .dnb-form-label {
margin-top: 0.5rem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
display: inline-flex;
flex-direction: column;

margin: var(--textarea-padding-width);
margin: 0 var(--textarea-padding-width);
}

&__shell {
Expand All @@ -58,12 +58,22 @@

&__row {
display: inline-flex;

margin: var(--textarea-padding-width) 0;
}

&__suffix.dnb-suffix {
padding-left: 1rem;
}

&__counter {
margin-top: 0.5rem;
margin-left: -0.5rem;

font-size: var(--font-size-small);
color: var(--color-black-55);
}

&__textarea {
position: relative;
z-index: 2;
Expand Down Expand Up @@ -138,7 +148,7 @@

&__inner > .dnb-form-status {
order: 2;
margin: 1rem 0 0 calc(1px - 1px - var(--textarea-padding-width));
margin: 0.5rem 0 0 calc(1px - 1px - var(--textarea-padding-width));
}

&:not(#{&}--vertical) .dnb-form-label {
Expand Down
3 changes: 3 additions & 0 deletions packages/dnb-eufemia/src/shared/locales/en-GB.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
export default {
'en-GB': {
Textarea: {
characterCounter: '%count of %max characters remaining',
},
TimelineItem: {
alt_label_completed: 'Complete',
alt_label_current: 'Current',
Expand Down
3 changes: 3 additions & 0 deletions packages/dnb-eufemia/src/shared/locales/nb-NO.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
export default {
'nb-NO': {
Textarea: {
characterCounter: '%count av %max gjenstående tegn',
},
TimelineItem: {
alt_label_completed: 'Utført',
alt_label_current: 'Nåværende',
Expand Down

0 comments on commit 532a1aa

Please sign in to comment.