braid-design-system
Version:
Themeable design system for the SEEK Group
136 lines (135 loc) • 4.47 kB
JavaScript
import { jsx, jsxs } from "react/jsx-runtime";
import { forwardRef, useState, useRef, useCallback } from "react";
import { Box } from "../Box/Box.mjs";
import { Field } from "../private/Field/Field.mjs";
import { getCharacterLimitStatus } from "../private/Field/getCharacterLimitStatus.mjs";
import { formatRanges } from "./formatRanges.mjs";
import { highlights, field } from "./Textarea.css.mjs";
const pxToInt = (str) => typeof str === "string" ? parseInt(str.replace("px", ""), 10) : 0;
const calculateLines = (target, lines, lineLimit) => {
const { paddingBottom, paddingTop, lineHeight } = window.getComputedStyle(target);
if (!lineHeight.endsWith("px")) {
return lines;
}
const padding = pxToInt(paddingTop) + pxToInt(paddingBottom);
const currentRows = Math.floor(
(target.scrollHeight - padding) / pxToInt(lineHeight)
);
if (target && target.value === "") {
return lines;
}
return typeof lineLimit === "number" && currentRows > lineLimit ? lineLimit : currentRows;
};
const Textarea = forwardRef(
({
value,
onChange,
onBlur,
onFocus,
onPaste,
placeholder,
characterLimit,
highlightRanges: highlightRangesProp = [],
lines = 3,
lineLimit,
grow = true,
tone,
spellCheck,
...restProps
}, ref) => {
const [rows, setRows] = useState(lines);
const highlightsRef = useRef(null);
const updateScroll = useCallback(
(scrollTop) => {
if (highlightsRef.current) {
highlightsRef.current.scrollTop = scrollTop;
}
},
[highlightsRef]
);
const inputLength = String(value).length;
const hasExceededCharacterLimit = characterLimit && inputLength > characterLimit;
const highlightTone = !hasExceededCharacterLimit && (tone === "critical" || tone === "caution") ? tone : "critical";
const highlightRanges = hasExceededCharacterLimit ? [{ start: characterLimit }] : highlightRangesProp;
const hasHighlights = highlightRanges.length > 0;
return /* @__PURE__ */ jsx(
Field,
{
...restProps,
componentName: "Textarea",
tone,
value,
icon: void 0,
prefix: void 0,
secondaryMessage: characterLimit ? getCharacterLimitStatus({
value,
characterLimit
}) : null,
children: (overlays, { className, borderRadius, background, ...fieldProps }) => /* @__PURE__ */ jsxs(
Box,
{
position: "relative",
width: "full",
zIndex: 0,
background,
borderRadius,
children: [
hasHighlights ? /* @__PURE__ */ jsx(
Box,
{
ref: highlightsRef,
position: "absolute",
overflow: "hidden",
pointerEvents: "none",
height: "full",
"aria-hidden": "true",
top: 0,
left: 0,
className: [highlights, className],
...fieldProps,
children: formatRanges(String(value), highlightRanges, highlightTone)
}
) : null,
/* @__PURE__ */ jsx(
Box,
{
component: "textarea",
position: "relative",
zIndex: 1,
rows,
value,
onChange: (e) => {
if (grow) {
setRows(calculateLines(e.currentTarget, lines, lineLimit));
}
if (typeof onChange === "function") {
onChange(e);
}
if (hasHighlights) {
updateScroll(e.currentTarget.scrollTop);
}
},
onBlur,
onFocus,
onPaste,
onScroll: hasHighlights ? (event) => updateScroll(event.currentTarget.scrollTop) : void 0,
placeholder: !restProps.disabled ? placeholder : void 0,
spellCheck,
className: [field, className],
borderRadius,
...fieldProps,
ref
}
),
overlays
]
}
)
}
);
}
);
Textarea.displayName = "Textarea";
export {
Textarea
};