@navikt/ds-react 5.10.4 → 5.11.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/_docs.json +38 -0
- package/cjs/form/Switch.js +3 -3
- package/cjs/form/Textarea.js +4 -3
- package/cjs/form/TextareaCounter.js +45 -6
- package/cjs/util/TextareaAutoSize.js +67 -69
- package/esm/form/Switch.js +3 -3
- package/esm/form/Switch.js.map +1 -1
- package/esm/form/Textarea.d.ts +7 -1
- package/esm/form/Textarea.js +4 -3
- package/esm/form/Textarea.js.map +1 -1
- package/esm/form/TextareaCounter.js +22 -6
- package/esm/form/TextareaCounter.js.map +1 -1
- package/esm/util/TextareaAutoSize.d.ts +4 -0
- package/esm/util/TextareaAutoSize.js +67 -69
- package/esm/util/TextareaAutoSize.js.map +1 -1
- package/package.json +3 -3
- package/src/form/Switch.tsx +7 -3
- package/src/form/Textarea.tsx +12 -2
- package/src/form/TextareaCounter.tsx +39 -12
- package/src/form/stories/textarea.stories.tsx +38 -2
- package/src/util/TextareaAutoSize.tsx +99 -82
|
@@ -9,53 +9,75 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
9
9
|
}
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
|
-
/* https://github.com/mui/material-ui/blob/master/packages/mui-base/src/TextareaAutosize/TextareaAutosize.
|
|
12
|
+
/* https://github.com/mui/material-ui/blob/master/packages/mui-base/src/TextareaAutosize/TextareaAutosize.tsx */
|
|
13
13
|
import React, { forwardRef, useEffect, useMemo, useRef, useState } from "react";
|
|
14
14
|
import ReactDOM from "react-dom";
|
|
15
15
|
import { debounce, mergeRefs, useClientLayoutEffect } from "../util";
|
|
16
|
+
const updateState = (prevState, newState, renders) => {
|
|
17
|
+
const { outerHeightStyle, overflow } = newState;
|
|
18
|
+
// Need a large enough difference to update the height.
|
|
19
|
+
// This prevents infinite rendering loop.
|
|
20
|
+
if (renders.current < 20 &&
|
|
21
|
+
((outerHeightStyle > 0 &&
|
|
22
|
+
Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) ||
|
|
23
|
+
prevState.overflow !== overflow)) {
|
|
24
|
+
renders.current += 1;
|
|
25
|
+
return {
|
|
26
|
+
overflow,
|
|
27
|
+
outerHeightStyle,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
if (process.env.NODE_ENV !== "production") {
|
|
31
|
+
if (renders.current === 20) {
|
|
32
|
+
console.error([
|
|
33
|
+
"Textarea: Too many re-renders. The layout is unstable.",
|
|
34
|
+
"TextareaAutosize limits the number of renders to prevent an infinite loop.",
|
|
35
|
+
].join("\n"));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return prevState;
|
|
39
|
+
};
|
|
16
40
|
/**
|
|
17
|
-
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerDocument.ts
|
|
18
|
-
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerWindow.ts
|
|
41
|
+
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerDocument/ownerDocument.ts
|
|
42
|
+
* https://github.com/mui/material-ui/blob/master/packages/mui-utils/src/ownerWindow/ownerWindow.ts
|
|
19
43
|
*/
|
|
20
44
|
const ownerWindow = (node) => {
|
|
21
45
|
const doc = (node && node.ownerDocument) || document;
|
|
22
46
|
return doc.defaultView || window;
|
|
23
47
|
};
|
|
24
|
-
function getStyleValue(
|
|
25
|
-
return parseInt(
|
|
48
|
+
function getStyleValue(value) {
|
|
49
|
+
return parseInt(value, 10) || 0;
|
|
26
50
|
}
|
|
27
51
|
const TextareaAutosize = forwardRef((_a, ref) => {
|
|
28
|
-
var { className, onChange, maxRows, minRows = 1, style, value } = _a, other = __rest(_a, ["className", "onChange", "maxRows", "minRows", "style", "value"]);
|
|
52
|
+
var { className, onChange, maxRows, minRows = 1, autoScrollbar, style, value } = _a, other = __rest(_a, ["className", "onChange", "maxRows", "minRows", "autoScrollbar", "style", "value"]);
|
|
29
53
|
const { current: isControlled } = useRef(value != null);
|
|
30
54
|
const inputRef = useRef(null);
|
|
31
55
|
const handleRef = useMemo(() => mergeRefs([inputRef, ref]), [ref]);
|
|
32
56
|
const shadowRef = useRef(null);
|
|
33
57
|
const renders = useRef(0);
|
|
34
|
-
const [state, setState] = useState({});
|
|
58
|
+
const [state, setState] = useState({ outerHeightStyle: 0 });
|
|
35
59
|
const getUpdatedState = React.useCallback(() => {
|
|
36
|
-
if (!inputRef.current || !shadowRef.current)
|
|
37
|
-
return;
|
|
38
60
|
const input = inputRef.current;
|
|
39
61
|
const containerWindow = ownerWindow(input);
|
|
40
62
|
const computedStyle = containerWindow.getComputedStyle(input);
|
|
41
63
|
// If input's width is shrunk and it's not visible, don't sync height.
|
|
42
64
|
if (computedStyle.width === "0px") {
|
|
43
|
-
return;
|
|
65
|
+
return { outerHeightStyle: 0 };
|
|
44
66
|
}
|
|
45
67
|
const inputShallow = shadowRef.current;
|
|
46
68
|
inputShallow.style.width = computedStyle.width;
|
|
47
|
-
inputShallow.value = input.value ||
|
|
69
|
+
inputShallow.value = input.value || other.placeholder || "x";
|
|
48
70
|
if (inputShallow.value.slice(-1) === "\n") {
|
|
49
71
|
// Certain fonts which overflow the line height will cause the textarea
|
|
50
72
|
// to report a different scrollHeight depending on whether the last line
|
|
51
73
|
// is empty. Make it non-empty to avoid this issue.
|
|
52
74
|
inputShallow.value += " ";
|
|
53
75
|
}
|
|
54
|
-
const boxSizing = computedStyle
|
|
55
|
-
const padding = getStyleValue(computedStyle
|
|
56
|
-
getStyleValue(computedStyle
|
|
57
|
-
const border = getStyleValue(computedStyle
|
|
58
|
-
getStyleValue(computedStyle
|
|
76
|
+
const boxSizing = computedStyle.boxSizing;
|
|
77
|
+
const padding = getStyleValue(computedStyle.paddingBottom) +
|
|
78
|
+
getStyleValue(computedStyle.paddingTop);
|
|
79
|
+
const border = getStyleValue(computedStyle.borderBottomWidth) +
|
|
80
|
+
getStyleValue(computedStyle.borderTopWidth);
|
|
59
81
|
// The height of the inner content
|
|
60
82
|
const innerHeight = inputShallow.scrollHeight - padding;
|
|
61
83
|
// Measure height of a textarea with a single row
|
|
@@ -74,77 +96,50 @@ const TextareaAutosize = forwardRef((_a, ref) => {
|
|
|
74
96
|
const outerHeightStyle = outerHeight + (boxSizing === "border-box" ? padding + border : 0);
|
|
75
97
|
const overflow = Math.abs(outerHeight - innerHeight) <= 1;
|
|
76
98
|
return { outerHeightStyle, overflow };
|
|
77
|
-
}, [maxRows, minRows, other
|
|
99
|
+
}, [maxRows, minRows, other.placeholder]);
|
|
78
100
|
const syncHeight = React.useCallback(() => {
|
|
79
101
|
const newState = getUpdatedState();
|
|
80
102
|
if (isEmpty(newState)) {
|
|
81
103
|
return;
|
|
82
104
|
}
|
|
83
|
-
setState((prevState) =>
|
|
84
|
-
return updateState(prevState, newState);
|
|
85
|
-
});
|
|
105
|
+
setState((prevState) => updateState(prevState, newState, renders));
|
|
86
106
|
}, [getUpdatedState]);
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
((outerHeightStyle > 0 &&
|
|
93
|
-
Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1) ||
|
|
94
|
-
prevState.overflow !== overflow)) {
|
|
95
|
-
renders.current += 1;
|
|
96
|
-
return {
|
|
97
|
-
overflow,
|
|
98
|
-
outerHeightStyle,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
if (process.env.NODE_ENV !== "production") {
|
|
102
|
-
if (renders.current === 20) {
|
|
103
|
-
console.error([
|
|
104
|
-
"Textarea: Too many re-renders. The layout is unstable.",
|
|
105
|
-
"TextareaAutosize limits the number of renders to prevent an infinite loop.",
|
|
106
|
-
].join("\n"));
|
|
107
|
+
useClientLayoutEffect(() => {
|
|
108
|
+
const syncHeightWithFlushSync = () => {
|
|
109
|
+
const newState = getUpdatedState();
|
|
110
|
+
if (isEmpty(newState)) {
|
|
111
|
+
return;
|
|
107
112
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
// In React 18, state updates in a ResizeObserver's callback are happening after the paint which causes flickering
|
|
117
|
-
// when doing some visual updates in it. Using flushSync ensures that the dom will be painted after the states updates happen
|
|
118
|
-
// Related issue - https://github.com/facebook/react/issues/24331
|
|
119
|
-
ReactDOM.flushSync(() => {
|
|
120
|
-
setState((prevState) => {
|
|
121
|
-
return updateState(prevState, newState);
|
|
113
|
+
// In React 18, state updates in a ResizeObserver's callback are happening after
|
|
114
|
+
// the paint, this leads to an infinite rendering.
|
|
115
|
+
//
|
|
116
|
+
// Using flushSync ensures that the states is updated before the next pain.
|
|
117
|
+
// Related issue - https://github.com/facebook/react/issues/24331
|
|
118
|
+
ReactDOM.flushSync(() => {
|
|
119
|
+
setState((prevState) => updateState(prevState, newState, renders));
|
|
122
120
|
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
React.useEffect(() => {
|
|
126
|
-
const handleResize = debounce(() => {
|
|
121
|
+
};
|
|
122
|
+
const handleResize = () => {
|
|
127
123
|
renders.current = 0;
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
});
|
|
132
|
-
let resizeObserver;
|
|
124
|
+
syncHeightWithFlushSync();
|
|
125
|
+
};
|
|
126
|
+
const debounceHandleResize = debounce(handleResize);
|
|
133
127
|
const input = inputRef.current;
|
|
134
128
|
const containerWindow = ownerWindow(input);
|
|
135
|
-
containerWindow.addEventListener("resize",
|
|
129
|
+
containerWindow.addEventListener("resize", debounceHandleResize);
|
|
130
|
+
let resizeObserver;
|
|
136
131
|
if (typeof ResizeObserver !== "undefined") {
|
|
137
132
|
resizeObserver = new ResizeObserver(handleResize);
|
|
138
133
|
resizeObserver.observe(input);
|
|
139
134
|
}
|
|
140
135
|
return () => {
|
|
141
|
-
|
|
142
|
-
containerWindow.removeEventListener("resize",
|
|
136
|
+
debounceHandleResize.clear();
|
|
137
|
+
containerWindow.removeEventListener("resize", debounceHandleResize);
|
|
143
138
|
if (resizeObserver) {
|
|
144
139
|
resizeObserver.disconnect();
|
|
145
140
|
}
|
|
146
141
|
};
|
|
147
|
-
});
|
|
142
|
+
}, [getUpdatedState]);
|
|
148
143
|
useClientLayoutEffect(() => {
|
|
149
144
|
syncHeight();
|
|
150
145
|
});
|
|
@@ -163,7 +158,10 @@ const TextareaAutosize = forwardRef((_a, ref) => {
|
|
|
163
158
|
return (React.createElement(React.Fragment, null,
|
|
164
159
|
React.createElement("textarea", Object.assign({ value: value, onChange: handleChange, ref: handleRef,
|
|
165
160
|
// Apply the rows prop to get a "correct" first SSR paint
|
|
166
|
-
rows: minRows, style: Object.assign(
|
|
161
|
+
rows: minRows, style: Object.assign({ height: state.outerHeightStyle,
|
|
162
|
+
// Need a large enough difference to allow scrolling.
|
|
163
|
+
// This prevents infinite rendering loop.
|
|
164
|
+
overflow: state.overflow && !autoScrollbar ? "hidden" : undefined }, style) }, other, { className: className })),
|
|
167
165
|
React.createElement("textarea", { "aria-hidden": true, className: className, readOnly: true, ref: shadowRef, tabIndex: -1, style: Object.assign({
|
|
168
166
|
// Visibility needed to hide the extra text area on iPads
|
|
169
167
|
visibility: "hidden",
|
|
@@ -178,7 +176,7 @@ function isEmpty(obj) {
|
|
|
178
176
|
return (obj === undefined ||
|
|
179
177
|
obj === null ||
|
|
180
178
|
Object.keys(obj).length === 0 ||
|
|
181
|
-
(
|
|
179
|
+
(obj.outerHeightStyle === 0 && !obj.overflow));
|
|
182
180
|
}
|
|
183
181
|
export default TextareaAutosize;
|
|
184
182
|
//# sourceMappingURL=TextareaAutoSize.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextareaAutoSize.js","sourceRoot":"","sources":["../../src/util/TextareaAutoSize.tsx"],"names":[],"mappings":";;;;;;;;;;;AAAA
|
|
1
|
+
{"version":3,"file":"TextareaAutoSize.js","sourceRoot":"","sources":["../../src/util/TextareaAutoSize.tsx"],"names":[],"mappings":";;;;;;;;;;;AAAA,gHAAgH;AAChH,OAAO,KAAK,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAChF,OAAO,QAAQ,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAOrE,MAAM,WAAW,GAAG,CAClB,SAAgB,EAChB,QAAe,EACf,OAAuC,EACvC,EAAE;IACF,MAAM,EAAE,gBAAgB,EAAE,QAAQ,EAAE,GAAG,QAAQ,CAAC;IAChD,uDAAuD;IACvD,yCAAyC;IACzC,IACE,OAAO,CAAC,OAAO,GAAG,EAAE;QACpB,CAAC,CAAC,gBAAgB,GAAG,CAAC;YACpB,IAAI,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,gBAAgB,IAAI,CAAC,CAAC,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;YACnE,SAAS,CAAC,QAAQ,KAAK,QAAQ,CAAC,EAClC;QACA,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;QACrB,OAAO;YACL,QAAQ;YACR,gBAAgB;SACjB,CAAC;KACH;IACD,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE;QACzC,IAAI,OAAO,CAAC,OAAO,KAAK,EAAE,EAAE;YAC1B,OAAO,CAAC,KAAK,CACX;gBACE,wDAAwD;gBACxD,4EAA4E;aAC7E,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;SACH;KACF;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,GAAG,CAAC,IAAsB,EAAU,EAAE;IACrD,MAAM,GAAG,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,QAAQ,CAAC;IACrD,OAAO,GAAG,CAAC,WAAW,IAAI,MAAM,CAAC;AACnC,CAAC,CAAC;AAEF,SAAS,aAAa,CAAC,KAAa;IAClC,OAAO,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAsBD,MAAM,gBAAgB,GAAG,UAAU,CACjC,CACE,EASwB,EACxB,GAAG,EACH,EAAE;QAXF,EACE,SAAS,EACT,QAAQ,EACR,OAAO,EACP,OAAO,GAAG,CAAC,EACX,aAAa,EACb,KAAK,EACL,KAAK,OAEiB,EADnB,KAAK,cARV,kFASC,CADS;IAIV,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,MAAM,CAAsB,IAAI,CAAC,CAAC;IACnD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACnE,MAAM,SAAS,GAAG,MAAM,CAAsB,IAAI,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1B,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAQ,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAC;IAEnE,MAAM,eAAe,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QAC7C,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAQ,CAAC;QAChC,MAAM,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QAC3C,MAAM,aAAa,GAAG,eAAe,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;QAE9D,sEAAsE;QACtE,IAAI,aAAa,CAAC,KAAK,KAAK,KAAK,EAAE;YACjC,OAAO,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC;SAChC;QAED,MAAM,YAAY,GAAG,SAAS,CAAC,OAAQ,CAAC;QACxC,YAAY,CAAC,KAAK,CAAC,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC;QAC/C,YAAY,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,WAAW,IAAI,GAAG,CAAC;QAC7D,IAAI,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE;YACzC,uEAAuE;YACvE,wEAAwE;YACxE,mDAAmD;YACnD,YAAY,CAAC,KAAK,IAAI,GAAG,CAAC;SAC3B;QAED,MAAM,SAAS,GAAG,aAAa,CAAC,SAAS,CAAC;QAC1C,MAAM,OAAO,GACX,aAAa,CAAC,aAAa,CAAC,aAAa,CAAC;YAC1C,aAAa,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,MAAM,GACV,aAAa,CAAC,aAAa,CAAC,iBAAiB,CAAC;YAC9C,aAAa,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC;QAE9C,kCAAkC;QAClC,MAAM,WAAW,GAAG,YAAY,CAAC,YAAY,GAAG,OAAO,CAAC;QAExD,iDAAiD;QACjD,YAAY,CAAC,KAAK,GAAG,GAAG,CAAC;QACzB,MAAM,eAAe,GAAG,YAAY,CAAC,YAAY,GAAG,OAAO,CAAC;QAE5D,kCAAkC;QAClC,IAAI,WAAW,GAAG,WAAW,CAAC;QAE9B,IAAI,OAAO,EAAE;YACX,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,eAAe,EAAE,WAAW,CAAC,CAAC;SACxE;QACD,IAAI,OAAO,EAAE;YACX,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,eAAe,EAAE,WAAW,CAAC,CAAC;SACxE;QACD,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;QAErD,uEAAuE;QACvE,MAAM,gBAAgB,GACpB,WAAW,GAAG,CAAC,SAAS,KAAK,YAAY,CAAC,CAAC,CAAC,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAE1D,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,CAAC;IACxC,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;IAE1C,MAAM,UAAU,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QACxC,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC;QAEnC,IAAI,OAAO,CAAC,QAAQ,CAAC,EAAE;YACrB,OAAO;SACR;QAED,QAAQ,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IACrE,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IAEtB,qBAAqB,CAAC,GAAG,EAAE;QACzB,MAAM,uBAAuB,GAAG,GAAG,EAAE;YACnC,MAAM,QAAQ,GAAG,eAAe,EAAE,CAAC;YAEnC,IAAI,OAAO,CAAC,QAAQ,CAAC,EAAE;gBACrB,OAAO;aACR;YAED,gFAAgF;YAChF,kDAAkD;YAClD,EAAE;YACF,2EAA2E;YAC3E,iEAAiE;YACjE,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE;gBACtB,QAAQ,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;YACrE,CAAC,CAAC,CAAC;QACL,CAAC,CAAC;QAEF,MAAM,YAAY,GAAG,GAAG,EAAE;YACxB,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC;YACpB,uBAAuB,EAAE,CAAC;QAC5B,CAAC,CAAC;QAEF,MAAM,oBAAoB,GAAG,QAAQ,CAAC,YAAY,CAAC,CAAC;QACpD,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAQ,CAAC;QAChC,MAAM,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QAE3C,eAAe,CAAC,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC;QAEjE,IAAI,cAA8B,CAAC;QACnC,IAAI,OAAO,cAAc,KAAK,WAAW,EAAE;YACzC,cAAc,GAAG,IAAI,cAAc,CAAC,YAAY,CAAC,CAAC;YAClD,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;SAC/B;QAED,OAAO,GAAG,EAAE;YACV,oBAAoB,CAAC,KAAK,EAAE,CAAC;YAC7B,eAAe,CAAC,mBAAmB,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC;YACpE,IAAI,cAAc,EAAE;gBAClB,cAAc,CAAC,UAAU,EAAE,CAAC;aAC7B;QACH,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IAEtB,qBAAqB,CAAC,GAAG,EAAE;QACzB,UAAU,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC;IACtB,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAEZ,MAAM,YAAY,GAAG,CAAC,KAA6C,EAAE,EAAE;QACrE,OAAO,CAAC,OAAO,GAAG,CAAC,CAAC;QAEpB,IAAI,CAAC,YAAY,EAAE;YACjB,UAAU,EAAE,CAAC;SACd;QAED,IAAI,QAAQ,EAAE;YACZ,QAAQ,CAAC,KAAK,CAAC,CAAC;SACjB;IACH,CAAC,CAAC;IAEF,OAAO,CACL;QACE,gDACE,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,YAAY,EACtB,GAAG,EAAE,SAAS;YACd,yDAAyD;YACzD,IAAI,EAAE,OAAO,EACb,KAAK,kBACH,MAAM,EAAE,KAAK,CAAC,gBAAgB;gBAC9B,qDAAqD;gBACrD,yCAAyC;gBACzC,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,IAC9D,KAAK,KAEN,KAAK,IACT,SAAS,EAAE,SAAS,IACpB;QACF,uDAEE,SAAS,EAAE,SAAS,EACpB,QAAQ,QACR,GAAG,EAAE,SAAS,EACd,QAAQ,EAAE,CAAC,CAAC,EACZ,KAAK;gBACH,yDAAyD;gBACzD,UAAU,EAAE,QAAQ;gBACpB,+BAA+B;gBAC/B,QAAQ,EAAE,UAAU;gBACpB,6BAA6B;gBAC7B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,CAAC,EACT,GAAG,EAAE,CAAC,EACN,IAAI,EAAE,CAAC;gBACP,oEAAoE;gBACpE,SAAS,EAAE,eAAe,IACvB,KAAK,IAEV,CACD,CACJ,CAAC;AACJ,CAAC,CACF,CAAC;AAEF,SAAS,OAAO,CAAC,GAAU;IACzB,OAAO,CACL,GAAG,KAAK,SAAS;QACjB,GAAG,KAAK,IAAI;QACZ,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC;QAC7B,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAC9C,CAAC;AACJ,CAAC;AAED,eAAe,gBAAgB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@navikt/ds-react",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.11.1",
|
|
4
4
|
"description": "Aksel react-components for NAV designsystem",
|
|
5
5
|
"author": "Aksel | NAV designsystem team",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@floating-ui/react": "0.25.4",
|
|
41
|
-
"@navikt/aksel-icons": "^5.
|
|
42
|
-
"@navikt/ds-tokens": "^5.
|
|
41
|
+
"@navikt/aksel-icons": "^5.11.1",
|
|
42
|
+
"@navikt/ds-tokens": "^5.11.1",
|
|
43
43
|
"@radix-ui/react-tabs": "1.0.0",
|
|
44
44
|
"@radix-ui/react-toggle-group": "1.0.0",
|
|
45
45
|
"clsx": "^1.2.1",
|
package/src/form/Switch.tsx
CHANGED
|
@@ -5,11 +5,11 @@ import React, {
|
|
|
5
5
|
useEffect,
|
|
6
6
|
useState,
|
|
7
7
|
} from "react";
|
|
8
|
-
import { FormFieldProps, useFormField } from "./useFormField";
|
|
9
|
-
import { ReadOnlyIcon } from "./ReadOnlyIcon";
|
|
10
8
|
import { Loader } from "../loader";
|
|
11
9
|
import { BodyShort } from "../typography";
|
|
12
10
|
import { omit } from "../util";
|
|
11
|
+
import { ReadOnlyIcon } from "./ReadOnlyIcon";
|
|
12
|
+
import { FormFieldProps, useFormField } from "./useFormField";
|
|
13
13
|
|
|
14
14
|
const SelectedIcon = () => (
|
|
15
15
|
<svg
|
|
@@ -134,7 +134,11 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
|
|
|
134
134
|
<span className="navds-switch__track">
|
|
135
135
|
<span className="navds-switch__thumb">
|
|
136
136
|
{loading ? (
|
|
137
|
-
<Loader
|
|
137
|
+
<Loader
|
|
138
|
+
size="xsmall"
|
|
139
|
+
aria-live="polite"
|
|
140
|
+
variant={checked ? "interaction" : "neutral"}
|
|
141
|
+
/>
|
|
138
142
|
) : checked ? (
|
|
139
143
|
<SelectedIcon />
|
|
140
144
|
) : null}
|
package/src/form/Textarea.tsx
CHANGED
|
@@ -45,7 +45,13 @@ export interface TextareaProps
|
|
|
45
45
|
/**
|
|
46
46
|
* Enables resizing of field
|
|
47
47
|
*/
|
|
48
|
-
resize?: boolean;
|
|
48
|
+
resize?: boolean | "vertical" | "horizontal";
|
|
49
|
+
/**
|
|
50
|
+
* Textarea will stop growing and get a scrollbar when there's no more room to grow.
|
|
51
|
+
* Requires `display:flex` on the parent.
|
|
52
|
+
* Experimental feature that may be removed or get breaking changes in a minor version.
|
|
53
|
+
*/
|
|
54
|
+
UNSAFE_autoScrollbar?: boolean;
|
|
49
55
|
/**
|
|
50
56
|
* i18n-translations for counter-text
|
|
51
57
|
*/
|
|
@@ -86,6 +92,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
86
92
|
maxLength,
|
|
87
93
|
hideLabel = false,
|
|
88
94
|
resize,
|
|
95
|
+
UNSAFE_autoScrollbar,
|
|
89
96
|
i18n,
|
|
90
97
|
readOnly,
|
|
91
98
|
...rest
|
|
@@ -121,7 +128,9 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
121
128
|
"navds-form-field--readonly": readOnly,
|
|
122
129
|
"navds-textarea--readonly": readOnly,
|
|
123
130
|
"navds-textarea--error": hasError,
|
|
124
|
-
"navds-textarea--
|
|
131
|
+
"navds-textarea--autoscrollbar": UNSAFE_autoScrollbar,
|
|
132
|
+
[`navds-textarea--resize-${resize === true ? "both" : resize}`]:
|
|
133
|
+
resize,
|
|
125
134
|
}
|
|
126
135
|
)}
|
|
127
136
|
>
|
|
@@ -157,6 +166,7 @@ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
|
157
166
|
: setControlledValue(e.target.value)
|
|
158
167
|
}
|
|
159
168
|
minRows={getMinRows()}
|
|
169
|
+
autoScrollbar={UNSAFE_autoScrollbar}
|
|
160
170
|
ref={ref}
|
|
161
171
|
readOnly={readOnly}
|
|
162
172
|
className={cl(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import cl from "clsx";
|
|
2
|
-
import React from "react";
|
|
2
|
+
import React, { useEffect, useState } from "react";
|
|
3
3
|
import { BodyShort } from "../typography";
|
|
4
|
+
import debounce from "../util/debounce";
|
|
4
5
|
import type { TextareaProps } from "./Textarea";
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
@@ -13,19 +14,45 @@ interface Props {
|
|
|
13
14
|
const TextareaCounter = ({ maxLength, currentLength, size, i18n }: Props) => {
|
|
14
15
|
const difference = maxLength - currentLength;
|
|
15
16
|
|
|
17
|
+
const [debouncedDiff, setDebouncedDiff] = useState(difference);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const debounceFunc = debounce(() => {
|
|
21
|
+
setDebouncedDiff(difference);
|
|
22
|
+
}, 2000);
|
|
23
|
+
debounceFunc();
|
|
24
|
+
|
|
25
|
+
return () => {
|
|
26
|
+
debounceFunc.clear();
|
|
27
|
+
};
|
|
28
|
+
}, [difference]);
|
|
29
|
+
|
|
16
30
|
return (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
31
|
+
<>
|
|
32
|
+
{difference < 20 && (
|
|
33
|
+
<span
|
|
34
|
+
role="status"
|
|
35
|
+
className="navds-textarea__sr-counter navds-sr-only"
|
|
36
|
+
>
|
|
37
|
+
{getCounterText(debouncedDiff, i18n)}
|
|
38
|
+
</span>
|
|
39
|
+
)}
|
|
40
|
+
|
|
41
|
+
<BodyShort
|
|
42
|
+
className={cl("navds-textarea__counter", {
|
|
43
|
+
"navds-textarea__counter--error": difference < 0,
|
|
44
|
+
})}
|
|
45
|
+
size={size}
|
|
46
|
+
>
|
|
47
|
+
{getCounterText(difference, i18n)}
|
|
48
|
+
</BodyShort>
|
|
49
|
+
</>
|
|
28
50
|
);
|
|
29
51
|
};
|
|
30
52
|
|
|
53
|
+
const getCounterText = (difference: number, i18n: TextareaProps["i18n"]) =>
|
|
54
|
+
difference < 0
|
|
55
|
+
? `${Math.abs(difference)} ${i18n?.counterTooMuch ?? "tegn for mye"}`
|
|
56
|
+
: `${difference} ${i18n?.counterLeft ?? "tegn igjen"}`;
|
|
57
|
+
|
|
31
58
|
export default TextareaCounter;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Meta, StoryObj } from "@storybook/react";
|
|
2
2
|
import React from "react";
|
|
3
|
+
import { Button } from "../../button";
|
|
3
4
|
import { Textarea } from "../index";
|
|
4
5
|
|
|
5
6
|
const meta: Meta<typeof Textarea> = {
|
|
@@ -16,10 +17,13 @@ export const Default: StoryObj<typeof Textarea> = {
|
|
|
16
17
|
args: {
|
|
17
18
|
maxLength: 0,
|
|
18
19
|
label: "Ipsum enim quis culpa",
|
|
19
|
-
resize: false,
|
|
20
20
|
},
|
|
21
21
|
|
|
22
22
|
argTypes: {
|
|
23
|
+
resize: {
|
|
24
|
+
control: { type: "radio" },
|
|
25
|
+
options: [true, "vertical", "horizontal"],
|
|
26
|
+
},
|
|
23
27
|
size: {
|
|
24
28
|
control: { type: "radio" },
|
|
25
29
|
options: ["medium", "small"],
|
|
@@ -99,7 +103,7 @@ export const HideLabel = () => {
|
|
|
99
103
|
};
|
|
100
104
|
|
|
101
105
|
export const MaxLength = () => {
|
|
102
|
-
return <Textarea maxLength={
|
|
106
|
+
return <Textarea maxLength={50} label="Ipsum enim quis culpa" />;
|
|
103
107
|
};
|
|
104
108
|
|
|
105
109
|
export const MinRows = () => {
|
|
@@ -138,3 +142,35 @@ export const Readonly = () => {
|
|
|
138
142
|
</div>
|
|
139
143
|
);
|
|
140
144
|
};
|
|
145
|
+
|
|
146
|
+
export const AutoScrollbar = (props) => (
|
|
147
|
+
<div
|
|
148
|
+
style={{
|
|
149
|
+
border: "1px solid lightGreen",
|
|
150
|
+
height: "90vh",
|
|
151
|
+
display: "flex",
|
|
152
|
+
flexDirection: "column",
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
<div style={{ border: "1px dashed gray" }}>
|
|
156
|
+
<h1>Header</h1>
|
|
157
|
+
</div>
|
|
158
|
+
<Textarea
|
|
159
|
+
resize
|
|
160
|
+
label="Textarea with autoScrollbar"
|
|
161
|
+
description="Description"
|
|
162
|
+
maxLength={30}
|
|
163
|
+
UNSAFE_autoScrollbar
|
|
164
|
+
{...props}
|
|
165
|
+
/>
|
|
166
|
+
<div style={{ border: "1px dashed gray" }}>
|
|
167
|
+
<Button>Send</Button>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
);
|
|
171
|
+
AutoScrollbar.argTypes = {
|
|
172
|
+
error: { type: "string" },
|
|
173
|
+
hideLabel: { type: "boolean" },
|
|
174
|
+
maxRows: { type: "number" },
|
|
175
|
+
minRows: { type: "number" },
|
|
176
|
+
};
|