diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/text-counter/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/text-counter/Examples.tsx index 646d77d0f15..b58a9ab522c 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/text-counter/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/text-counter/Examples.tsx @@ -26,12 +26,28 @@ export function CountCharactersUp() { ) } +export function CountCharactersExceeded() { + return ( + + + + ) +} + export function CountCharactersInteractive() { return ( {() => { + const text = 'Count me!' + const variant: TextCounterProps['variant'] = 'down' + const initialData = { + max: 10, + variant, + text, + } + const Counter = () => { - const { data } = Form.useData('text-counter-up', initialData) + const { data } = Form.useData('text-counter', initialData) return ( @@ -59,15 +75,8 @@ export function CountCharactersInteractive() { ) } - const variant: TextCounterProps['variant'] = 'down' - const initialData = { - max: 10, - variant, - text: 'Count me!', - } - return ( - + ) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/text-counter/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/text-counter/demos.mdx index 4a7a14bbad9..28629493d1d 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/text-counter/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/text-counter/demos.mdx @@ -6,6 +6,10 @@ import * as Examples from './Examples' ## Demos +### Interactive + + + ### Count characters downwards @@ -14,6 +18,6 @@ import * as Examples from './Examples' -### Interactive +### Show message as exceeded - + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/text-counter/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/text-counter/properties.mdx index e4a6acc4241..cfacec041b8 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/text-counter/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/text-counter/properties.mdx @@ -9,5 +9,4 @@ showTabs: true | `text` | _(required)_ The text to count characters from. | | `max` | _(required)_ The maximum number of characters allowed. | | `variant` | _(optional)_ The counting variant. Can be either `up` (counts up from zero) or `down` (counts down from max). Default is `down`. | -| `bypassAriaLive` | _(optional)_ If true, AriaLive is bypassed. This is useful to signal that a given input field value should not be read out. | | [Space](/uilib/layout/space/properties) | _(optional)_ spacing properties like `top` or `bottom` are supported. | diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/textarea/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/textarea/demos.mdx index d38fccb2885..2d8d5968aba 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/textarea/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/textarea/demos.mdx @@ -43,6 +43,8 @@ import { ### Character counter +Internally, the [TextCounter](uilib/components/fragments/text-counter/) fragment is used to display the character counter. + ### With FormStatus failure message diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/textarea/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/textarea/properties.mdx index 2ff00ab72ab..604cee56430 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/textarea/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/textarea/properties.mdx @@ -16,7 +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 a number to define the displayed max length. You can also use an object defining the [TextCounter](/uilib/fragments/TextCounter/) `variant` or properties. Please avoid using `maxLength` for accessibility reasons. | +| `characterCounter` | _(optional)_ use a number to define the displayed max length. You can also use an object defining the [TextCounter](uilib/components/fragments/text-counter/) `variant` or properties. Please avoid using `maxLength` for accessibility reasons. | | `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. | diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/properties.mdx index c05e25c9ec7..eaf51e73304 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/properties.mdx @@ -6,28 +6,28 @@ import DataValueReadwriteProperties from '../../data-value-readwrite-properties. ### Component-specific props -| Property | Type | Description | -| --------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `type` | `string` | _(optional)_ Input DOM element type. | -| `multiline` | `boolean` | _(optional)_ True to be able to write in multiple lines (switching from input-element to textarea-element). | -| `leftIcon` | `string` | _(optional)_ For icon at the left side of the text input. | -| `rightIcon` | `string` | _(optional)_ For icon at the right side of the text input. | -| `inputClassName` | `string` | _(optional)_ Class name set on the <input> DOM element. | -| `innerRef` | `React.ref` | _(optional)_ by providing a React.ref we can get the internally used input element (DOM). E.g. `innerRef={myRef}` by using `React.createRef()` or `React.useRef()`. | -| `clear` | `boolean` | _(optional)_ True to have a clickable clear-icon for removing the active value. | -| `autoresize` | `boolean` | _(optional)_ For `multiline`, set `true` to expand when writing longer texts. | -| `autoComplete` | `on` or `string` | _(optional)_ For HTML `autocomplete` [attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete). | -| `capitalize` | `boolean` | _(optional)_ When set to `true`, it will capitalize the first letter of every word, transforming the rest to lowercase. | -| `trim` | `boolean` | _(optional)_ When `true`, it will trim leading and trailing whitespaces on blur, triggering onChange if the value changes. | -| `inputMode` | `string` | _(optional)_ Define a [inputmode](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode). | -| `autoresizeMaxRows` | `boolean` | _(optional)_ For `multiline`, set how many rows of text can be shown at max. | -| `characterCounter` | `boolean` or `string` | _(optional)_ For `multiline`, use a number to define the displayed max length. You can also use an object defining the [TextCounter](/uilib/fragments/TextCounter/) `variant` or properties. | -| `minLength` | `number` | _(optional)_ Validation for minimum length of the text (number of characters). | -| `maxLength` | `number` | _(optional)_ Validation for maximum length of the text (number of characters). | -| `pattern` | `string` | _(optional)_ Validation based on regex pattern. | -| `width` | `string` or `false` | _(optional)_ `false` for no width (use browser default), `small`, `medium` or `large` for predefined standard widths, `stretch` for fill available width. | -| `help` | `object` | _(optional)_ Provide a help button. Object consisting of `title` and `content`. | -| [Space](/uilib/layout/space/properties) | Various | _(optional)_ Spacing properties like `top` or `bottom` are supported. | +| Property | Type | Description | +| --------------------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | `string` | _(optional)_ Input DOM element type. | +| `multiline` | `boolean` | _(optional)_ True to be able to write in multiple lines (switching from input-element to textarea-element). | +| `leftIcon` | `string` | _(optional)_ For icon at the left side of the text input. | +| `rightIcon` | `string` | _(optional)_ For icon at the right side of the text input. | +| `inputClassName` | `string` | _(optional)_ Class name set on the <input> DOM element. | +| `innerRef` | `React.ref` | _(optional)_ by providing a React.ref we can get the internally used input element (DOM). E.g. `innerRef={myRef}` by using `React.createRef()` or `React.useRef()`. | +| `clear` | `boolean` | _(optional)_ True to have a clickable clear-icon for removing the active value. | +| `autoresize` | `boolean` | _(optional)_ For `multiline`, set `true` to expand when writing longer texts. | +| `autoComplete` | `on` or `string` | _(optional)_ For HTML `autocomplete` [attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete). | +| `capitalize` | `boolean` | _(optional)_ When set to `true`, it will capitalize the first letter of every word, transforming the rest to lowercase. | +| `trim` | `boolean` | _(optional)_ When `true`, it will trim leading and trailing whitespaces on blur, triggering onChange if the value changes. | +| `inputMode` | `string` | _(optional)_ Define a [inputmode](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inputmode). | +| `autoresizeMaxRows` | `boolean` | _(optional)_ For `multiline`, set how many rows of text can be shown at max. | +| `characterCounter` | `boolean` or `string` | _(optional)_ For `multiline`, use a number to define the displayed max length. You can also use an object defining the [TextCounter](uilib/components/fragments/text-counter/) `variant` or properties. | +| `minLength` | `number` | _(optional)_ Validation for minimum length of the text (number of characters). | +| `maxLength` | `number` | _(optional)_ Validation for maximum length of the text (number of characters). | +| `pattern` | `string` | _(optional)_ Validation based on regex pattern. | +| `width` | `string` or `false` | _(optional)_ `false` for no width (use browser default), `small`, `medium` or `large` for predefined standard widths, `stretch` for fill available width. | +| `help` | `object` | _(optional)_ Provide a help button. Object consisting of `title` and `content`. | +| [Space](/uilib/layout/space/properties) | Various | _(optional)_ Spacing properties like `top` or `bottom` are supported. | ## Properties diff --git a/packages/dnb-eufemia/src/components/aria-live/AriaLive.tsx b/packages/dnb-eufemia/src/components/aria-live/AriaLive.tsx index d966dd69bc5..4d6d208fc4c 100644 --- a/packages/dnb-eufemia/src/components/aria-live/AriaLive.tsx +++ b/packages/dnb-eufemia/src/components/aria-live/AriaLive.tsx @@ -2,10 +2,11 @@ import React from 'react' import { AriaLiveAllProps } from './types' import useAriaLive from './useAriaLive' -export default function AriaLive(props: AriaLiveAllProps) { +export default function AriaLive({ element, ...props }: AriaLiveAllProps) { const ariaAttributes = useAriaLive(props) + const Element = element || 'section' - return
+ return } AriaLive._supportsSpacingProps = 'children' diff --git a/packages/dnb-eufemia/src/components/aria-live/__tests__/AriaLive.test.tsx b/packages/dnb-eufemia/src/components/aria-live/__tests__/AriaLive.test.tsx index 4d117084276..40225bba922 100644 --- a/packages/dnb-eufemia/src/components/aria-live/__tests__/AriaLive.test.tsx +++ b/packages/dnb-eufemia/src/components/aria-live/__tests__/AriaLive.test.tsx @@ -110,6 +110,24 @@ describe('AriaLive', () => { expect(element).toHaveAttribute('data-test', 'test-data') }) + it('should reset (remove) given message after a while', async () => { + render( + +

Announcement

+
+ ) + + const element = document.querySelector('.dnb-aria-live') + + await waitFor(() => { + expect(element).toHaveTextContent('Announcement') + }) + + await waitFor(() => { + expect(element).toHaveTextContent('') + }) + }) + it('should have dnb-sr-only class', () => { render( diff --git a/packages/dnb-eufemia/src/components/aria-live/types.ts b/packages/dnb-eufemia/src/components/aria-live/types.ts index e4631b28e40..87b17ad660f 100644 --- a/packages/dnb-eufemia/src/components/aria-live/types.ts +++ b/packages/dnb-eufemia/src/components/aria-live/types.ts @@ -1,4 +1,6 @@ export type AriaLiveProps = { + element?: React.ElementType + /** * The variant of the announcement. Can be 'text' or 'content'. */ diff --git a/packages/dnb-eufemia/src/components/aria-live/useAriaLive.tsx b/packages/dnb-eufemia/src/components/aria-live/useAriaLive.tsx index 3618d64b2ab..209cd8e38eb 100644 --- a/packages/dnb-eufemia/src/components/aria-live/useAriaLive.tsx +++ b/packages/dnb-eufemia/src/components/aria-live/useAriaLive.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import classnames from 'classnames' import { AriaLiveAllProps } from './types' import { extendPropsWithContext } from '../../shared/component-helper' @@ -37,6 +37,7 @@ const priorityConfig: { export default function useAriaLive(props: AriaLiveAllProps) { const [announcement, setAnnouncement] = useState('') + const timeoutRef = useRef(null) const { disabled = false, @@ -56,21 +57,37 @@ export default function useAriaLive(props: AriaLiveAllProps) { priorityConfig[props.priority || 'low'] ) + const showTextAnnouncement = delay > -1 + useEffect(() => { - if (delay > -1) { + if (showTextAnnouncement) { setAnnouncement('') + const isTest = process.env.NODE_ENV === 'test' const timer = setTimeout( - () => setAnnouncement(disabled ? '' : children), - (process.env.NODE_ENV === 'test' ? 0 : delay) ?? 1000 + () => { + if (!disabled) { + setAnnouncement(children) + } + + clearTimeout(timeoutRef.current) + timeoutRef.current = setTimeout( + () => setAnnouncement(''), + isTest ? 100 : delay + 1000 + ) + }, + (isTest ? 0 : delay) ?? 1000 ) - return () => clearTimeout(timer) + return () => { + clearTimeout(timer) + clearTimeout(timeoutRef.current) + } } - }, [disabled, delay, children]) + }, [delay, children, disabled, showTextAnnouncement]) return { - 'aria-live': !disabled ? politeness : 'off', + 'aria-live': disabled && !showTextAnnouncement ? 'off' : politeness, 'aria-atomic': atomic, 'aria-relevant': relevant, className: classnames( @@ -78,7 +95,7 @@ export default function useAriaLive(props: AriaLiveAllProps) { !showAnnouncement && 'dnb-sr-only', className ), - children: delay > -1 ? announcement : children, + children: showTextAnnouncement ? announcement : children, ...rest, } } diff --git a/packages/dnb-eufemia/src/components/textarea/Textarea.js b/packages/dnb-eufemia/src/components/textarea/Textarea.js index 01790ec997e..0349b13b1bf 100644 --- a/packages/dnb-eufemia/src/components/textarea/Textarea.js +++ b/packages/dnb-eufemia/src/components/textarea/Textarea.js @@ -550,9 +550,9 @@ export default class Textarea extends React.PureComponent { {characterCounter && ( {