@atlaskit/form
Version:
A form allows people to input information.
116 lines (102 loc) • 4.49 kB
JavaScript
/* character-counter.tsx generated by @compiled/babel-plugin v0.39.1 */
import "./character-counter.compiled.css";
import * as React from 'react';
import { ax, ix } from "@compiled/react/runtime";
import { useContext, useEffect, useRef, useState } from 'react';
import ErrorIcon from '@atlaskit/icon/core/status-error';
import { Flex, Text } from '@atlaskit/primitives/compiled';
import VisuallyHidden from '@atlaskit/visually-hidden';
import { FieldId } from './field-id-context';
// Extracted styles for character counter message container
const messageContainerStyles = {
root: "_11c8wadc _syaze6sf _1pfh1b66"
};
// Extracted styles for error icon wrapper, need to use css to override the default height
const errorIconWrapperStyles = null;
// Error icon with wrapper for character count violations
const ErrorIconWithWrapper = () => /*#__PURE__*/React.createElement("span", {
className: ax(["_1e0c1txw _4t3i7vkz _4cvr1h6o"])
}, /*#__PURE__*/React.createElement(ErrorIcon, {
label: "error",
size: "small"
}));
// Helper to pluralise "character(s)"
const pluralize = count => `character${count !== 1 ? 's' : ''}`;
/**
* __Character Counter__
*
* A character counter component that displays remaining characters for text input.
* Displays messages for over or under the maximum or minimum character limits.
*/
const CharacterCounter = ({
maxCharacters,
minCharacters,
currentValue,
overMaximumMessage,
underMaximumMessage,
underMinimumMessage,
shouldShowAsError = true,
inputId,
testId
}) => {
const [announcementText, setAnnouncementText] = useState('');
const debounceTimeoutRef = useRef(null);
// Resolve the field ID from context (form use) or inputId prop (standalone use)
const contextFieldId = useContext(FieldId);
const resolvedFieldId = contextFieldId || inputId;
const currentLength = (currentValue === null || currentValue === void 0 ? void 0 : currentValue.length) || 0;
// Check if character count violates limits
const isTooShort = minCharacters !== undefined && currentLength < minCharacters;
const isTooLong = maxCharacters !== undefined && currentLength > maxCharacters;
// Determine what to display based on the current value, the maximum and minimum character limits, and any custom messages
const getMessage = () => {
// Below minimum so show custom message or default
if (isTooShort) {
const needed = minCharacters - currentLength;
return underMinimumMessage || `${needed} more ${pluralize(needed)} needed`;
}
// Over maximum so show custom message or default
if (isTooLong) {
const over = currentLength - maxCharacters;
return overMaximumMessage || `${over} ${pluralize(over)} too many`;
}
// Within limits - show remaining count (if max is defined)
if (maxCharacters) {
const remaining = maxCharacters - currentLength;
return underMaximumMessage || `${remaining} ${pluralize(remaining)} remaining`;
}
// No message to show (min only limit satisfied)
return null;
};
const displayText = getMessage();
// Determine if the current character count violates limits
const displayAsError = (isTooShort || isTooLong) && shouldShowAsError;
// Debounce screen reader announcements so that it only reads the message when it input has settled
useEffect(() => {
// Debounce by 1 second to avoid announcing every keystroke
debounceTimeoutRef.current = setTimeout(() => {
setAnnouncementText(displayText || '');
}, 1000);
// Cleanup function clears the timeout when displayText changes or component unmounts
return () => {
clearTimeout(debounceTimeoutRef.current);
};
}, [displayText]);
// Don't render if there's no message to display (min only limit satisfied)
if (!displayText) {
return null;
}
return /*#__PURE__*/React.createElement(Flex, {
testId: testId
}, /*#__PURE__*/React.createElement(Flex, {
gap: "space.075",
xcss: messageContainerStyles.root
}, displayAsError && /*#__PURE__*/React.createElement(ErrorIconWithWrapper, null), /*#__PURE__*/React.createElement(Text, {
color: displayAsError ? 'color.text.danger' : 'color.text.subtlest',
size: "small",
id: resolvedFieldId ? `${resolvedFieldId}-character-counter` : undefined
}, displayText)), /*#__PURE__*/React.createElement(VisuallyHidden, null, /*#__PURE__*/React.createElement("div", {
"aria-live": "polite"
}, announcementText)));
};
export default CharacterCounter;