carbon-react 120.6.1 → 121.0.1
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/esm/__internal__/character-count/character-count.component.d.ts +4 -2
- package/esm/__internal__/character-count/character-count.component.js +16 -6
- package/esm/__internal__/character-count/character-count.style.d.ts +4 -1
- package/esm/__internal__/character-count/character-count.style.js +26 -2
- package/esm/components/breadcrumbs/crumb/crumb.component.js +1 -0
- package/esm/components/date/date.component.d.ts +1 -1
- package/esm/components/grouped-character/grouped-character.component.js +0 -1
- package/esm/components/link/link.component.js +9 -4
- package/esm/components/link/link.style.d.ts +1 -0
- package/esm/components/link/link.style.js +15 -16
- package/esm/components/menu/menu-item/menu-item.style.js +1 -15
- package/esm/components/number/number.component.js +0 -1
- package/esm/components/textarea/textarea.component.d.ts +0 -2
- package/esm/components/textarea/textarea.component.js +6 -11
- package/esm/components/textbox/textbox.component.d.ts +0 -2
- package/esm/components/textbox/textbox.component.js +7 -12
- package/esm/components/textbox/textbox.style.js +4 -0
- package/esm/hooks/__internal__/useCharacterCount/useCharacterCount.d.ts +1 -6
- package/esm/hooks/__internal__/useCharacterCount/useCharacterCount.js +24 -8
- package/esm/hooks/__internal__/useDebounce/index.d.ts +1 -0
- package/esm/hooks/__internal__/useDebounce/index.js +1 -0
- package/esm/hooks/__internal__/useDebounce/useDebounce.d.ts +4 -0
- package/esm/hooks/__internal__/useDebounce/useDebounce.js +19 -0
- package/esm/locales/en-gb.js +3 -3
- package/esm/locales/locale.d.ts +1 -1
- package/esm/locales/pl-pl.js +2 -2
- package/lib/__internal__/character-count/character-count.component.d.ts +4 -2
- package/lib/__internal__/character-count/character-count.component.js +16 -6
- package/lib/__internal__/character-count/character-count.style.d.ts +4 -1
- package/lib/__internal__/character-count/character-count.style.js +31 -5
- package/lib/components/breadcrumbs/crumb/crumb.component.js +1 -0
- package/lib/components/date/date.component.d.ts +1 -1
- package/lib/components/grouped-character/grouped-character.component.js +0 -1
- package/lib/components/link/link.component.js +8 -3
- package/lib/components/link/link.style.d.ts +1 -0
- package/lib/components/link/link.style.js +15 -16
- package/lib/components/menu/menu-item/menu-item.style.js +1 -15
- package/lib/components/number/number.component.js +0 -1
- package/lib/components/textarea/textarea.component.d.ts +0 -2
- package/lib/components/textarea/textarea.component.js +6 -11
- package/lib/components/textbox/textbox.component.d.ts +0 -2
- package/lib/components/textbox/textbox.component.js +7 -12
- package/lib/components/textbox/textbox.style.js +4 -0
- package/lib/hooks/__internal__/useCharacterCount/useCharacterCount.d.ts +1 -6
- package/lib/hooks/__internal__/useCharacterCount/useCharacterCount.js +23 -7
- package/lib/hooks/__internal__/useDebounce/index.d.ts +1 -0
- package/lib/hooks/__internal__/useDebounce/index.js +13 -0
- package/lib/hooks/__internal__/useDebounce/package.json +6 -0
- package/lib/hooks/__internal__/useDebounce/useDebounce.d.ts +4 -0
- package/lib/hooks/__internal__/useDebounce/useDebounce.js +26 -0
- package/lib/locales/en-gb.js +3 -3
- package/lib/locales/locale.d.ts +1 -1
- package/lib/locales/pl-pl.js +2 -2
- package/package.json +1 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
interface CharacterCountProps {
|
|
3
3
|
value: number;
|
|
4
|
+
debouncedValue?: number;
|
|
4
5
|
limit: number;
|
|
6
|
+
isDebouncedOverLimit?: boolean;
|
|
5
7
|
isOverLimit: boolean;
|
|
6
|
-
|
|
8
|
+
visuallyHiddenHintId?: string;
|
|
7
9
|
}
|
|
8
|
-
declare const CharacterCount: ({ value, limit, isOverLimit,
|
|
10
|
+
declare const CharacterCount: ({ value, debouncedValue, limit, isDebouncedOverLimit, isOverLimit, visuallyHiddenHintId, }: CharacterCountProps) => React.JSX.Element;
|
|
9
11
|
export default CharacterCount;
|
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import PropTypes from "prop-types";
|
|
3
|
-
import StyledCharacterCount from "./character-count.style";
|
|
3
|
+
import { StyledCharacterCountWrapper, StyledCharacterCount, VisuallyHiddenCharacterCount, VisuallyHiddenHint } from "./character-count.style";
|
|
4
4
|
import useLocale from "../../hooks/__internal__/useLocale";
|
|
5
5
|
const CharacterCount = ({
|
|
6
6
|
value,
|
|
7
|
+
debouncedValue = value,
|
|
7
8
|
limit,
|
|
9
|
+
isDebouncedOverLimit,
|
|
8
10
|
isOverLimit,
|
|
9
|
-
|
|
11
|
+
visuallyHiddenHintId
|
|
10
12
|
}) => {
|
|
11
13
|
const limitMinusValue = +limit - +value;
|
|
12
14
|
const valueMinusLimit = +value - +limit;
|
|
15
|
+
const debouncedLimitMinusValue = +limit - +debouncedValue;
|
|
16
|
+
const debouncedValueMinusLimit = debouncedValue - +limit;
|
|
13
17
|
const l = useLocale();
|
|
14
18
|
const getFormatNumber = (rawValue, locale) => new Intl.NumberFormat(locale).format(rawValue);
|
|
15
|
-
return /*#__PURE__*/React.createElement(
|
|
16
|
-
"
|
|
19
|
+
return /*#__PURE__*/React.createElement(StyledCharacterCountWrapper, null, /*#__PURE__*/React.createElement(VisuallyHiddenHint, {
|
|
20
|
+
"data-element": "visually-hidden-hint",
|
|
21
|
+
id: visuallyHiddenHintId
|
|
22
|
+
}, l.characterCount.visuallyHiddenHint(getFormatNumber(limit, l.locale()))), /*#__PURE__*/React.createElement(StyledCharacterCount, {
|
|
23
|
+
"aria-hidden": "true",
|
|
17
24
|
isOverLimit: isOverLimit,
|
|
18
|
-
"data-element":
|
|
19
|
-
}, !isOverLimit ? l.characterCount.charactersLeft(limitMinusValue, getFormatNumber(limitMinusValue, l.locale())) : l.characterCount.tooManyCharacters(valueMinusLimit, getFormatNumber(valueMinusLimit, l.locale())))
|
|
25
|
+
"data-element": "character-count"
|
|
26
|
+
}, !isOverLimit ? l.characterCount.charactersLeft(limitMinusValue, getFormatNumber(limitMinusValue, l.locale())) : l.characterCount.tooManyCharacters(valueMinusLimit, getFormatNumber(valueMinusLimit, l.locale()))), /*#__PURE__*/React.createElement(VisuallyHiddenCharacterCount, {
|
|
27
|
+
"data-element": "visually-hidden-character-count",
|
|
28
|
+
"aria-live": "polite"
|
|
29
|
+
}, !isDebouncedOverLimit ? l.characterCount.charactersLeft(debouncedLimitMinusValue, getFormatNumber(debouncedLimitMinusValue, l.locale())) : l.characterCount.tooManyCharacters(debouncedValueMinusLimit, getFormatNumber(debouncedValueMinusLimit, l.locale()))));
|
|
20
30
|
};
|
|
21
31
|
export default CharacterCount;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
declare const StyledCharacterCountWrapper: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
1
2
|
declare const StyledCharacterCount: import("styled-components").StyledComponent<"div", any, {
|
|
2
3
|
isOverLimit: boolean;
|
|
3
4
|
}, never>;
|
|
4
|
-
|
|
5
|
+
declare const VisuallyHiddenCharacterCount: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
6
|
+
declare const VisuallyHiddenHint: import("styled-components").StyledComponent<"div", any, {}, never>;
|
|
7
|
+
export { StyledCharacterCountWrapper, StyledCharacterCount, VisuallyHiddenCharacterCount, VisuallyHiddenHint, };
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import styled, { css } from "styled-components";
|
|
2
2
|
import baseTheme from "../../style/themes/base";
|
|
3
|
+
import visuallyHidden from "../../style/utils/visually-hidden";
|
|
4
|
+
const StyledCharacterCountWrapper = styled.div``;
|
|
3
5
|
const StyledCharacterCount = styled.div`
|
|
4
|
-
|
|
6
|
+
::after {
|
|
7
|
+
content: " ";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
text-align: left;
|
|
5
11
|
font-size: 12px;
|
|
6
12
|
margin-top: 4px;
|
|
7
13
|
margin-bottom: 4px;
|
|
@@ -15,7 +21,25 @@ const StyledCharacterCount = styled.div`
|
|
|
15
21
|
font-weight: 700;
|
|
16
22
|
`}
|
|
17
23
|
`;
|
|
24
|
+
const VisuallyHiddenCharacterCount = styled.div`
|
|
25
|
+
::after {
|
|
26
|
+
content: " ";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
${visuallyHidden}
|
|
30
|
+
`;
|
|
31
|
+
const VisuallyHiddenHint = styled.div`
|
|
32
|
+
::before {
|
|
33
|
+
content: " ";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
::after {
|
|
37
|
+
content: " ";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
${visuallyHidden}
|
|
41
|
+
`;
|
|
18
42
|
StyledCharacterCount.defaultProps = {
|
|
19
43
|
theme: baseTheme
|
|
20
44
|
};
|
|
21
|
-
export
|
|
45
|
+
export { StyledCharacterCountWrapper, StyledCharacterCount, VisuallyHiddenCharacterCount, VisuallyHiddenHint };
|
|
@@ -12,7 +12,7 @@ export interface DateChangeEvent {
|
|
|
12
12
|
};
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
export interface DateInputProps extends Omit<TextboxProps, "value" | "formattedValue" | "rawValue" | "onChange" | "onBlur" | "onMouseDown" | "onChangeDeferred" | "deferTimeout" | "children" | "leftChildren" | "placeholder" | "iconOnClick" | "iconOnMouseDown" | "
|
|
15
|
+
export interface DateInputProps extends Omit<TextboxProps, "value" | "formattedValue" | "rawValue" | "onChange" | "onBlur" | "onMouseDown" | "onChangeDeferred" | "deferTimeout" | "children" | "leftChildren" | "placeholder" | "iconOnClick" | "iconOnMouseDown" | "characterLimit" | "warnOverLimit" | "iconTabIndex" | "inputIcon"> {
|
|
16
16
|
/** Boolean to allow the input to have an empty value */
|
|
17
17
|
allowEmptyValue?: boolean;
|
|
18
18
|
/** Boolean to toggle where DatePicker is rendered in relation to the Date Input */
|
|
@@ -203,7 +203,6 @@ GroupedCharacter.propTypes = {
|
|
|
203
203
|
"dir": PropTypes.string,
|
|
204
204
|
"disabled": PropTypes.bool,
|
|
205
205
|
"draggable": PropTypes.oneOfType([PropTypes.oneOf(["false", "true"]), PropTypes.bool]),
|
|
206
|
-
"enforceCharacterLimit": PropTypes.bool,
|
|
207
206
|
"enterKeyHint": PropTypes.oneOf(["done", "enter", "go", "next", "previous", "search", "send"]),
|
|
208
207
|
"error": PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
|
209
208
|
"fieldHelp": PropTypes.node,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
2
|
-
import React, { useContext, useMemo } from "react";
|
|
2
|
+
import React, { useContext, useMemo, useState } from "react";
|
|
3
3
|
import PropTypes from "prop-types";
|
|
4
4
|
import Icon from "../icon";
|
|
5
5
|
import MenuContext from "../menu/menu.context";
|
|
@@ -29,6 +29,7 @@ const Link = /*#__PURE__*/React.forwardRef(({
|
|
|
29
29
|
removeAriaLabelOnIcon,
|
|
30
30
|
...rest
|
|
31
31
|
}, ref) => {
|
|
32
|
+
const [hasFocus, setHasFocus] = useState(false);
|
|
32
33
|
const l = useLocale();
|
|
33
34
|
const {
|
|
34
35
|
inMenu
|
|
@@ -75,7 +76,9 @@ const Link = /*#__PURE__*/React.forwardRef(({
|
|
|
75
76
|
href,
|
|
76
77
|
rel,
|
|
77
78
|
"aria-label": ariaLabel,
|
|
78
|
-
...ariaProps
|
|
79
|
+
...ariaProps,
|
|
80
|
+
onFocus: () => setHasFocus(true),
|
|
81
|
+
onBlur: () => setHasFocus(false)
|
|
79
82
|
};
|
|
80
83
|
const buttonProps = {
|
|
81
84
|
type: "button"
|
|
@@ -87,8 +90,7 @@ const Link = /*#__PURE__*/React.forwardRef(({
|
|
|
87
90
|
}
|
|
88
91
|
return /*#__PURE__*/React.createElement(type, type === "button" ? {
|
|
89
92
|
...componentProps,
|
|
90
|
-
...buttonProps
|
|
91
|
-
placeholderTabIndex
|
|
93
|
+
...buttonProps
|
|
92
94
|
} : {
|
|
93
95
|
...componentProps,
|
|
94
96
|
...(placeholderTabIndex && href === undefined && !onClick && {
|
|
@@ -107,6 +109,8 @@ const Link = /*#__PURE__*/React.forwardRef(({
|
|
|
107
109
|
isMenuItem: inMenu
|
|
108
110
|
}, tagComponent("link", rest), isSkipLink && {
|
|
109
111
|
"data-element": "skip-link"
|
|
112
|
+
}, {
|
|
113
|
+
hasFocus: hasFocus
|
|
110
114
|
}), createLinkBasedOnType());
|
|
111
115
|
});
|
|
112
116
|
Link.propTypes = {
|
|
@@ -162,6 +166,7 @@ Link.propTypes = {
|
|
|
162
166
|
"children": PropTypes.node,
|
|
163
167
|
"className": PropTypes.string,
|
|
164
168
|
"disabled": PropTypes.bool,
|
|
169
|
+
"hasFocus": PropTypes.bool,
|
|
165
170
|
"href": PropTypes.string,
|
|
166
171
|
"icon": PropTypes.oneOf(["accessibility_web", "add", "admin", "alert_on", "alert", "analysis", "app_facebook", "app_instagram", "app_tiktok", "app_twitter", "app_youtube", "apps", "arrow_bottom_right_circle", "arrow_down", "arrow_left_boxed", "arrow_left_right_small", "arrow_left_small", "arrow_left", "arrow_right_small", "arrow_right", "arrow_top_left_circle", "arrow_up", "arrow", "arrows_left_right", "attach", "bank_with_card", "bank", "basket_with_squares", "basket", "bed", "bill_paid", "bin", "block_arrow_right", "blocked_square", "blocked", "bold", "box_arrow_left", "box_arrow_right", "boxed_shapes", "bulk_destroy", "bullet_list_dotted", "bullet_list_numbers", "bullet_list", "business", "calendar_pay_date", "calendar_today", "calendar", "call", "camera", "car_lock", "car_money", "car_repair", "card_view", "card_wallet", "caret_down", "caret_large_down", "caret_large_left", "caret_large_right", "caret_large_up", "caret_left", "caret_right", "caret_up", "cart", "cash", "chart_bar", "chart_line", "chart_pie", "chat_notes", "chat", "check_all", "check_none", "chevron_down_thick", "chevron_down", "chevron_left_thick", "chevron_left", "chevron_right_thick", "chevron_right", "chevron_up_thick", "chevron_up", "circle_with_dots", "circles_connection", "clock", "close", "coins", "collaborate", "computer_clock", "connect_off", "connect", "construction", "contacts", "copy", "create", "credit_card_slash", "credit_card", "cross_circle", "cross", "csv", "dashboard", "delete", "delivery", "disconnect", "disputed", "document_right_align", "document_tick", "document_vertical_lines", "download", "draft", "drag_vertical", "drag", "drill", "dropdown", "duplicate", "edit", "edited", "ellipsis_horizontal", "ellipsis_vertical", "email_switch", "email", "entry", "envelope_dollar", "envelope_euro", "error_square", "error", "euro", "expand", "factory", "favourite_lined", "favourite", "fax", "feedback", "file_excel", "file_generic", "file_image", "file_pdf", "file_word", "files_leaning", "filter_new", "filter", "fit_height", "fit_width", "flag", "folder", "form_refresh", "gift", "go", "graduation_hat", "graph", "grid", "hand_cash_coins", "hand_cash_note", "heart_pulse", "help", "hide", "home", "image", "in_progress", "in_transit", "individual", "info", "intranet", "italic", "job_seeked", "key", "laptop", "ledger_arrow_left", "ledger_arrow_right", "ledger", "lightbulb_off", "lightbulb_on", "like", "link_cloud", "link_on", "link", "list_view", "location", "locked", "logout", "lookup", "marker", "message", "microphone", "minus_large", "minus", "mobile", "money_bag", "none", "old_warning", "palm_tree", "pause_circle", "pause", "pdf", "people_switch", "people", "percentage_boxed", "person_info", "person_tick", "person", "petrol_pump", "phone", "piggy_bank", "plane", "play_circle", "play", "plus_large", "plus", "pound", "print", "progress", "progressed", "protect", "question_hollow", "question_mark", "question", "recruiting", "refresh_clock", "refresh", "remove", "sage_coin", "save", "scan", "search", "send", "services", "settings_old", "settings", "share", "shop", "sort_down", "sort_up", "spanner", "split_container", "split", "square_dot", "squares_nine", "stacked_boxes", "stacked_squares", "submitted", "support_online", "sync", "tag", "talk", "target_man", "target", "theatre_masks", "three_boxes", "tick_circle", "tick_thick", "tick", "true_tick", "u_turn_left", "u_turn_right", "undo", "unlocked", "upload", "uploaded", "video", "view", "volunteering", "warning", "website", "welfare"]),
|
|
167
172
|
"iconAlign": PropTypes.oneOf(["left", "right"]),
|
|
@@ -42,7 +42,8 @@ const StyledLink = styled.span`
|
|
|
42
42
|
disabled,
|
|
43
43
|
variant,
|
|
44
44
|
isDarkBackground,
|
|
45
|
-
isMenuItem
|
|
45
|
+
isMenuItem,
|
|
46
|
+
hasFocus
|
|
46
47
|
}) => {
|
|
47
48
|
const colorMapKey = isDarkBackground ? "dark" : "light";
|
|
48
49
|
const {
|
|
@@ -117,21 +118,6 @@ const StyledLink = styled.span`
|
|
|
117
118
|
}
|
|
118
119
|
`}
|
|
119
120
|
}
|
|
120
|
-
|
|
121
|
-
${!disabled && !theme.focusRedesignOptOut && css`
|
|
122
|
-
a,
|
|
123
|
-
button {
|
|
124
|
-
outline: none;
|
|
125
|
-
text-decoration: none;
|
|
126
|
-
border-bottom-left-radius: var(--borderRadius000);
|
|
127
|
-
border-bottom-right-radius: var(--borderRadius000);
|
|
128
|
-
}
|
|
129
|
-
&:focus-within {
|
|
130
|
-
box-shadow: 0 4px 0 0 var(--colorsUtilityYin090);
|
|
131
|
-
border-bottom-left-radius: var(--borderRadius025);
|
|
132
|
-
border-bottom-right-radius: var(--borderRadius025);
|
|
133
|
-
}
|
|
134
|
-
`}
|
|
135
121
|
`}
|
|
136
122
|
|
|
137
123
|
a,
|
|
@@ -173,6 +159,19 @@ const StyledLink = styled.span`
|
|
|
173
159
|
`}
|
|
174
160
|
}
|
|
175
161
|
|
|
162
|
+
${!isSkipLink && !disabled && !theme.focusRedesignOptOut && hasFocus && css`
|
|
163
|
+
a,
|
|
164
|
+
button {
|
|
165
|
+
outline: none;
|
|
166
|
+
text-decoration: none;
|
|
167
|
+
border-bottom-left-radius: var(--borderRadius000);
|
|
168
|
+
border-bottom-right-radius: var(--borderRadius000);
|
|
169
|
+
}
|
|
170
|
+
box-shadow: 0 4px 0 0 var(--colorsUtilityYin090);
|
|
171
|
+
border-bottom-left-radius: var(--borderRadius025);
|
|
172
|
+
border-bottom-right-radius: var(--borderRadius025);
|
|
173
|
+
`}
|
|
174
|
+
|
|
176
175
|
button {
|
|
177
176
|
background-color: transparent;
|
|
178
177
|
border: none;
|
|
@@ -7,14 +7,6 @@ import menuConfigVariants from "../menu.config";
|
|
|
7
7
|
import Link from "../../link";
|
|
8
8
|
import addFocusStyling from "../../../style/utils/add-focus-styling";
|
|
9
9
|
import { baseTheme } from "../../../style/themes";
|
|
10
|
-
const overrideLinkFocusStyling = fullScreenView => `
|
|
11
|
-
&:focus-within {
|
|
12
|
-
box-shadow: none;
|
|
13
|
-
a {
|
|
14
|
-
background-color: ${fullScreenView ? "var(--colorsComponentsMenuAutumnStandard600)" : "transparent"};
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
`;
|
|
18
10
|
const oldFocusStyling = `
|
|
19
11
|
box-shadow: inset 0 0 0 var(--borderWidth300) var(--colorsSemanticFocus500);
|
|
20
12
|
`;
|
|
@@ -45,19 +37,13 @@ const StyledMenuItemWrapper = styled.a.attrs({
|
|
|
45
37
|
font-weight: 700;
|
|
46
38
|
height: 40px;
|
|
47
39
|
position: relative;
|
|
48
|
-
|
|
49
|
-
&& {
|
|
50
|
-
:focus-within > a, > button {
|
|
51
|
-
background-color: transparent;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
40
|
+
box-shadow: none;
|
|
54
41
|
|
|
55
42
|
a,
|
|
56
43
|
button {
|
|
57
44
|
cursor: pointer;
|
|
58
45
|
}
|
|
59
46
|
|
|
60
|
-
${overrideLinkFocusStyling(inFullscreenView)}
|
|
61
47
|
a:focus,
|
|
62
48
|
button:focus {
|
|
63
49
|
${({
|
|
@@ -135,7 +135,6 @@ Number.propTypes = {
|
|
|
135
135
|
"dir": PropTypes.string,
|
|
136
136
|
"disabled": PropTypes.bool,
|
|
137
137
|
"draggable": PropTypes.oneOfType([PropTypes.oneOf(["false", "true"]), PropTypes.bool]),
|
|
138
|
-
"enforceCharacterLimit": PropTypes.bool,
|
|
139
138
|
"enterKeyHint": PropTypes.oneOf(["done", "enter", "go", "next", "previous", "search", "send"]),
|
|
140
139
|
"error": PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
|
141
140
|
"fieldHelp": PropTypes.node,
|
|
@@ -26,8 +26,6 @@ export interface TextareaProps extends ValidationProps, MarginProps, Omit<Common
|
|
|
26
26
|
cols?: number;
|
|
27
27
|
/** If true, the component will be disabled */
|
|
28
28
|
disabled?: boolean;
|
|
29
|
-
/** Stop the user typing over the characterLimit */
|
|
30
|
-
enforceCharacterLimit?: boolean;
|
|
31
29
|
/** Indicate that error has occurred
|
|
32
30
|
Pass string to display icon, tooltip and red border
|
|
33
31
|
Pass true boolean to only display red border */
|
|
@@ -28,7 +28,6 @@ const Textarea = /*#__PURE__*/React.forwardRef(({
|
|
|
28
28
|
size,
|
|
29
29
|
children,
|
|
30
30
|
characterLimit,
|
|
31
|
-
enforceCharacterLimit = true,
|
|
32
31
|
onChange,
|
|
33
32
|
disabled = false,
|
|
34
33
|
labelInline,
|
|
@@ -119,7 +118,7 @@ const Textarea = /*#__PURE__*/React.forwardRef(({
|
|
|
119
118
|
label,
|
|
120
119
|
fieldHelp
|
|
121
120
|
});
|
|
122
|
-
const [
|
|
121
|
+
const [characterCount, visuallyHiddenHintId] = useCharacterCount(value, characterLimit);
|
|
123
122
|
useEffect(() => {
|
|
124
123
|
if (rows) {
|
|
125
124
|
minHeight.current = internalRef?.current?.scrollHeight || 0;
|
|
@@ -147,10 +146,8 @@ const Textarea = /*#__PURE__*/React.forwardRef(({
|
|
|
147
146
|
}, [expandable]);
|
|
148
147
|
const hasIconInside = !!(inputIcon || validationId && !validationOnLabel);
|
|
149
148
|
const hintId = useRef(guid());
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
const hintIdValue = characterLimit ? characterCountHintIdValue : inputHintIdValue;
|
|
153
|
-
const combinedAriaDescribedBy = [ariaDescribedBy, hintIdValue].filter(Boolean).join(" ");
|
|
149
|
+
const inputHintId = inputHint ? hintId.current : undefined;
|
|
150
|
+
const combinedAriaDescribedBy = [ariaDescribedBy, inputHintId, visuallyHiddenHintId].filter(Boolean).join(" ");
|
|
154
151
|
const input = /*#__PURE__*/React.createElement(InputPresentation, {
|
|
155
152
|
size: size,
|
|
156
153
|
disabled: disabled,
|
|
@@ -168,7 +165,6 @@ const Textarea = /*#__PURE__*/React.forwardRef(({
|
|
|
168
165
|
name: name,
|
|
169
166
|
value: value,
|
|
170
167
|
ref: callbackRef,
|
|
171
|
-
maxLength: maxLength,
|
|
172
168
|
onChange: onChange,
|
|
173
169
|
disabled: disabled,
|
|
174
170
|
readOnly: readOnly,
|
|
@@ -219,10 +215,10 @@ const Textarea = /*#__PURE__*/React.forwardRef(({
|
|
|
219
215
|
useValidationIcon: computeLabelPropValues(validationOnLabel),
|
|
220
216
|
adaptiveLabelBreakpoint: adaptiveLabelBreakpoint,
|
|
221
217
|
validationRedesignOptIn: validationRedesignOptIn
|
|
222
|
-
},
|
|
223
|
-
id:
|
|
218
|
+
}, inputHint ? /*#__PURE__*/React.createElement(StyledInputHint, {
|
|
219
|
+
id: inputHintId,
|
|
224
220
|
"data-element": "input-hint"
|
|
225
|
-
},
|
|
221
|
+
}, inputHint) : null, validationRedesignOptIn && labelHelp && /*#__PURE__*/React.createElement(StyledHintText, null, labelHelp), validationRedesignOptIn ? /*#__PURE__*/React.createElement(Box, {
|
|
226
222
|
position: "relative"
|
|
227
223
|
}, /*#__PURE__*/React.createElement(ValidationMessage, {
|
|
228
224
|
error: error,
|
|
@@ -317,7 +313,6 @@ Textarea.propTypes = {
|
|
|
317
313
|
"dir": PropTypes.string,
|
|
318
314
|
"disabled": PropTypes.bool,
|
|
319
315
|
"draggable": PropTypes.oneOfType([PropTypes.oneOf(["false", "true"]), PropTypes.bool]),
|
|
320
|
-
"enforceCharacterLimit": PropTypes.bool,
|
|
321
316
|
"enterKeyHint": PropTypes.oneOf(["done", "enter", "go", "next", "previous", "search", "send"]),
|
|
322
317
|
"error": PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
|
323
318
|
"expandable": PropTypes.bool,
|
|
@@ -94,8 +94,6 @@ export interface TextboxProps extends CommonTextboxProps {
|
|
|
94
94
|
positionedChildren?: React.ReactNode;
|
|
95
95
|
/** Character limit of the textarea */
|
|
96
96
|
characterLimit?: number;
|
|
97
|
-
/** Stop the user typing over the characterLimit */
|
|
98
|
-
enforceCharacterLimit?: boolean;
|
|
99
97
|
}
|
|
100
98
|
export declare const Textbox: React.ForwardRefExoticComponent<TextboxProps & React.RefAttributes<HTMLInputElement>>;
|
|
101
99
|
export default Textbox;
|
|
@@ -70,13 +70,13 @@ const Textbox = /*#__PURE__*/React.forwardRef(({
|
|
|
70
70
|
"data-component": dataComponent,
|
|
71
71
|
"data-element": dataElement,
|
|
72
72
|
"data-role": dataRole,
|
|
73
|
-
enforceCharacterLimit = true,
|
|
74
73
|
characterLimit,
|
|
75
74
|
helpAriaLabel,
|
|
76
75
|
...props
|
|
77
76
|
}, ref) => {
|
|
78
77
|
const characterCountValue = typeof value === "string" ? value : "";
|
|
79
|
-
const [
|
|
78
|
+
const [uniqueId, uniqueName] = useUniqueId(id, name);
|
|
79
|
+
const [characterCount, visuallyHiddenHintId] = useCharacterCount(characterCountValue, characterLimit);
|
|
80
80
|
const {
|
|
81
81
|
validationRedesignOptIn
|
|
82
82
|
} = useContext(NewValidationContext);
|
|
@@ -84,7 +84,6 @@ const Textbox = /*#__PURE__*/React.forwardRef(({
|
|
|
84
84
|
disableErrorBorder
|
|
85
85
|
} = useContext(NumeralDateContext);
|
|
86
86
|
const computeLabelPropValues = prop => validationRedesignOptIn ? undefined : prop;
|
|
87
|
-
const [uniqueId, uniqueName] = useUniqueId(id, name);
|
|
88
87
|
if (!deprecateInputRefWarnTriggered && inputRef) {
|
|
89
88
|
deprecateInputRefWarnTriggered = true;
|
|
90
89
|
Logger.deprecate("The `inputRef` prop in `Textbox` component is deprecated and will soon be removed. Please use `ref` instead.");
|
|
@@ -108,10 +107,8 @@ const Textbox = /*#__PURE__*/React.forwardRef(({
|
|
|
108
107
|
fieldHelp
|
|
109
108
|
});
|
|
110
109
|
const hintId = useRef(guid());
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const hintIdValue = characterLimit ? characterCountHintIdValue : inputHintIdValue;
|
|
114
|
-
const combinedAriaDescribedBy = [ariaDescribedBy, hintIdValue].filter(Boolean).join(" ");
|
|
110
|
+
const inputHintId = inputHint ? hintId.current : undefined;
|
|
111
|
+
const combinedAriaDescribedBy = [ariaDescribedBy, inputHintId, visuallyHiddenHintId].filter(Boolean).join(" ");
|
|
115
112
|
const hasIconInside = !!(inputIcon || validationId && !validationOnLabel);
|
|
116
113
|
const input = /*#__PURE__*/React.createElement(InputPresentation, {
|
|
117
114
|
align: align,
|
|
@@ -152,7 +149,6 @@ const Textbox = /*#__PURE__*/React.forwardRef(({
|
|
|
152
149
|
placeholder: disabled || readOnly ? "" : placeholder,
|
|
153
150
|
readOnly: readOnly,
|
|
154
151
|
value: typeof formattedValue === "string" ? formattedValue : value,
|
|
155
|
-
maxLength: maxLength,
|
|
156
152
|
validationIconId: validationRedesignOptIn ? undefined : validationId
|
|
157
153
|
}, props)), children, /*#__PURE__*/React.createElement(InputIconToggle, {
|
|
158
154
|
align: align,
|
|
@@ -197,10 +193,10 @@ const Textbox = /*#__PURE__*/React.forwardRef(({
|
|
|
197
193
|
"data-element": dataElement,
|
|
198
194
|
validationIconId: validationRedesignOptIn ? undefined : validationId,
|
|
199
195
|
validationRedesignOptIn: validationRedesignOptIn
|
|
200
|
-
}, filterStyledSystemMarginProps(props)),
|
|
201
|
-
id:
|
|
196
|
+
}, filterStyledSystemMarginProps(props)), inputHint ? /*#__PURE__*/React.createElement(StyledInputHint, {
|
|
197
|
+
id: inputHintId,
|
|
202
198
|
"data-element": "input-hint"
|
|
203
|
-
},
|
|
199
|
+
}, inputHint) : null, validationRedesignOptIn && labelHelp && /*#__PURE__*/React.createElement(StyledHintText, null, labelHelp), validationRedesignOptIn ? /*#__PURE__*/React.createElement(Box, {
|
|
204
200
|
position: "relative"
|
|
205
201
|
}, /*#__PURE__*/React.createElement(ValidationMessage, {
|
|
206
202
|
error: error,
|
|
@@ -295,7 +291,6 @@ Textbox.propTypes = {
|
|
|
295
291
|
"dir": PropTypes.string,
|
|
296
292
|
"disabled": PropTypes.bool,
|
|
297
293
|
"draggable": PropTypes.oneOfType([PropTypes.oneOf(["false", "true"]), PropTypes.bool]),
|
|
298
|
-
"enforceCharacterLimit": PropTypes.bool,
|
|
299
294
|
"enterKeyHint": PropTypes.oneOf(["done", "enter", "go", "next", "previous", "search", "send"]),
|
|
300
295
|
"error": PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
|
|
301
296
|
"fieldHelp": PropTypes.node,
|
|
@@ -1,8 +1,3 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
-
declare const useCharacterCount: (value?: string, characterLimit?: number
|
|
3
|
-
number | undefined,
|
|
4
|
-
JSX.Element | null,
|
|
5
|
-
string | undefined,
|
|
6
|
-
string | null
|
|
7
|
-
];
|
|
2
|
+
declare const useCharacterCount: (value?: string, characterLimit?: number) => [JSX.Element | null, string | undefined];
|
|
8
3
|
export default useCharacterCount;
|
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
import React, { useMemo, useRef } from "react";
|
|
1
|
+
import React, { useMemo, useRef, useEffect, useState } from "react";
|
|
2
2
|
import CharacterCount from "../../../__internal__/character-count";
|
|
3
|
-
import useLocale from "../useLocale";
|
|
4
3
|
import guid from "../../../__internal__/utils/helpers/guid";
|
|
5
|
-
|
|
4
|
+
import useDebounce from "../useDebounce";
|
|
5
|
+
const useCharacterCount = (value = "", characterLimit) => {
|
|
6
6
|
const isCharacterLimitValid = typeof characterLimit === "number" && !Number.isNaN(characterLimit);
|
|
7
|
-
const
|
|
8
|
-
const
|
|
7
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
8
|
+
const debounceWaitTime = 2000;
|
|
9
|
+
const updateDebouncedValue = useDebounce(newValue => {
|
|
10
|
+
setDebouncedValue(newValue);
|
|
11
|
+
}, debounceWaitTime);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (characterLimit) {
|
|
14
|
+
updateDebouncedValue(value);
|
|
15
|
+
}
|
|
16
|
+
}, [value, characterLimit, updateDebouncedValue]);
|
|
9
17
|
const hintId = useRef(guid());
|
|
10
18
|
const isOverLimit = useMemo(() => {
|
|
11
19
|
if (value && isCharacterLimitValid) {
|
|
@@ -13,11 +21,19 @@ const useCharacterCount = (value = "", characterLimit, enforceCharacterLimit = t
|
|
|
13
21
|
}
|
|
14
22
|
return false;
|
|
15
23
|
}, [value, characterLimit, isCharacterLimitValid]);
|
|
16
|
-
|
|
24
|
+
const isDebouncedOverLimit = useMemo(() => {
|
|
25
|
+
if (debouncedValue && isCharacterLimitValid) {
|
|
26
|
+
return debouncedValue.length > characterLimit;
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}, [debouncedValue, characterLimit, isCharacterLimitValid]);
|
|
30
|
+
return [isCharacterLimitValid ? /*#__PURE__*/React.createElement(CharacterCount, {
|
|
17
31
|
isOverLimit: isOverLimit,
|
|
32
|
+
isDebouncedOverLimit: isDebouncedOverLimit,
|
|
18
33
|
value: value.length,
|
|
34
|
+
debouncedValue: debouncedValue.length,
|
|
19
35
|
limit: characterLimit,
|
|
20
|
-
|
|
21
|
-
}) : null,
|
|
36
|
+
visuallyHiddenHintId: hintId.current
|
|
37
|
+
}) : null, isCharacterLimitValid ? hintId.current : undefined];
|
|
22
38
|
};
|
|
23
39
|
export default useCharacterCount;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./useDebounce";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./useDebounce";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useMemo, useEffect, useRef } from "react";
|
|
2
|
+
import debounce from "lodash/debounce";
|
|
3
|
+
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
|
+
|
|
6
|
+
const useDebounce = (callback, delay) => {
|
|
7
|
+
const callbackRef = useRef(callback);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
callbackRef.current = callback;
|
|
10
|
+
});
|
|
11
|
+
const debouncedCallback = useMemo(() => debounce(callbackRef.current, delay), [delay]);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
return () => {
|
|
14
|
+
debouncedCallback.cancel();
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
return debouncedCallback;
|
|
18
|
+
};
|
|
19
|
+
export default useDebounce;
|
package/esm/locales/en-gb.js
CHANGED
|
@@ -25,9 +25,9 @@ const enGB = {
|
|
|
25
25
|
yes: () => "Yes"
|
|
26
26
|
},
|
|
27
27
|
characterCount: {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
tooManyCharacters: (count, formattedCount) => count === 1 ? `${formattedCount} character too many` : `${formattedCount} characters too many`,
|
|
29
|
+
charactersLeft: (count, formattedCount) => count === 1 ? `${formattedCount} character left` : `${formattedCount} characters left`,
|
|
30
|
+
visuallyHiddenHint: formattedCount => `You can enter up to ${formattedCount} characters`
|
|
31
31
|
},
|
|
32
32
|
date: {
|
|
33
33
|
dateFnsLocale: () => enGBDateLocale
|
package/esm/locales/locale.d.ts
CHANGED
|
@@ -20,9 +20,9 @@ interface Locale {
|
|
|
20
20
|
ariaLabel: () => string;
|
|
21
21
|
};
|
|
22
22
|
characterCount: {
|
|
23
|
-
hintString: () => string;
|
|
24
23
|
tooManyCharacters: (count: number, formattedCount: string) => string;
|
|
25
24
|
charactersLeft: (count: number, formattedCount: string) => string;
|
|
25
|
+
visuallyHiddenHint: (formattedCount: string) => string;
|
|
26
26
|
};
|
|
27
27
|
confirm: {
|
|
28
28
|
no: () => string;
|
package/esm/locales/pl-pl.js
CHANGED
|
@@ -62,9 +62,9 @@ const plPL = {
|
|
|
62
62
|
yes: () => "Tak"
|
|
63
63
|
},
|
|
64
64
|
characterCount: {
|
|
65
|
-
hintString: () => "Pole zawiera licznik znaków",
|
|
66
65
|
tooManyCharacters: (count, formattedCount) => `Masz o ${formattedCount} ${PolishPlural("znak", "znaki", "znaków", count)} za dużo`,
|
|
67
|
-
charactersLeft: (count, formattedCount) => `Masz ${formattedCount} ${PolishPlural("pozostały", "pozostałe", "pozostałych", count)} ${PolishPlural("znak", "znaki", "znaków", count)}
|
|
66
|
+
charactersLeft: (count, formattedCount) => `Masz ${formattedCount} ${PolishPlural("pozostały", "pozostałe", "pozostałych", count)} ${PolishPlural("znak", "znaki", "znaków", count)}`,
|
|
67
|
+
visuallyHiddenHint: formattedCount => `Można wprowadzić do ${formattedCount} znaków`
|
|
68
68
|
},
|
|
69
69
|
date: {
|
|
70
70
|
dateFnsLocale: () => plDateLocale
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
interface CharacterCountProps {
|
|
3
3
|
value: number;
|
|
4
|
+
debouncedValue?: number;
|
|
4
5
|
limit: number;
|
|
6
|
+
isDebouncedOverLimit?: boolean;
|
|
5
7
|
isOverLimit: boolean;
|
|
6
|
-
|
|
8
|
+
visuallyHiddenHintId?: string;
|
|
7
9
|
}
|
|
8
|
-
declare const CharacterCount: ({ value, limit, isOverLimit,
|
|
10
|
+
declare const CharacterCount: ({ value, debouncedValue, limit, isDebouncedOverLimit, isOverLimit, visuallyHiddenHintId, }: CharacterCountProps) => React.JSX.Element;
|
|
9
11
|
export default CharacterCount;
|