@atlaskit/form 14.3.1 → 14.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/codemods/__tests__/{not-yet-migrate-to-simplified-form.test.tsx → migrate-to-simplified-form.test.tsx} +1 -1
- package/codemods/migrate-data-testid-to-testid-prop.tsx +1 -1
- package/codemods/{not-yet-migrate-to-simplified-form.tsx → migrate-to-simplified-form.tsx} +1 -1
- package/dist/cjs/character-counter-field.compiled.css +3 -0
- package/dist/cjs/character-counter-field.js +142 -0
- package/dist/cjs/character-counter.compiled.css +8 -0
- package/dist/cjs/character-counter.js +130 -0
- package/dist/cjs/index.js +8 -1
- package/dist/es2019/character-counter-field.compiled.css +3 -0
- package/dist/es2019/character-counter-field.js +133 -0
- package/dist/es2019/character-counter.compiled.css +8 -0
- package/dist/es2019/character-counter.js +114 -0
- package/dist/es2019/index.js +2 -1
- package/dist/esm/character-counter-field.compiled.css +3 -0
- package/dist/esm/character-counter-field.js +133 -0
- package/dist/esm/character-counter.compiled.css +8 -0
- package/dist/esm/character-counter.js +122 -0
- package/dist/esm/index.js +2 -1
- package/dist/types/character-counter-field.d.ts +51 -0
- package/dist/types/character-counter.d.ts +69 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types-ts4.5/character-counter-field.d.ts +51 -0
- package/dist/types-ts4.5/character-counter.d.ts +69 -0
- package/dist/types-ts4.5/index.d.ts +2 -0
- package/package.json +15 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# @atlaskit/form
|
|
2
2
|
|
|
3
|
+
## 14.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`c19181795ec37`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/c19181795ec37) -
|
|
8
|
+
Introduces `CharacterCounterField`, a new field component that provides automatic character count
|
|
9
|
+
validation and display. This component wraps the standard `Field` with built-in support for
|
|
10
|
+
minimum and maximum character limits, displaying a real-time character counter to users.
|
|
11
|
+
|
|
12
|
+
**Key features:**
|
|
13
|
+
- Automatic validation for `minCharacters` and `maxCharacters` limits
|
|
14
|
+
- Integrated character counter display with error states
|
|
15
|
+
- Customizable messaging for character limit violations via `overMaximumMessage`,
|
|
16
|
+
`underMaximumMessage`, and `underMinimumMessage` props
|
|
17
|
+
- Seamless integration with existing Field validation through the `validate` prop
|
|
18
|
+
- Full accessibility support with proper ARIA announcements for screen readers
|
|
19
|
+
- Supports both `TextField` and `TextArea` components via render prop pattern
|
|
20
|
+
|
|
21
|
+
**Example usage:**
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
<CharacterCounterField
|
|
25
|
+
name="description"
|
|
26
|
+
label="Description"
|
|
27
|
+
maxCharacters={200}
|
|
28
|
+
minCharacters={10}
|
|
29
|
+
helperMessage="Provide a brief description"
|
|
30
|
+
>
|
|
31
|
+
{({ fieldProps }) => <TextArea {...fieldProps} />}
|
|
32
|
+
</CharacterCounterField>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This component simplifies the implementation of character-limited inputs by combining validation,
|
|
36
|
+
error handling, and counter display in a single, accessible component.
|
|
37
|
+
|
|
38
|
+
## 14.3.2
|
|
39
|
+
|
|
40
|
+
### Patch Changes
|
|
41
|
+
|
|
42
|
+
- [`a60a82196851a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/a60a82196851a) -
|
|
43
|
+
Internal refactors to remove unused variables. No functional or public changes.
|
|
44
|
+
- Updated dependencies
|
|
45
|
+
|
|
3
46
|
## 14.3.1
|
|
4
47
|
|
|
5
48
|
### Patch Changes
|
|
@@ -98,7 +98,7 @@ const migrateDataTestIdToTestIdProp = (j: JSCodeshift, collection: Collection<an
|
|
|
98
98
|
let dataTestIdAttr: any = null;
|
|
99
99
|
let dataTestIdValue: any = null;
|
|
100
100
|
|
|
101
|
-
htmlForm.openingElement.attributes?.forEach((attr
|
|
101
|
+
htmlForm.openingElement.attributes?.forEach((attr) => {
|
|
102
102
|
// Find data-testid attribute
|
|
103
103
|
if (
|
|
104
104
|
attr.type === 'JSXAttribute' &&
|
|
@@ -83,7 +83,7 @@ const generateFeatureFlag = (filePath: string): string => {
|
|
|
83
83
|
const flag = `platform-design_system_team-form--${suffix}`;
|
|
84
84
|
return flag.length > 50 ? flag.substring(0, 50) : flag;
|
|
85
85
|
}
|
|
86
|
-
} catch
|
|
86
|
+
} catch {
|
|
87
87
|
// Continue searching if JSON parsing fails
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/* character-counter-field.tsx generated by @compiled/babel-plugin v0.38.1 */
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
5
|
+
var _typeof = require("@babel/runtime/helpers/typeof");
|
|
6
|
+
Object.defineProperty(exports, "__esModule", {
|
|
7
|
+
value: true
|
|
8
|
+
});
|
|
9
|
+
exports.default = CharacterCounterField;
|
|
10
|
+
require("./character-counter-field.compiled.css");
|
|
11
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
12
|
+
var React = _react;
|
|
13
|
+
var _runtime = require("@compiled/react/runtime");
|
|
14
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
15
|
+
var _characterCounter = require("./character-counter");
|
|
16
|
+
var _field = _interopRequireDefault(require("./field"));
|
|
17
|
+
var _messages = require("./messages");
|
|
18
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
|
|
19
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
20
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
21
|
+
// Override label specific margin block end to fix double spacing issue
|
|
22
|
+
var fieldWrapperStyles = {
|
|
23
|
+
root: "_14uxze3t"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Override helper message margins to fix inconsistent spacing issue
|
|
27
|
+
var helperMessageWrapperStyles = {
|
|
28
|
+
root: "_6rth1b66 _6ul5ze3t"
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* __Character Counter Field__
|
|
32
|
+
*
|
|
33
|
+
* A field component that wraps the standard Field with automatic character count validation.
|
|
34
|
+
* Validates minimum and maximum character limits and displays a character counter.
|
|
35
|
+
*/
|
|
36
|
+
function CharacterCounterField(_ref) {
|
|
37
|
+
var maxCharacters = _ref.maxCharacters,
|
|
38
|
+
minCharacters = _ref.minCharacters,
|
|
39
|
+
children = _ref.children,
|
|
40
|
+
userValidate = _ref.validate,
|
|
41
|
+
overMaximumMessage = _ref.overMaximumMessage,
|
|
42
|
+
underMaximumMessage = _ref.underMaximumMessage,
|
|
43
|
+
underMinimumMessage = _ref.underMinimumMessage,
|
|
44
|
+
helperMessage = _ref.helperMessage,
|
|
45
|
+
defaultValue = _ref.defaultValue,
|
|
46
|
+
id = _ref.id,
|
|
47
|
+
isRequired = _ref.isRequired,
|
|
48
|
+
isDisabled = _ref.isDisabled,
|
|
49
|
+
label = _ref.label,
|
|
50
|
+
elementAfterLabel = _ref.elementAfterLabel,
|
|
51
|
+
name = _ref.name,
|
|
52
|
+
testId = _ref.testId;
|
|
53
|
+
// Default validation function for character limits
|
|
54
|
+
// __TOO_SHORT__ and __TOO_LONG__ are default error codes recognised by the CharacterCounter component
|
|
55
|
+
var validateCharacterCount = function validateCharacterCount(value) {
|
|
56
|
+
var stringValue = String(value || '');
|
|
57
|
+
var length = stringValue.length;
|
|
58
|
+
|
|
59
|
+
// Check minimum length
|
|
60
|
+
if (minCharacters !== undefined && length < minCharacters) {
|
|
61
|
+
return '__TOO_SHORT__';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Check maximum length
|
|
65
|
+
if (maxCharacters !== undefined && length > maxCharacters) {
|
|
66
|
+
return '__TOO_LONG__';
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Combine user validation and character validation
|
|
72
|
+
// Any user defined validation takes priority over character validation
|
|
73
|
+
// If there is no user defined validation for character limits e.g. used maxLength prior to CharacterCounterField,
|
|
74
|
+
// use the default error codes and display the appropriate error message
|
|
75
|
+
var combinedValidate = function combinedValidate(value, formState, fieldState) {
|
|
76
|
+
// First run character validation
|
|
77
|
+
var characterError = validateCharacterCount(value);
|
|
78
|
+
|
|
79
|
+
// Then run user's custom validation if provided
|
|
80
|
+
var userError = userValidate === null || userValidate === void 0 ? void 0 : userValidate(value, formState, fieldState);
|
|
81
|
+
|
|
82
|
+
// If user validation returns a promise, handle it
|
|
83
|
+
if (userError instanceof Promise) {
|
|
84
|
+
return userError.then(function (error) {
|
|
85
|
+
// User error takes priority over character validation
|
|
86
|
+
return error || characterError;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// User error takes priority over character validation
|
|
91
|
+
return userError || characterError;
|
|
92
|
+
};
|
|
93
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
94
|
+
className: (0, _runtime.ax)([fieldWrapperStyles.root])
|
|
95
|
+
}, /*#__PURE__*/React.createElement(_field.default, {
|
|
96
|
+
defaultValue: defaultValue,
|
|
97
|
+
id: id,
|
|
98
|
+
isRequired: isRequired,
|
|
99
|
+
isDisabled: isDisabled,
|
|
100
|
+
label: label,
|
|
101
|
+
elementAfterLabel: elementAfterLabel,
|
|
102
|
+
name: name,
|
|
103
|
+
testId: testId,
|
|
104
|
+
validate: combinedValidate
|
|
105
|
+
}, function (_ref2) {
|
|
106
|
+
var extendedFieldProps = _ref2.fieldProps,
|
|
107
|
+
error = _ref2.error,
|
|
108
|
+
valid = _ref2.valid,
|
|
109
|
+
meta = _ref2.meta;
|
|
110
|
+
// Determine if error is a character count violation (handled by CharacterCounter)
|
|
111
|
+
// or an external validation error (needs ErrorMessage)
|
|
112
|
+
var isCharacterCountViolation = error === '__TOO_SHORT__' || error === '__TOO_LONG__';
|
|
113
|
+
var showExternalError = error && !isCharacterCountViolation;
|
|
114
|
+
var showCharacterCounter = (maxCharacters !== undefined || minCharacters !== undefined) && !showExternalError;
|
|
115
|
+
|
|
116
|
+
// Extend aria-describedby to reference the appropriate message component
|
|
117
|
+
var fieldPropsWithCounter = _objectSpread(_objectSpread({}, extendedFieldProps), {}, {
|
|
118
|
+
'aria-describedby': showCharacterCounter ? "".concat(extendedFieldProps['aria-describedby'], " ").concat(extendedFieldProps.id, "-character-counter").trim() : extendedFieldProps['aria-describedby']
|
|
119
|
+
});
|
|
120
|
+
return /*#__PURE__*/React.createElement(_react.Fragment, null, /*#__PURE__*/React.createElement(_messages.MessageWrapper, null, helperMessage && /*#__PURE__*/React.createElement("div", {
|
|
121
|
+
className: (0, _runtime.ax)([helperMessageWrapperStyles.root])
|
|
122
|
+
}, /*#__PURE__*/React.createElement(_messages.HelperMessage, {
|
|
123
|
+
testId: "".concat(testId, "-helper")
|
|
124
|
+
}, helperMessage))), children({
|
|
125
|
+
fieldProps: fieldPropsWithCounter,
|
|
126
|
+
error: error,
|
|
127
|
+
valid: valid,
|
|
128
|
+
meta: meta
|
|
129
|
+
}), /*#__PURE__*/React.createElement(_messages.MessageWrapper, null, showExternalError && /*#__PURE__*/React.createElement(_messages.ErrorMessage, {
|
|
130
|
+
testId: "".concat(testId, "-error")
|
|
131
|
+
}, error)), showCharacterCounter && /*#__PURE__*/React.createElement(_characterCounter.CharacterCounter, {
|
|
132
|
+
maxCharacters: maxCharacters,
|
|
133
|
+
minCharacters: minCharacters,
|
|
134
|
+
currentValue: String(extendedFieldProps.value || ''),
|
|
135
|
+
shouldShowAsError: isCharacterCountViolation,
|
|
136
|
+
overMaximumMessage: overMaximumMessage,
|
|
137
|
+
underMaximumMessage: underMaximumMessage,
|
|
138
|
+
underMinimumMessage: underMinimumMessage,
|
|
139
|
+
testId: "".concat(testId, "-character-counter")
|
|
140
|
+
}));
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
._11c8dcr7{font:var(--ds-font-body-UNSAFE_small,normal 400 9pt/1pc ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",Ubuntu,"Helvetica Neue",sans-serif)}
|
|
2
|
+
._zulp12x7{gap:var(--ds-space-075,6px)}
|
|
3
|
+
._1bah1q9y{justify-content:baseline}
|
|
4
|
+
._1e0c1txw{display:flex}
|
|
5
|
+
._1pfh1b66{margin-block-start:var(--ds-space-050,4px)}
|
|
6
|
+
._4cvr1h6o{align-items:center}
|
|
7
|
+
._4t3i7vkz{height:1pc}
|
|
8
|
+
._syaze6sf{color:var(--ds-text-danger,#ae2a19)}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/* character-counter.tsx generated by @compiled/babel-plugin v0.38.1 */
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
5
|
+
var _typeof = require("@babel/runtime/helpers/typeof");
|
|
6
|
+
Object.defineProperty(exports, "__esModule", {
|
|
7
|
+
value: true
|
|
8
|
+
});
|
|
9
|
+
exports.CharacterCounter = void 0;
|
|
10
|
+
require("./character-counter.compiled.css");
|
|
11
|
+
var _react = _interopRequireWildcard(require("react"));
|
|
12
|
+
var React = _react;
|
|
13
|
+
var _runtime = require("@compiled/react/runtime");
|
|
14
|
+
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
15
|
+
var _statusErrorError = _interopRequireDefault(require("@atlaskit/icon/core/migration/status-error--error"));
|
|
16
|
+
var _compiled = require("@atlaskit/primitives/compiled");
|
|
17
|
+
var _visuallyHidden = _interopRequireDefault(require("@atlaskit/visually-hidden"));
|
|
18
|
+
var _fieldIdContext = require("./field-id-context");
|
|
19
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
|
|
20
|
+
// Extracted styles for character counter message container
|
|
21
|
+
var messageContainerStyles = null;
|
|
22
|
+
|
|
23
|
+
// Extracted styles for error icon wrapper
|
|
24
|
+
var errorIconWrapperStyles = null;
|
|
25
|
+
|
|
26
|
+
// Error icon with wrapper for character count violations
|
|
27
|
+
var ErrorIconWithWrapper = function ErrorIconWithWrapper() {
|
|
28
|
+
return /*#__PURE__*/React.createElement("span", {
|
|
29
|
+
className: (0, _runtime.ax)(["_1e0c1txw _4t3i7vkz _4cvr1h6o"])
|
|
30
|
+
}, /*#__PURE__*/React.createElement(_statusErrorError.default, {
|
|
31
|
+
LEGACY_margin: "0 -2px 0 0",
|
|
32
|
+
LEGACY_size: "small",
|
|
33
|
+
label: "error",
|
|
34
|
+
size: "small"
|
|
35
|
+
}));
|
|
36
|
+
};
|
|
37
|
+
// Helper to pluralise "character(s)"
|
|
38
|
+
var pluralize = function pluralize(count) {
|
|
39
|
+
return "character".concat(count !== 1 ? 's' : '');
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* __Character Counter__
|
|
44
|
+
*
|
|
45
|
+
* A character counter component that displays remaining characters for text input.
|
|
46
|
+
* Displays messages for over or under the maximum or minimum character limits.
|
|
47
|
+
*/
|
|
48
|
+
var CharacterCounter = exports.CharacterCounter = function CharacterCounter(_ref) {
|
|
49
|
+
var maxCharacters = _ref.maxCharacters,
|
|
50
|
+
minCharacters = _ref.minCharacters,
|
|
51
|
+
currentValue = _ref.currentValue,
|
|
52
|
+
overMaximumMessage = _ref.overMaximumMessage,
|
|
53
|
+
underMaximumMessage = _ref.underMaximumMessage,
|
|
54
|
+
underMinimumMessage = _ref.underMinimumMessage,
|
|
55
|
+
_ref$shouldShowAsErro = _ref.shouldShowAsError,
|
|
56
|
+
shouldShowAsError = _ref$shouldShowAsErro === void 0 ? true : _ref$shouldShowAsErro,
|
|
57
|
+
inputId = _ref.inputId,
|
|
58
|
+
testId = _ref.testId;
|
|
59
|
+
var _useState = (0, _react.useState)(''),
|
|
60
|
+
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
|
|
61
|
+
announcementText = _useState2[0],
|
|
62
|
+
setAnnouncementText = _useState2[1];
|
|
63
|
+
var debounceTimeoutRef = (0, _react.useRef)(null);
|
|
64
|
+
|
|
65
|
+
// Resolve the field ID from context (form use) or inputId prop (standalone use)
|
|
66
|
+
var contextFieldId = (0, _react.useContext)(_fieldIdContext.FieldId);
|
|
67
|
+
var resolvedFieldId = contextFieldId || inputId;
|
|
68
|
+
var currentLength = (currentValue === null || currentValue === void 0 ? void 0 : currentValue.length) || 0;
|
|
69
|
+
|
|
70
|
+
// Check if character count violates limits
|
|
71
|
+
var isTooShort = minCharacters !== undefined && currentLength < minCharacters;
|
|
72
|
+
var isTooLong = maxCharacters !== undefined && currentLength > maxCharacters;
|
|
73
|
+
|
|
74
|
+
// Determine what to display based on the current value, the maximum and minimum character limits, and any custom messages
|
|
75
|
+
var getMessage = function getMessage() {
|
|
76
|
+
// Below minimum so show custom message or default
|
|
77
|
+
if (isTooShort) {
|
|
78
|
+
var needed = minCharacters - currentLength;
|
|
79
|
+
return underMinimumMessage || "".concat(needed, " more ").concat(pluralize(needed), " needed");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Over maximum so show custom message or default
|
|
83
|
+
if (isTooLong) {
|
|
84
|
+
var over = currentLength - maxCharacters;
|
|
85
|
+
return overMaximumMessage || "".concat(over, " ").concat(pluralize(over), " too many");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Within limits - show remaining count (if max is defined)
|
|
89
|
+
if (maxCharacters) {
|
|
90
|
+
var remaining = maxCharacters - currentLength;
|
|
91
|
+
return underMaximumMessage || "".concat(remaining, " ").concat(pluralize(remaining), " remaining");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// No message to show (min only limit satisfied)
|
|
95
|
+
return null;
|
|
96
|
+
};
|
|
97
|
+
var displayText = getMessage();
|
|
98
|
+
|
|
99
|
+
// Determine if the current character count violates limits
|
|
100
|
+
var displayAsError = (isTooShort || isTooLong) && shouldShowAsError;
|
|
101
|
+
|
|
102
|
+
// Debounce screen reader announcements so that it only reads the message when it input has settled
|
|
103
|
+
(0, _react.useEffect)(function () {
|
|
104
|
+
// Debounce by 1 second to avoid announcing every keystroke
|
|
105
|
+
debounceTimeoutRef.current = setTimeout(function () {
|
|
106
|
+
setAnnouncementText(displayText || '');
|
|
107
|
+
}, 1000);
|
|
108
|
+
|
|
109
|
+
// Cleanup function clears the timeout when displayText changes or component unmounts
|
|
110
|
+
return function () {
|
|
111
|
+
clearTimeout(debounceTimeoutRef.current);
|
|
112
|
+
};
|
|
113
|
+
}, [displayText]);
|
|
114
|
+
|
|
115
|
+
// Don't render if there's no message to display (min only limit satisfied)
|
|
116
|
+
if (!displayText) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
120
|
+
"data-testid": testId
|
|
121
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
122
|
+
className: (0, _runtime.ax)(["_zulp12x7 _11c8dcr7 _1e0c1txw _1bah1q9y _syaze6sf _1pfh1b66"])
|
|
123
|
+
}, displayAsError && /*#__PURE__*/React.createElement(ErrorIconWithWrapper, null), /*#__PURE__*/React.createElement(_compiled.Text, {
|
|
124
|
+
color: displayAsError ? 'color.text.danger' : 'color.text.subtlest',
|
|
125
|
+
size: "small",
|
|
126
|
+
id: resolvedFieldId ? "".concat(resolvedFieldId, "-character-counter") : undefined
|
|
127
|
+
}, displayText)), /*#__PURE__*/React.createElement(_visuallyHidden.default, null, /*#__PURE__*/React.createElement("div", {
|
|
128
|
+
"aria-live": "polite"
|
|
129
|
+
}, announcementText)));
|
|
130
|
+
};
|
package/dist/cjs/index.js
CHANGED
|
@@ -4,6 +4,12 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
+
Object.defineProperty(exports, "CharacterCounterField", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function get() {
|
|
10
|
+
return _characterCounterField.default;
|
|
11
|
+
}
|
|
12
|
+
});
|
|
7
13
|
Object.defineProperty(exports, "CheckboxField", {
|
|
8
14
|
enumerable: true,
|
|
9
15
|
get: function get() {
|
|
@@ -111,4 +117,5 @@ var _label = require("./label");
|
|
|
111
117
|
var _messages = require("./messages");
|
|
112
118
|
var _fieldset = _interopRequireDefault(require("./fieldset"));
|
|
113
119
|
var _requiredAsterisk = _interopRequireDefault(require("./required-asterisk"));
|
|
114
|
-
var _useFormState = require("./use-form-state");
|
|
120
|
+
var _useFormState = require("./use-form-state");
|
|
121
|
+
var _characterCounterField = _interopRequireDefault(require("./character-counter-field"));
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* character-counter-field.tsx generated by @compiled/babel-plugin v0.38.1 */
|
|
2
|
+
import "./character-counter-field.compiled.css";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { ax, ix } from "@compiled/react/runtime";
|
|
5
|
+
import { Fragment } from 'react';
|
|
6
|
+
import { CharacterCounter } from './character-counter';
|
|
7
|
+
import Field from './field';
|
|
8
|
+
import { ErrorMessage, HelperMessage, MessageWrapper } from './messages';
|
|
9
|
+
// Override label specific margin block end to fix double spacing issue
|
|
10
|
+
const fieldWrapperStyles = {
|
|
11
|
+
root: "_14uxze3t"
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Override helper message margins to fix inconsistent spacing issue
|
|
15
|
+
const helperMessageWrapperStyles = {
|
|
16
|
+
root: "_6rth1b66 _6ul5ze3t"
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* __Character Counter Field__
|
|
20
|
+
*
|
|
21
|
+
* A field component that wraps the standard Field with automatic character count validation.
|
|
22
|
+
* Validates minimum and maximum character limits and displays a character counter.
|
|
23
|
+
*/
|
|
24
|
+
export default function CharacterCounterField({
|
|
25
|
+
maxCharacters,
|
|
26
|
+
minCharacters,
|
|
27
|
+
children,
|
|
28
|
+
validate: userValidate,
|
|
29
|
+
overMaximumMessage,
|
|
30
|
+
underMaximumMessage,
|
|
31
|
+
underMinimumMessage,
|
|
32
|
+
helperMessage,
|
|
33
|
+
defaultValue,
|
|
34
|
+
id,
|
|
35
|
+
isRequired,
|
|
36
|
+
isDisabled,
|
|
37
|
+
label,
|
|
38
|
+
elementAfterLabel,
|
|
39
|
+
name,
|
|
40
|
+
testId
|
|
41
|
+
}) {
|
|
42
|
+
// Default validation function for character limits
|
|
43
|
+
// __TOO_SHORT__ and __TOO_LONG__ are default error codes recognised by the CharacterCounter component
|
|
44
|
+
const validateCharacterCount = value => {
|
|
45
|
+
const stringValue = String(value || '');
|
|
46
|
+
const length = stringValue.length;
|
|
47
|
+
|
|
48
|
+
// Check minimum length
|
|
49
|
+
if (minCharacters !== undefined && length < minCharacters) {
|
|
50
|
+
return '__TOO_SHORT__';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check maximum length
|
|
54
|
+
if (maxCharacters !== undefined && length > maxCharacters) {
|
|
55
|
+
return '__TOO_LONG__';
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Combine user validation and character validation
|
|
61
|
+
// Any user defined validation takes priority over character validation
|
|
62
|
+
// If there is no user defined validation for character limits e.g. used maxLength prior to CharacterCounterField,
|
|
63
|
+
// use the default error codes and display the appropriate error message
|
|
64
|
+
const combinedValidate = (value, formState, fieldState) => {
|
|
65
|
+
// First run character validation
|
|
66
|
+
const characterError = validateCharacterCount(value);
|
|
67
|
+
|
|
68
|
+
// Then run user's custom validation if provided
|
|
69
|
+
const userError = userValidate === null || userValidate === void 0 ? void 0 : userValidate(value, formState, fieldState);
|
|
70
|
+
|
|
71
|
+
// If user validation returns a promise, handle it
|
|
72
|
+
if (userError instanceof Promise) {
|
|
73
|
+
return userError.then(error => {
|
|
74
|
+
// User error takes priority over character validation
|
|
75
|
+
return error || characterError;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// User error takes priority over character validation
|
|
80
|
+
return userError || characterError;
|
|
81
|
+
};
|
|
82
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
83
|
+
className: ax([fieldWrapperStyles.root])
|
|
84
|
+
}, /*#__PURE__*/React.createElement(Field, {
|
|
85
|
+
defaultValue: defaultValue,
|
|
86
|
+
id: id,
|
|
87
|
+
isRequired: isRequired,
|
|
88
|
+
isDisabled: isDisabled,
|
|
89
|
+
label: label,
|
|
90
|
+
elementAfterLabel: elementAfterLabel,
|
|
91
|
+
name: name,
|
|
92
|
+
testId: testId,
|
|
93
|
+
validate: combinedValidate
|
|
94
|
+
}, ({
|
|
95
|
+
fieldProps: extendedFieldProps,
|
|
96
|
+
error,
|
|
97
|
+
valid,
|
|
98
|
+
meta
|
|
99
|
+
}) => {
|
|
100
|
+
// Determine if error is a character count violation (handled by CharacterCounter)
|
|
101
|
+
// or an external validation error (needs ErrorMessage)
|
|
102
|
+
const isCharacterCountViolation = error === '__TOO_SHORT__' || error === '__TOO_LONG__';
|
|
103
|
+
const showExternalError = error && !isCharacterCountViolation;
|
|
104
|
+
const showCharacterCounter = (maxCharacters !== undefined || minCharacters !== undefined) && !showExternalError;
|
|
105
|
+
|
|
106
|
+
// Extend aria-describedby to reference the appropriate message component
|
|
107
|
+
const fieldPropsWithCounter = {
|
|
108
|
+
...extendedFieldProps,
|
|
109
|
+
'aria-describedby': showCharacterCounter ? `${extendedFieldProps['aria-describedby']} ${extendedFieldProps.id}-character-counter`.trim() : extendedFieldProps['aria-describedby']
|
|
110
|
+
};
|
|
111
|
+
return /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(MessageWrapper, null, helperMessage && /*#__PURE__*/React.createElement("div", {
|
|
112
|
+
className: ax([helperMessageWrapperStyles.root])
|
|
113
|
+
}, /*#__PURE__*/React.createElement(HelperMessage, {
|
|
114
|
+
testId: `${testId}-helper`
|
|
115
|
+
}, helperMessage))), children({
|
|
116
|
+
fieldProps: fieldPropsWithCounter,
|
|
117
|
+
error,
|
|
118
|
+
valid,
|
|
119
|
+
meta
|
|
120
|
+
}), /*#__PURE__*/React.createElement(MessageWrapper, null, showExternalError && /*#__PURE__*/React.createElement(ErrorMessage, {
|
|
121
|
+
testId: `${testId}-error`
|
|
122
|
+
}, error)), showCharacterCounter && /*#__PURE__*/React.createElement(CharacterCounter, {
|
|
123
|
+
maxCharacters: maxCharacters,
|
|
124
|
+
minCharacters: minCharacters,
|
|
125
|
+
currentValue: String(extendedFieldProps.value || ''),
|
|
126
|
+
shouldShowAsError: isCharacterCountViolation,
|
|
127
|
+
overMaximumMessage: overMaximumMessage,
|
|
128
|
+
underMaximumMessage: underMaximumMessage,
|
|
129
|
+
underMinimumMessage: underMinimumMessage,
|
|
130
|
+
testId: `${testId}-character-counter`
|
|
131
|
+
}));
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
._11c8dcr7{font:var(--ds-font-body-UNSAFE_small,normal 400 9pt/1pc ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",Ubuntu,"Helvetica Neue",sans-serif)}
|
|
2
|
+
._zulp12x7{gap:var(--ds-space-075,6px)}
|
|
3
|
+
._1bah1q9y{justify-content:baseline}
|
|
4
|
+
._1e0c1txw{display:flex}
|
|
5
|
+
._1pfh1b66{margin-block-start:var(--ds-space-050,4px)}
|
|
6
|
+
._4cvr1h6o{align-items:center}
|
|
7
|
+
._4t3i7vkz{height:1pc}
|
|
8
|
+
._syaze6sf{color:var(--ds-text-danger,#ae2a19)}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/* character-counter.tsx generated by @compiled/babel-plugin v0.38.1 */
|
|
2
|
+
import "./character-counter.compiled.css";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { ax, ix } from "@compiled/react/runtime";
|
|
5
|
+
import { useContext, useEffect, useRef, useState } from 'react';
|
|
6
|
+
import ErrorIcon from '@atlaskit/icon/core/migration/status-error--error';
|
|
7
|
+
import { Text } from '@atlaskit/primitives/compiled';
|
|
8
|
+
import VisuallyHidden from '@atlaskit/visually-hidden';
|
|
9
|
+
import { FieldId } from './field-id-context';
|
|
10
|
+
|
|
11
|
+
// Extracted styles for character counter message container
|
|
12
|
+
const messageContainerStyles = null;
|
|
13
|
+
|
|
14
|
+
// Extracted styles for error icon wrapper
|
|
15
|
+
const errorIconWrapperStyles = null;
|
|
16
|
+
|
|
17
|
+
// Error icon with wrapper for character count violations
|
|
18
|
+
const ErrorIconWithWrapper = () => /*#__PURE__*/React.createElement("span", {
|
|
19
|
+
className: ax(["_1e0c1txw _4t3i7vkz _4cvr1h6o"])
|
|
20
|
+
}, /*#__PURE__*/React.createElement(ErrorIcon, {
|
|
21
|
+
LEGACY_margin: "0 -2px 0 0",
|
|
22
|
+
LEGACY_size: "small",
|
|
23
|
+
label: "error",
|
|
24
|
+
size: "small"
|
|
25
|
+
}));
|
|
26
|
+
// Helper to pluralise "character(s)"
|
|
27
|
+
const pluralize = count => `character${count !== 1 ? 's' : ''}`;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* __Character Counter__
|
|
31
|
+
*
|
|
32
|
+
* A character counter component that displays remaining characters for text input.
|
|
33
|
+
* Displays messages for over or under the maximum or minimum character limits.
|
|
34
|
+
*/
|
|
35
|
+
export const CharacterCounter = ({
|
|
36
|
+
maxCharacters,
|
|
37
|
+
minCharacters,
|
|
38
|
+
currentValue,
|
|
39
|
+
overMaximumMessage,
|
|
40
|
+
underMaximumMessage,
|
|
41
|
+
underMinimumMessage,
|
|
42
|
+
shouldShowAsError = true,
|
|
43
|
+
inputId,
|
|
44
|
+
testId
|
|
45
|
+
}) => {
|
|
46
|
+
const [announcementText, setAnnouncementText] = useState('');
|
|
47
|
+
const debounceTimeoutRef = useRef(null);
|
|
48
|
+
|
|
49
|
+
// Resolve the field ID from context (form use) or inputId prop (standalone use)
|
|
50
|
+
const contextFieldId = useContext(FieldId);
|
|
51
|
+
const resolvedFieldId = contextFieldId || inputId;
|
|
52
|
+
const currentLength = (currentValue === null || currentValue === void 0 ? void 0 : currentValue.length) || 0;
|
|
53
|
+
|
|
54
|
+
// Check if character count violates limits
|
|
55
|
+
const isTooShort = minCharacters !== undefined && currentLength < minCharacters;
|
|
56
|
+
const isTooLong = maxCharacters !== undefined && currentLength > maxCharacters;
|
|
57
|
+
|
|
58
|
+
// Determine what to display based on the current value, the maximum and minimum character limits, and any custom messages
|
|
59
|
+
const getMessage = () => {
|
|
60
|
+
// Below minimum so show custom message or default
|
|
61
|
+
if (isTooShort) {
|
|
62
|
+
const needed = minCharacters - currentLength;
|
|
63
|
+
return underMinimumMessage || `${needed} more ${pluralize(needed)} needed`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Over maximum so show custom message or default
|
|
67
|
+
if (isTooLong) {
|
|
68
|
+
const over = currentLength - maxCharacters;
|
|
69
|
+
return overMaximumMessage || `${over} ${pluralize(over)} too many`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Within limits - show remaining count (if max is defined)
|
|
73
|
+
if (maxCharacters) {
|
|
74
|
+
const remaining = maxCharacters - currentLength;
|
|
75
|
+
return underMaximumMessage || `${remaining} ${pluralize(remaining)} remaining`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// No message to show (min only limit satisfied)
|
|
79
|
+
return null;
|
|
80
|
+
};
|
|
81
|
+
const displayText = getMessage();
|
|
82
|
+
|
|
83
|
+
// Determine if the current character count violates limits
|
|
84
|
+
const displayAsError = (isTooShort || isTooLong) && shouldShowAsError;
|
|
85
|
+
|
|
86
|
+
// Debounce screen reader announcements so that it only reads the message when it input has settled
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
// Debounce by 1 second to avoid announcing every keystroke
|
|
89
|
+
debounceTimeoutRef.current = setTimeout(() => {
|
|
90
|
+
setAnnouncementText(displayText || '');
|
|
91
|
+
}, 1000);
|
|
92
|
+
|
|
93
|
+
// Cleanup function clears the timeout when displayText changes or component unmounts
|
|
94
|
+
return () => {
|
|
95
|
+
clearTimeout(debounceTimeoutRef.current);
|
|
96
|
+
};
|
|
97
|
+
}, [displayText]);
|
|
98
|
+
|
|
99
|
+
// Don't render if there's no message to display (min only limit satisfied)
|
|
100
|
+
if (!displayText) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
104
|
+
"data-testid": testId
|
|
105
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
106
|
+
className: ax(["_zulp12x7 _11c8dcr7 _1e0c1txw _1bah1q9y _syaze6sf _1pfh1b66"])
|
|
107
|
+
}, displayAsError && /*#__PURE__*/React.createElement(ErrorIconWithWrapper, null), /*#__PURE__*/React.createElement(Text, {
|
|
108
|
+
color: displayAsError ? 'color.text.danger' : 'color.text.subtlest',
|
|
109
|
+
size: "small",
|
|
110
|
+
id: resolvedFieldId ? `${resolvedFieldId}-character-counter` : undefined
|
|
111
|
+
}, displayText)), /*#__PURE__*/React.createElement(VisuallyHidden, null, /*#__PURE__*/React.createElement("div", {
|
|
112
|
+
"aria-live": "polite"
|
|
113
|
+
}, announcementText)));
|
|
114
|
+
};
|
package/dist/es2019/index.js
CHANGED
|
@@ -9,4 +9,5 @@ export { Label, Legend } from './label';
|
|
|
9
9
|
export { HelperMessage, ErrorMessage, MessageWrapper, ValidMessage } from './messages';
|
|
10
10
|
export { default as Fieldset } from './fieldset';
|
|
11
11
|
export { default as RequiredAsterisk } from './required-asterisk';
|
|
12
|
-
export { useFormState } from './use-form-state';
|
|
12
|
+
export { useFormState } from './use-form-state';
|
|
13
|
+
export { default as CharacterCounterField } from './character-counter-field';
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* character-counter-field.tsx generated by @compiled/babel-plugin v0.38.1 */
|
|
2
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
3
|
+
import "./character-counter-field.compiled.css";
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { ax, ix } from "@compiled/react/runtime";
|
|
6
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
7
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
8
|
+
import { Fragment } from 'react';
|
|
9
|
+
import { CharacterCounter } from './character-counter';
|
|
10
|
+
import Field from './field';
|
|
11
|
+
import { ErrorMessage, HelperMessage, MessageWrapper } from './messages';
|
|
12
|
+
// Override label specific margin block end to fix double spacing issue
|
|
13
|
+
var fieldWrapperStyles = {
|
|
14
|
+
root: "_14uxze3t"
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Override helper message margins to fix inconsistent spacing issue
|
|
18
|
+
var helperMessageWrapperStyles = {
|
|
19
|
+
root: "_6rth1b66 _6ul5ze3t"
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* __Character Counter Field__
|
|
23
|
+
*
|
|
24
|
+
* A field component that wraps the standard Field with automatic character count validation.
|
|
25
|
+
* Validates minimum and maximum character limits and displays a character counter.
|
|
26
|
+
*/
|
|
27
|
+
export default function CharacterCounterField(_ref) {
|
|
28
|
+
var maxCharacters = _ref.maxCharacters,
|
|
29
|
+
minCharacters = _ref.minCharacters,
|
|
30
|
+
children = _ref.children,
|
|
31
|
+
userValidate = _ref.validate,
|
|
32
|
+
overMaximumMessage = _ref.overMaximumMessage,
|
|
33
|
+
underMaximumMessage = _ref.underMaximumMessage,
|
|
34
|
+
underMinimumMessage = _ref.underMinimumMessage,
|
|
35
|
+
helperMessage = _ref.helperMessage,
|
|
36
|
+
defaultValue = _ref.defaultValue,
|
|
37
|
+
id = _ref.id,
|
|
38
|
+
isRequired = _ref.isRequired,
|
|
39
|
+
isDisabled = _ref.isDisabled,
|
|
40
|
+
label = _ref.label,
|
|
41
|
+
elementAfterLabel = _ref.elementAfterLabel,
|
|
42
|
+
name = _ref.name,
|
|
43
|
+
testId = _ref.testId;
|
|
44
|
+
// Default validation function for character limits
|
|
45
|
+
// __TOO_SHORT__ and __TOO_LONG__ are default error codes recognised by the CharacterCounter component
|
|
46
|
+
var validateCharacterCount = function validateCharacterCount(value) {
|
|
47
|
+
var stringValue = String(value || '');
|
|
48
|
+
var length = stringValue.length;
|
|
49
|
+
|
|
50
|
+
// Check minimum length
|
|
51
|
+
if (minCharacters !== undefined && length < minCharacters) {
|
|
52
|
+
return '__TOO_SHORT__';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check maximum length
|
|
56
|
+
if (maxCharacters !== undefined && length > maxCharacters) {
|
|
57
|
+
return '__TOO_LONG__';
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Combine user validation and character validation
|
|
63
|
+
// Any user defined validation takes priority over character validation
|
|
64
|
+
// If there is no user defined validation for character limits e.g. used maxLength prior to CharacterCounterField,
|
|
65
|
+
// use the default error codes and display the appropriate error message
|
|
66
|
+
var combinedValidate = function combinedValidate(value, formState, fieldState) {
|
|
67
|
+
// First run character validation
|
|
68
|
+
var characterError = validateCharacterCount(value);
|
|
69
|
+
|
|
70
|
+
// Then run user's custom validation if provided
|
|
71
|
+
var userError = userValidate === null || userValidate === void 0 ? void 0 : userValidate(value, formState, fieldState);
|
|
72
|
+
|
|
73
|
+
// If user validation returns a promise, handle it
|
|
74
|
+
if (userError instanceof Promise) {
|
|
75
|
+
return userError.then(function (error) {
|
|
76
|
+
// User error takes priority over character validation
|
|
77
|
+
return error || characterError;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// User error takes priority over character validation
|
|
82
|
+
return userError || characterError;
|
|
83
|
+
};
|
|
84
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
85
|
+
className: ax([fieldWrapperStyles.root])
|
|
86
|
+
}, /*#__PURE__*/React.createElement(Field, {
|
|
87
|
+
defaultValue: defaultValue,
|
|
88
|
+
id: id,
|
|
89
|
+
isRequired: isRequired,
|
|
90
|
+
isDisabled: isDisabled,
|
|
91
|
+
label: label,
|
|
92
|
+
elementAfterLabel: elementAfterLabel,
|
|
93
|
+
name: name,
|
|
94
|
+
testId: testId,
|
|
95
|
+
validate: combinedValidate
|
|
96
|
+
}, function (_ref2) {
|
|
97
|
+
var extendedFieldProps = _ref2.fieldProps,
|
|
98
|
+
error = _ref2.error,
|
|
99
|
+
valid = _ref2.valid,
|
|
100
|
+
meta = _ref2.meta;
|
|
101
|
+
// Determine if error is a character count violation (handled by CharacterCounter)
|
|
102
|
+
// or an external validation error (needs ErrorMessage)
|
|
103
|
+
var isCharacterCountViolation = error === '__TOO_SHORT__' || error === '__TOO_LONG__';
|
|
104
|
+
var showExternalError = error && !isCharacterCountViolation;
|
|
105
|
+
var showCharacterCounter = (maxCharacters !== undefined || minCharacters !== undefined) && !showExternalError;
|
|
106
|
+
|
|
107
|
+
// Extend aria-describedby to reference the appropriate message component
|
|
108
|
+
var fieldPropsWithCounter = _objectSpread(_objectSpread({}, extendedFieldProps), {}, {
|
|
109
|
+
'aria-describedby': showCharacterCounter ? "".concat(extendedFieldProps['aria-describedby'], " ").concat(extendedFieldProps.id, "-character-counter").trim() : extendedFieldProps['aria-describedby']
|
|
110
|
+
});
|
|
111
|
+
return /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(MessageWrapper, null, helperMessage && /*#__PURE__*/React.createElement("div", {
|
|
112
|
+
className: ax([helperMessageWrapperStyles.root])
|
|
113
|
+
}, /*#__PURE__*/React.createElement(HelperMessage, {
|
|
114
|
+
testId: "".concat(testId, "-helper")
|
|
115
|
+
}, helperMessage))), children({
|
|
116
|
+
fieldProps: fieldPropsWithCounter,
|
|
117
|
+
error: error,
|
|
118
|
+
valid: valid,
|
|
119
|
+
meta: meta
|
|
120
|
+
}), /*#__PURE__*/React.createElement(MessageWrapper, null, showExternalError && /*#__PURE__*/React.createElement(ErrorMessage, {
|
|
121
|
+
testId: "".concat(testId, "-error")
|
|
122
|
+
}, error)), showCharacterCounter && /*#__PURE__*/React.createElement(CharacterCounter, {
|
|
123
|
+
maxCharacters: maxCharacters,
|
|
124
|
+
minCharacters: minCharacters,
|
|
125
|
+
currentValue: String(extendedFieldProps.value || ''),
|
|
126
|
+
shouldShowAsError: isCharacterCountViolation,
|
|
127
|
+
overMaximumMessage: overMaximumMessage,
|
|
128
|
+
underMaximumMessage: underMaximumMessage,
|
|
129
|
+
underMinimumMessage: underMinimumMessage,
|
|
130
|
+
testId: "".concat(testId, "-character-counter")
|
|
131
|
+
}));
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
._11c8dcr7{font:var(--ds-font-body-UNSAFE_small,normal 400 9pt/1pc ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI",Ubuntu,"Helvetica Neue",sans-serif)}
|
|
2
|
+
._zulp12x7{gap:var(--ds-space-075,6px)}
|
|
3
|
+
._1bah1q9y{justify-content:baseline}
|
|
4
|
+
._1e0c1txw{display:flex}
|
|
5
|
+
._1pfh1b66{margin-block-start:var(--ds-space-050,4px)}
|
|
6
|
+
._4cvr1h6o{align-items:center}
|
|
7
|
+
._4t3i7vkz{height:1pc}
|
|
8
|
+
._syaze6sf{color:var(--ds-text-danger,#ae2a19)}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/* character-counter.tsx generated by @compiled/babel-plugin v0.38.1 */
|
|
2
|
+
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
3
|
+
import "./character-counter.compiled.css";
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { ax, ix } from "@compiled/react/runtime";
|
|
6
|
+
import { useContext, useEffect, useRef, useState } from 'react';
|
|
7
|
+
import ErrorIcon from '@atlaskit/icon/core/migration/status-error--error';
|
|
8
|
+
import { Text } from '@atlaskit/primitives/compiled';
|
|
9
|
+
import VisuallyHidden from '@atlaskit/visually-hidden';
|
|
10
|
+
import { FieldId } from './field-id-context';
|
|
11
|
+
|
|
12
|
+
// Extracted styles for character counter message container
|
|
13
|
+
var messageContainerStyles = null;
|
|
14
|
+
|
|
15
|
+
// Extracted styles for error icon wrapper
|
|
16
|
+
var errorIconWrapperStyles = null;
|
|
17
|
+
|
|
18
|
+
// Error icon with wrapper for character count violations
|
|
19
|
+
var ErrorIconWithWrapper = function ErrorIconWithWrapper() {
|
|
20
|
+
return /*#__PURE__*/React.createElement("span", {
|
|
21
|
+
className: ax(["_1e0c1txw _4t3i7vkz _4cvr1h6o"])
|
|
22
|
+
}, /*#__PURE__*/React.createElement(ErrorIcon, {
|
|
23
|
+
LEGACY_margin: "0 -2px 0 0",
|
|
24
|
+
LEGACY_size: "small",
|
|
25
|
+
label: "error",
|
|
26
|
+
size: "small"
|
|
27
|
+
}));
|
|
28
|
+
};
|
|
29
|
+
// Helper to pluralise "character(s)"
|
|
30
|
+
var pluralize = function pluralize(count) {
|
|
31
|
+
return "character".concat(count !== 1 ? 's' : '');
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* __Character Counter__
|
|
36
|
+
*
|
|
37
|
+
* A character counter component that displays remaining characters for text input.
|
|
38
|
+
* Displays messages for over or under the maximum or minimum character limits.
|
|
39
|
+
*/
|
|
40
|
+
export var CharacterCounter = function CharacterCounter(_ref) {
|
|
41
|
+
var maxCharacters = _ref.maxCharacters,
|
|
42
|
+
minCharacters = _ref.minCharacters,
|
|
43
|
+
currentValue = _ref.currentValue,
|
|
44
|
+
overMaximumMessage = _ref.overMaximumMessage,
|
|
45
|
+
underMaximumMessage = _ref.underMaximumMessage,
|
|
46
|
+
underMinimumMessage = _ref.underMinimumMessage,
|
|
47
|
+
_ref$shouldShowAsErro = _ref.shouldShowAsError,
|
|
48
|
+
shouldShowAsError = _ref$shouldShowAsErro === void 0 ? true : _ref$shouldShowAsErro,
|
|
49
|
+
inputId = _ref.inputId,
|
|
50
|
+
testId = _ref.testId;
|
|
51
|
+
var _useState = useState(''),
|
|
52
|
+
_useState2 = _slicedToArray(_useState, 2),
|
|
53
|
+
announcementText = _useState2[0],
|
|
54
|
+
setAnnouncementText = _useState2[1];
|
|
55
|
+
var debounceTimeoutRef = useRef(null);
|
|
56
|
+
|
|
57
|
+
// Resolve the field ID from context (form use) or inputId prop (standalone use)
|
|
58
|
+
var contextFieldId = useContext(FieldId);
|
|
59
|
+
var resolvedFieldId = contextFieldId || inputId;
|
|
60
|
+
var currentLength = (currentValue === null || currentValue === void 0 ? void 0 : currentValue.length) || 0;
|
|
61
|
+
|
|
62
|
+
// Check if character count violates limits
|
|
63
|
+
var isTooShort = minCharacters !== undefined && currentLength < minCharacters;
|
|
64
|
+
var isTooLong = maxCharacters !== undefined && currentLength > maxCharacters;
|
|
65
|
+
|
|
66
|
+
// Determine what to display based on the current value, the maximum and minimum character limits, and any custom messages
|
|
67
|
+
var getMessage = function getMessage() {
|
|
68
|
+
// Below minimum so show custom message or default
|
|
69
|
+
if (isTooShort) {
|
|
70
|
+
var needed = minCharacters - currentLength;
|
|
71
|
+
return underMinimumMessage || "".concat(needed, " more ").concat(pluralize(needed), " needed");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Over maximum so show custom message or default
|
|
75
|
+
if (isTooLong) {
|
|
76
|
+
var over = currentLength - maxCharacters;
|
|
77
|
+
return overMaximumMessage || "".concat(over, " ").concat(pluralize(over), " too many");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Within limits - show remaining count (if max is defined)
|
|
81
|
+
if (maxCharacters) {
|
|
82
|
+
var remaining = maxCharacters - currentLength;
|
|
83
|
+
return underMaximumMessage || "".concat(remaining, " ").concat(pluralize(remaining), " remaining");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// No message to show (min only limit satisfied)
|
|
87
|
+
return null;
|
|
88
|
+
};
|
|
89
|
+
var displayText = getMessage();
|
|
90
|
+
|
|
91
|
+
// Determine if the current character count violates limits
|
|
92
|
+
var displayAsError = (isTooShort || isTooLong) && shouldShowAsError;
|
|
93
|
+
|
|
94
|
+
// Debounce screen reader announcements so that it only reads the message when it input has settled
|
|
95
|
+
useEffect(function () {
|
|
96
|
+
// Debounce by 1 second to avoid announcing every keystroke
|
|
97
|
+
debounceTimeoutRef.current = setTimeout(function () {
|
|
98
|
+
setAnnouncementText(displayText || '');
|
|
99
|
+
}, 1000);
|
|
100
|
+
|
|
101
|
+
// Cleanup function clears the timeout when displayText changes or component unmounts
|
|
102
|
+
return function () {
|
|
103
|
+
clearTimeout(debounceTimeoutRef.current);
|
|
104
|
+
};
|
|
105
|
+
}, [displayText]);
|
|
106
|
+
|
|
107
|
+
// Don't render if there's no message to display (min only limit satisfied)
|
|
108
|
+
if (!displayText) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
112
|
+
"data-testid": testId
|
|
113
|
+
}, /*#__PURE__*/React.createElement("div", {
|
|
114
|
+
className: ax(["_zulp12x7 _11c8dcr7 _1e0c1txw _1bah1q9y _syaze6sf _1pfh1b66"])
|
|
115
|
+
}, displayAsError && /*#__PURE__*/React.createElement(ErrorIconWithWrapper, null), /*#__PURE__*/React.createElement(Text, {
|
|
116
|
+
color: displayAsError ? 'color.text.danger' : 'color.text.subtlest',
|
|
117
|
+
size: "small",
|
|
118
|
+
id: resolvedFieldId ? "".concat(resolvedFieldId, "-character-counter") : undefined
|
|
119
|
+
}, displayText)), /*#__PURE__*/React.createElement(VisuallyHidden, null, /*#__PURE__*/React.createElement("div", {
|
|
120
|
+
"aria-live": "polite"
|
|
121
|
+
}, announcementText)));
|
|
122
|
+
};
|
package/dist/esm/index.js
CHANGED
|
@@ -9,4 +9,5 @@ export { Label, Legend } from './label';
|
|
|
9
9
|
export { HelperMessage, ErrorMessage, MessageWrapper, ValidMessage } from './messages';
|
|
10
10
|
export { default as Fieldset } from './fieldset';
|
|
11
11
|
export { default as RequiredAsterisk } from './required-asterisk';
|
|
12
|
-
export { useFormState } from './use-form-state';
|
|
12
|
+
export { useFormState } from './use-form-state';
|
|
13
|
+
export { default as CharacterCounterField } from './character-counter-field';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jsxRuntime classic
|
|
3
|
+
* @jsx jsx
|
|
4
|
+
*/
|
|
5
|
+
import { type ReactNode } from 'react';
|
|
6
|
+
import { type FieldComponentProps, type FieldProps, type Meta } from './field';
|
|
7
|
+
type SupportedElements = HTMLInputElement | HTMLTextAreaElement;
|
|
8
|
+
export interface CharacterCounterFieldProps<FieldValue = string, Element extends SupportedElements = HTMLInputElement> extends Omit<FieldComponentProps<FieldValue, Element>, 'children' | 'component' | 'helperMessage' | 'errorMessage' | 'validMessage' | 'transform'> {
|
|
9
|
+
/**
|
|
10
|
+
* The input component to render. Use a render function that receives `fieldProps`, `error`, `valid`, and `meta` state.
|
|
11
|
+
* Spread `fieldProps` onto your input element (such as `TextField` or `TextArea`).
|
|
12
|
+
*/
|
|
13
|
+
children: (args: {
|
|
14
|
+
fieldProps: FieldProps<FieldValue, Element>;
|
|
15
|
+
error?: string;
|
|
16
|
+
valid: boolean;
|
|
17
|
+
meta: Meta;
|
|
18
|
+
}) => ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* Helper text displayed above the input to provide additional context or instructions.
|
|
21
|
+
*/
|
|
22
|
+
helperMessage?: ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* Maximum number of characters allowed. When exceeded, the field displays an error message or the message provided by `overMaximumMessage`.
|
|
25
|
+
*/
|
|
26
|
+
maxCharacters?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Minimum number of characters required. When not met, the character counter displays an error message or the message provided by `underMinimumMessage`.
|
|
29
|
+
*/
|
|
30
|
+
minCharacters?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Custom message displayed when input exceeds the maximum character limit. Use this to provide context-specific guidance or localized messages. Overrides the default "X characters too many" message.
|
|
33
|
+
*/
|
|
34
|
+
overMaximumMessage?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Custom message displayed when input is under the maximum limit. Use this to provide context-specific guidance or localized messages. Overrides the default "X characters remaining" message.
|
|
37
|
+
*/
|
|
38
|
+
underMaximumMessage?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Custom message displayed when input is under the minimum requirement. Use this to guide users on how much more they need to type. Overrides the default "Minimum of X characters required" message.
|
|
41
|
+
*/
|
|
42
|
+
underMinimumMessage?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* __Character Counter Field__
|
|
46
|
+
*
|
|
47
|
+
* A field component that wraps the standard Field with automatic character count validation.
|
|
48
|
+
* Validates minimum and maximum character limits and displays a character counter.
|
|
49
|
+
*/
|
|
50
|
+
export default function CharacterCounterField<FieldValue = string, Element extends SupportedElements = HTMLInputElement>({ maxCharacters, minCharacters, children, validate: userValidate, overMaximumMessage, underMaximumMessage, underMinimumMessage, helperMessage, defaultValue, id, isRequired, isDisabled, label, elementAfterLabel, name, testId, }: CharacterCounterFieldProps<FieldValue, Element>): JSX.Element;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jsxRuntime classic
|
|
3
|
+
* @jsx jsx
|
|
4
|
+
*/
|
|
5
|
+
export interface CharacterCounterProps {
|
|
6
|
+
/**
|
|
7
|
+
* Maximum number of characters allowed (optional)
|
|
8
|
+
*/
|
|
9
|
+
maxCharacters?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Minimum number of characters required (optional)
|
|
12
|
+
*/
|
|
13
|
+
minCharacters?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Current value of the input field
|
|
16
|
+
*/
|
|
17
|
+
currentValue?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Optional custom message to display when character limit is exceeded
|
|
20
|
+
*/
|
|
21
|
+
overMaximumMessage?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Optional custom message to display when character limit is not exceeded
|
|
24
|
+
*/
|
|
25
|
+
underMaximumMessage?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Optional custom message to display when minimum character requirement is not met
|
|
28
|
+
*/
|
|
29
|
+
underMinimumMessage?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Whether to style violations as errors (red text + icon).
|
|
32
|
+
* By default, violations are automatically styled as errors.
|
|
33
|
+
*
|
|
34
|
+
* In forms, set this to false to suppress error styling when
|
|
35
|
+
* the form hasn't flagged an error yet (e.g., field not touched).
|
|
36
|
+
*
|
|
37
|
+
* // Standalone: smart default (violations = errors)
|
|
38
|
+
* <CharacterCounter currentValue={value} maxCharacters={100} />
|
|
39
|
+
*
|
|
40
|
+
* // Form: align with final-form error state
|
|
41
|
+
* <CharacterCounter
|
|
42
|
+
* currentValue={value}
|
|
43
|
+
* maxCharacters={100}
|
|
44
|
+
* shouldShowAsError={isCharacterCountViolation}
|
|
45
|
+
* />
|
|
46
|
+
*/
|
|
47
|
+
shouldShowAsError?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* ID of the associated input for accessibility.
|
|
50
|
+
* Not needed if the character counter is used within CharacterCounterField.
|
|
51
|
+
* When provided, the character counter will have an ID of `${inputId}-character-counter`
|
|
52
|
+
* which should be referenced in the input's `aria-describedby` attribute.
|
|
53
|
+
* If not provided, will attempt to use InputId context from Form.
|
|
54
|
+
*/
|
|
55
|
+
inputId?: string;
|
|
56
|
+
/**
|
|
57
|
+
* A testId prop is provided for specified elements, which is a unique string
|
|
58
|
+
* that appears as a data attribute data-testid in the rendered code,
|
|
59
|
+
* serving as a hook for automated tests
|
|
60
|
+
*/
|
|
61
|
+
testId?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* __Character Counter__
|
|
65
|
+
*
|
|
66
|
+
* A character counter component that displays remaining characters for text input.
|
|
67
|
+
* Displays messages for over or under the maximum or minimum character limits.
|
|
68
|
+
*/
|
|
69
|
+
export declare const CharacterCounter: ({ maxCharacters, minCharacters, currentValue, overMaximumMessage, underMaximumMessage, underMinimumMessage, shouldShowAsError, inputId, testId, }: CharacterCounterProps) => JSX.Element | null;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -16,3 +16,5 @@ export { default as Fieldset } from './fieldset';
|
|
|
16
16
|
export { default as RequiredAsterisk } from './required-asterisk';
|
|
17
17
|
export type { OnSubmitHandler, FormApi } from './types';
|
|
18
18
|
export { useFormState } from './use-form-state';
|
|
19
|
+
export { default as CharacterCounterField } from './character-counter-field';
|
|
20
|
+
export type { CharacterCounterFieldProps } from './character-counter-field';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jsxRuntime classic
|
|
3
|
+
* @jsx jsx
|
|
4
|
+
*/
|
|
5
|
+
import { type ReactNode } from 'react';
|
|
6
|
+
import { type FieldComponentProps, type FieldProps, type Meta } from './field';
|
|
7
|
+
type SupportedElements = HTMLInputElement | HTMLTextAreaElement;
|
|
8
|
+
export interface CharacterCounterFieldProps<FieldValue = string, Element extends SupportedElements = HTMLInputElement> extends Omit<FieldComponentProps<FieldValue, Element>, 'children' | 'component' | 'helperMessage' | 'errorMessage' | 'validMessage' | 'transform'> {
|
|
9
|
+
/**
|
|
10
|
+
* The input component to render. Use a render function that receives `fieldProps`, `error`, `valid`, and `meta` state.
|
|
11
|
+
* Spread `fieldProps` onto your input element (such as `TextField` or `TextArea`).
|
|
12
|
+
*/
|
|
13
|
+
children: (args: {
|
|
14
|
+
fieldProps: FieldProps<FieldValue, Element>;
|
|
15
|
+
error?: string;
|
|
16
|
+
valid: boolean;
|
|
17
|
+
meta: Meta;
|
|
18
|
+
}) => ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* Helper text displayed above the input to provide additional context or instructions.
|
|
21
|
+
*/
|
|
22
|
+
helperMessage?: ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* Maximum number of characters allowed. When exceeded, the field displays an error message or the message provided by `overMaximumMessage`.
|
|
25
|
+
*/
|
|
26
|
+
maxCharacters?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Minimum number of characters required. When not met, the character counter displays an error message or the message provided by `underMinimumMessage`.
|
|
29
|
+
*/
|
|
30
|
+
minCharacters?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Custom message displayed when input exceeds the maximum character limit. Use this to provide context-specific guidance or localized messages. Overrides the default "X characters too many" message.
|
|
33
|
+
*/
|
|
34
|
+
overMaximumMessage?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Custom message displayed when input is under the maximum limit. Use this to provide context-specific guidance or localized messages. Overrides the default "X characters remaining" message.
|
|
37
|
+
*/
|
|
38
|
+
underMaximumMessage?: string;
|
|
39
|
+
/**
|
|
40
|
+
* Custom message displayed when input is under the minimum requirement. Use this to guide users on how much more they need to type. Overrides the default "Minimum of X characters required" message.
|
|
41
|
+
*/
|
|
42
|
+
underMinimumMessage?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* __Character Counter Field__
|
|
46
|
+
*
|
|
47
|
+
* A field component that wraps the standard Field with automatic character count validation.
|
|
48
|
+
* Validates minimum and maximum character limits and displays a character counter.
|
|
49
|
+
*/
|
|
50
|
+
export default function CharacterCounterField<FieldValue = string, Element extends SupportedElements = HTMLInputElement>({ maxCharacters, minCharacters, children, validate: userValidate, overMaximumMessage, underMaximumMessage, underMinimumMessage, helperMessage, defaultValue, id, isRequired, isDisabled, label, elementAfterLabel, name, testId, }: CharacterCounterFieldProps<FieldValue, Element>): JSX.Element;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jsxRuntime classic
|
|
3
|
+
* @jsx jsx
|
|
4
|
+
*/
|
|
5
|
+
export interface CharacterCounterProps {
|
|
6
|
+
/**
|
|
7
|
+
* Maximum number of characters allowed (optional)
|
|
8
|
+
*/
|
|
9
|
+
maxCharacters?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Minimum number of characters required (optional)
|
|
12
|
+
*/
|
|
13
|
+
minCharacters?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Current value of the input field
|
|
16
|
+
*/
|
|
17
|
+
currentValue?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Optional custom message to display when character limit is exceeded
|
|
20
|
+
*/
|
|
21
|
+
overMaximumMessage?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Optional custom message to display when character limit is not exceeded
|
|
24
|
+
*/
|
|
25
|
+
underMaximumMessage?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Optional custom message to display when minimum character requirement is not met
|
|
28
|
+
*/
|
|
29
|
+
underMinimumMessage?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Whether to style violations as errors (red text + icon).
|
|
32
|
+
* By default, violations are automatically styled as errors.
|
|
33
|
+
*
|
|
34
|
+
* In forms, set this to false to suppress error styling when
|
|
35
|
+
* the form hasn't flagged an error yet (e.g., field not touched).
|
|
36
|
+
*
|
|
37
|
+
* // Standalone: smart default (violations = errors)
|
|
38
|
+
* <CharacterCounter currentValue={value} maxCharacters={100} />
|
|
39
|
+
*
|
|
40
|
+
* // Form: align with final-form error state
|
|
41
|
+
* <CharacterCounter
|
|
42
|
+
* currentValue={value}
|
|
43
|
+
* maxCharacters={100}
|
|
44
|
+
* shouldShowAsError={isCharacterCountViolation}
|
|
45
|
+
* />
|
|
46
|
+
*/
|
|
47
|
+
shouldShowAsError?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* ID of the associated input for accessibility.
|
|
50
|
+
* Not needed if the character counter is used within CharacterCounterField.
|
|
51
|
+
* When provided, the character counter will have an ID of `${inputId}-character-counter`
|
|
52
|
+
* which should be referenced in the input's `aria-describedby` attribute.
|
|
53
|
+
* If not provided, will attempt to use InputId context from Form.
|
|
54
|
+
*/
|
|
55
|
+
inputId?: string;
|
|
56
|
+
/**
|
|
57
|
+
* A testId prop is provided for specified elements, which is a unique string
|
|
58
|
+
* that appears as a data attribute data-testid in the rendered code,
|
|
59
|
+
* serving as a hook for automated tests
|
|
60
|
+
*/
|
|
61
|
+
testId?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* __Character Counter__
|
|
65
|
+
*
|
|
66
|
+
* A character counter component that displays remaining characters for text input.
|
|
67
|
+
* Displays messages for over or under the maximum or minimum character limits.
|
|
68
|
+
*/
|
|
69
|
+
export declare const CharacterCounter: ({ maxCharacters, minCharacters, currentValue, overMaximumMessage, underMaximumMessage, underMinimumMessage, shouldShowAsError, inputId, testId, }: CharacterCounterProps) => JSX.Element | null;
|
|
@@ -16,3 +16,5 @@ export { default as Fieldset } from './fieldset';
|
|
|
16
16
|
export { default as RequiredAsterisk } from './required-asterisk';
|
|
17
17
|
export type { OnSubmitHandler, FormApi } from './types';
|
|
18
18
|
export { useFormState } from './use-form-state';
|
|
19
|
+
export { default as CharacterCounterField } from './character-counter-field';
|
|
20
|
+
export type { CharacterCounterFieldProps } from './character-counter-field';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/form",
|
|
3
|
-
"version": "14.
|
|
3
|
+
"version": "14.4.0",
|
|
4
4
|
"description": "A form allows users to input information.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -30,9 +30,11 @@
|
|
|
30
30
|
"@atlaskit/heading": "^5.2.0",
|
|
31
31
|
"@atlaskit/icon": "^29.0.0",
|
|
32
32
|
"@atlaskit/platform-feature-flags": "^1.1.0",
|
|
33
|
-
"@atlaskit/primitives": "^16.
|
|
34
|
-
"@atlaskit/tokens": "^8.
|
|
33
|
+
"@atlaskit/primitives": "^16.4.0",
|
|
34
|
+
"@atlaskit/tokens": "^8.4.0",
|
|
35
|
+
"@atlaskit/visually-hidden": "^3.0.0",
|
|
35
36
|
"@babel/runtime": "^7.0.0",
|
|
37
|
+
"@compiled/react": "^0.18.6",
|
|
36
38
|
"final-form": "^4.20.3",
|
|
37
39
|
"final-form-focus": "^1.1.2",
|
|
38
40
|
"lodash": "^4.17.21"
|
|
@@ -45,21 +47,21 @@
|
|
|
45
47
|
"@af/integration-testing": "workspace:^",
|
|
46
48
|
"@af/visual-regression": "workspace:^",
|
|
47
49
|
"@atlaskit/banner": "^14.0.0",
|
|
48
|
-
"@atlaskit/button": "^23.
|
|
49
|
-
"@atlaskit/checkbox": "^17.
|
|
50
|
+
"@atlaskit/button": "^23.7.0",
|
|
51
|
+
"@atlaskit/checkbox": "^17.2.0",
|
|
50
52
|
"@atlaskit/codemod-utils": "^4.2.0",
|
|
51
|
-
"@atlaskit/datetime-picker": "^17.
|
|
53
|
+
"@atlaskit/datetime-picker": "^17.2.0",
|
|
52
54
|
"@atlaskit/docs": "^11.2.0",
|
|
53
55
|
"@atlaskit/link": "^3.2.0",
|
|
54
56
|
"@atlaskit/lozenge": "^13.1.0",
|
|
55
|
-
"@atlaskit/modal-dialog": "^14.
|
|
57
|
+
"@atlaskit/modal-dialog": "^14.8.0",
|
|
56
58
|
"@atlaskit/radio": "^8.3.0",
|
|
57
|
-
"@atlaskit/range": "^9.
|
|
58
|
-
"@atlaskit/section-message": "^8.
|
|
59
|
-
"@atlaskit/select": "^21.
|
|
60
|
-
"@atlaskit/textarea": "^8.
|
|
61
|
-
"@atlaskit/textfield": "^8.
|
|
62
|
-
"@atlaskit/toggle": "^15.
|
|
59
|
+
"@atlaskit/range": "^9.3.0",
|
|
60
|
+
"@atlaskit/section-message": "^8.10.0",
|
|
61
|
+
"@atlaskit/select": "^21.5.0",
|
|
62
|
+
"@atlaskit/textarea": "^8.2.0",
|
|
63
|
+
"@atlaskit/textfield": "^8.2.0",
|
|
64
|
+
"@atlaskit/toggle": "^15.2.0",
|
|
63
65
|
"@atlassian/feature-flags-test-utils": "^1.0.0",
|
|
64
66
|
"@atlassian/ssr-tests": "workspace:^",
|
|
65
67
|
"@testing-library/react": "^13.4.0",
|