@carbon/react
Version:
React components for the Carbon Design System
225 lines (223 loc) • 8.04 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 2026
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import { usePrefix } from "../../internal/usePrefix.js";
import { useId } from "../../internal/useId.js";
import { deprecate } from "../../prop-types/deprecate.js";
import { deprecateValuesWithin } from "../../prop-types/deprecateValuesWithin.js";
import { mapPopoverAlign } from "../../tools/mapPopoverAlign.js";
import Button_default from "../Button/index.js";
import { useResizeObserver } from "../../internal/useResizeObserver.js";
import Copy_default from "../Copy/index.js";
import CopyButton_default from "../CopyButton/index.js";
import classNames from "classnames";
import { useCallback, useRef, useState } from "react";
import PropTypes from "prop-types";
import { jsx, jsxs } from "react/jsx-runtime";
import { ChevronDown } from "@carbon/icons-react";
import copy from "copy-to-clipboard";
//#region src/components/CodeSnippet/CodeSnippet.tsx
/**
* Copyright IBM Corp. 2016, 2025
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
const rowHeightInPixels = 16;
const defaultMaxCollapsedNumberOfRows = 15;
const defaultMaxExpandedNumberOfRows = 0;
const defaultMinCollapsedNumberOfRows = 3;
const defaultMinExpandedNumberOfRows = 16;
function CodeSnippet({ align = "bottom", autoAlign = false, className, type = "single", children, disabled, feedback, feedbackTimeout, onClick, ["aria-label"]: ariaLabel = "Copy to clipboard", ariaLabel: deprecatedAriaLabel, copyText, copyButtonDescription, light, showMoreText = "Show more", showLessText = "Show less", hideCopyButton, wrapText = false, maxCollapsedNumberOfRows = defaultMaxCollapsedNumberOfRows, maxExpandedNumberOfRows = defaultMaxExpandedNumberOfRows, minCollapsedNumberOfRows = defaultMinCollapsedNumberOfRows, minExpandedNumberOfRows = defaultMinExpandedNumberOfRows, ...rest }) {
const [expandedCode, setExpandedCode] = useState(false);
const [shouldShowMoreLessBtn, setShouldShowMoreLessBtn] = useState(false);
const { current: uid } = useRef(useId());
const codeContentRef = useRef(null);
const codeContainerRef = useRef(null);
const innerCodeRef = useRef(null);
const getCodeRef = useCallback(() => {
if (type === "single") return codeContainerRef;
if (type === "multi") return codeContentRef;
else return innerCodeRef;
}, [type]);
const prefix = usePrefix();
useResizeObserver({
ref: getCodeRef(),
onResize: () => {
if (innerCodeRef?.current && type === "multi") {
const { height } = innerCodeRef.current.getBoundingClientRect();
if (maxCollapsedNumberOfRows > 0 && (maxExpandedNumberOfRows <= 0 || maxExpandedNumberOfRows > maxCollapsedNumberOfRows) && height > maxCollapsedNumberOfRows * rowHeightInPixels) setShouldShowMoreLessBtn(true);
else setShouldShowMoreLessBtn(false);
if (expandedCode && minExpandedNumberOfRows > 0 && height <= minExpandedNumberOfRows * rowHeightInPixels) setExpandedCode(false);
}
}
});
const handleCopyClick = (evt) => {
if (copyText || innerCodeRef?.current) copy(copyText ?? innerCodeRef?.current?.innerText ?? "");
if (onClick) onClick(evt);
};
const codeSnippetClasses = classNames(className, `${prefix}--snippet`, {
[`${prefix}--snippet--${type}`]: type,
[`${prefix}--snippet--disabled`]: type !== "inline" && disabled,
[`${prefix}--snippet--expand`]: expandedCode,
[`${prefix}--snippet--light`]: light,
[`${prefix}--snippet--no-copy`]: hideCopyButton,
[`${prefix}--snippet--wraptext`]: wrapText
});
const expandCodeBtnText = expandedCode ? showLessText : showMoreText;
if (type === "inline") {
if (hideCopyButton) return /* @__PURE__ */ jsx("span", {
className: codeSnippetClasses,
children: /* @__PURE__ */ jsx("code", {
id: uid,
ref: innerCodeRef,
children
})
});
return /* @__PURE__ */ jsx(Copy_default, {
...rest,
align,
autoAlign,
onClick: handleCopyClick,
"aria-label": deprecatedAriaLabel || ariaLabel,
"aria-describedby": uid,
className: codeSnippetClasses,
disabled,
feedback,
feedbackTimeout,
children: /* @__PURE__ */ jsx("code", {
id: uid,
ref: innerCodeRef,
children
})
});
}
const containerStyle = {};
if (type === "multi") {
const styles = {};
if (expandedCode) {
if (maxExpandedNumberOfRows > 0) styles.maxHeight = maxExpandedNumberOfRows * rowHeightInPixels;
if (minExpandedNumberOfRows > 0) styles.minHeight = minExpandedNumberOfRows * rowHeightInPixels;
} else {
if (maxCollapsedNumberOfRows > 0) styles.maxHeight = maxCollapsedNumberOfRows * rowHeightInPixels;
if (minCollapsedNumberOfRows > 0) styles.minHeight = minCollapsedNumberOfRows * rowHeightInPixels;
}
if (Object.keys(styles).length) containerStyle.style = styles;
}
return /* @__PURE__ */ jsxs("div", {
...rest,
className: codeSnippetClasses,
children: [
/* @__PURE__ */ jsx("div", {
ref: codeContainerRef,
role: type === "single" || type === "multi" ? "textbox" : void 0,
tabIndex: (type === "single" || type === "multi") && !disabled ? 0 : void 0,
className: `${prefix}--snippet-container`,
"aria-label": deprecatedAriaLabel || ariaLabel || "code-snippet",
"aria-readonly": type === "single" || type === "multi" ? true : void 0,
"aria-multiline": type === "multi" ? true : void 0,
...containerStyle,
children: /* @__PURE__ */ jsx("pre", {
ref: codeContentRef,
...containerStyle,
children: /* @__PURE__ */ jsx("code", {
ref: innerCodeRef,
children
})
})
}),
!hideCopyButton && /* @__PURE__ */ jsx(CopyButton_default, {
align,
autoAlign,
size: type === "multi" ? "sm" : "md",
disabled,
onClick: handleCopyClick,
feedback,
feedbackTimeout,
iconDescription: copyButtonDescription
}),
shouldShowMoreLessBtn && /* @__PURE__ */ jsxs(Button_default, {
kind: "ghost",
size: "sm",
className: `${prefix}--snippet-btn--expand`,
disabled,
onClick: () => setExpandedCode(!expandedCode),
children: [/* @__PURE__ */ jsx("span", {
className: `${prefix}--snippet-btn--text`,
children: expandCodeBtnText
}), /* @__PURE__ */ jsx(ChevronDown, {
className: `${prefix}--icon-chevron--down ${prefix}--snippet__icon`,
name: "chevron--down",
role: "img"
})]
})
]
});
}
CodeSnippet.propTypes = {
align: deprecateValuesWithin(PropTypes.oneOf([
"top",
"top-left",
"top-right",
"bottom",
"bottom-left",
"bottom-right",
"left",
"left-bottom",
"left-top",
"right",
"right-bottom",
"right-top",
"top-start",
"top-end",
"bottom-start",
"bottom-end",
"left-end",
"left-start",
"right-end",
"right-start"
]), [
"top",
"top-start",
"top-end",
"bottom",
"bottom-start",
"bottom-end",
"left",
"left-start",
"left-end",
"right",
"right-start",
"right-end"
], mapPopoverAlign),
["aria-label"]: PropTypes.string,
ariaLabel: deprecate(PropTypes.string, "This prop syntax has been deprecated. Please use the new `aria-label`."),
autoAlign: PropTypes.bool,
children: PropTypes.node,
className: PropTypes.string,
copyButtonDescription: PropTypes.string,
copyText: PropTypes.string,
disabled: PropTypes.bool,
feedback: PropTypes.string,
feedbackTimeout: PropTypes.number,
hideCopyButton: PropTypes.bool,
light: deprecate(PropTypes.bool, "The `light` prop for `CodeSnippet` has been deprecated in favor of the new `Layer` component. It will be removed in the next major release."),
maxCollapsedNumberOfRows: PropTypes.number,
maxExpandedNumberOfRows: PropTypes.number,
minCollapsedNumberOfRows: PropTypes.number,
minExpandedNumberOfRows: PropTypes.number,
onClick: PropTypes.func,
showLessText: PropTypes.string,
showMoreText: PropTypes.string,
type: PropTypes.oneOf([
"single",
"inline",
"multi"
]),
wrapText: PropTypes.bool
};
//#endregion
export { CodeSnippet as default };