certimate/ui/src/components/CodeInput.tsx
2025-09-09 22:07:43 +08:00

122 lines
4.0 KiB
TypeScript

import { useContext, useMemo, useRef } from "react";
import { json } from "@codemirror/lang-json";
import { yaml } from "@codemirror/lang-yaml";
import { StreamLanguage } from "@codemirror/language";
import { powerShell } from "@codemirror/legacy-modes/mode/powershell";
import { shell } from "@codemirror/legacy-modes/mode/shell";
import { basicSetup } from "@uiw/codemirror-extensions-basic-setup";
import { vscodeDark, vscodeLight } from "@uiw/codemirror-theme-vscode";
import CodeMirror, { type ReactCodeMirrorProps, type ReactCodeMirrorRef } from "@uiw/react-codemirror";
import { useFocusWithin, useHover } from "ahooks";
import { theme } from "antd";
import DisabledContext from "antd/es/config-provider/DisabledContext";
import { useBrowserTheme } from "@/hooks";
import { mergeCls } from "@/utils/css";
export interface CodeInputProps extends Omit<ReactCodeMirrorProps, "extensions" | "lang" | "theme"> {
disabled?: boolean;
language?: string | string[];
readOnly?: boolean;
}
const CodeInput = ({ className, style, disabled, language, readOnly, ...props }: CodeInputProps) => {
const { token: themeToken } = theme.useToken();
const { theme: browserTheme } = useBrowserTheme();
const injectedDisabled = useContext(DisabledContext);
const mergedDisabled = disabled ?? injectedDisabled;
const cmRef = useRef<ReactCodeMirrorRef>(null);
const isFocusing = useFocusWithin(cmRef.current?.editor);
const isHovering = useHover(cmRef.current?.editor);
const cmTheme = useMemo(() => {
if (browserTheme === "dark") {
return vscodeDark;
}
return vscodeLight;
}, [browserTheme]);
const cmExtensions = useMemo(() => {
const temp: NonNullable<ReactCodeMirrorProps["extensions"]> = [
basicSetup({
foldGutter: false,
dropCursor: false,
allowMultipleSelections: false,
indentOnInput: false,
}),
];
const langs = Array.isArray(language) ? language : [language];
langs.forEach((lang) => {
switch (lang) {
case "shell":
temp.push(StreamLanguage.define(shell));
break;
case "json":
temp.push(json());
break;
case "powershell":
temp.push(StreamLanguage.define(powerShell));
break;
case "yaml":
temp.push(yaml());
break;
}
});
return temp;
}, [language]);
return (
<div
className={mergeCls("ant-input", className)}
style={{
...style,
paddingBlock: themeToken.Input?.paddingBlock,
paddingInline: themeToken.Input?.paddingInline,
fontSize: themeToken.Input?.inputFontSize,
lineHeight: themeToken.lineHeight,
color: mergedDisabled ? themeToken.colorTextDisabled : themeToken.colorText,
backgroundColor: mergedDisabled
? themeToken.colorBgContainerDisabled
: isFocusing
? (themeToken.Input?.activeBg ?? themeToken.colorBgContainer)
: isHovering
? (themeToken.Input?.hoverBg ?? themeToken.colorBgContainer)
: void 0,
borderWidth: `${themeToken.lineWidth}px`,
borderStyle: themeToken.lineType,
borderColor: isFocusing
? (themeToken.Input?.activeBorderColor ?? themeToken.colorPrimaryActive)
: isHovering
? (themeToken.Input?.hoverBorderColor ?? themeToken.colorPrimaryHover)
: themeToken.colorBorder,
borderRadius: `${themeToken.borderRadius}px`,
boxShadow: isFocusing ? themeToken.Input?.activeShadow : void 0,
overflow: "hidden",
}}
>
<CodeMirror
ref={cmRef}
height="100%"
style={{ height: "100%" }}
{...props}
basicSetup={{
foldGutter: false,
dropCursor: false,
allowMultipleSelections: false,
indentOnInput: false,
}}
extensions={cmExtensions}
readOnly={readOnly || mergedDisabled}
theme={cmTheme}
/>
</div>
);
};
export default CodeInput;