Merge branch 'main' into feat/showWarningOnImportedSecretDeletion

This commit is contained in:
carlosmonastyrski
2025-04-14 09:04:47 -03:00
423 changed files with 14637 additions and 1130 deletions
@@ -0,0 +1,159 @@
/* eslint-disable no-underscore-dangle */
import { forwardRef, InputHTMLAttributes } from "react";
import { InitialConfigType, LexicalComposer } from "@lexical/react/LexicalComposer";
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
import { ReactNode } from "@tanstack/react-router";
import { cva, VariantProps } from "cva";
import { EditorState, LexicalEditor } from "lexical";
import { twMerge } from "tailwind-merge";
import { HighlightNode } from "./EditorHighlight";
import { EditorPlaceholderPlugin } from "./EditorPlaceholderPlugin";
// Catch any errors that occur during Lexical updates and log them
// or throw them as needed. If you don't throw them, Lexical will
// try to recover gracefully without losing user data.
function onError(error: Error) {
console.error(error);
}
const inputVariants = cva(
"input w-full py-[0.375rem] text-gray-400 placeholder:text-sm placeholder-gray-500 placeholder-opacity-50 outline-none focus:ring-2 hover:ring-bunker-400/60 duration-100",
{
variants: {
size: {
xs: ["text-xs"],
sm: ["text-sm"],
md: ["text-md"],
lg: ["text-lg"]
},
isRounded: {
true: ["rounded-md"],
false: ""
},
variant: {
filled: ["bg-mineshaft-900", "text-gray-400"],
outline: ["bg-transparent"],
plain: "bg-transparent outline-none"
},
isError: {
true: "focus:ring-red/50 placeholder-red-300",
false: "focus:ring-primary-400/50 focus:ring-1"
}
},
compoundVariants: []
}
);
const inputParentContainerVariants = cva("inline-flex font-inter items-center border relative", {
variants: {
isRounded: {
true: ["rounded-md"],
false: ""
},
isError: {
true: "border-red",
false: "border-mineshaft-500"
},
isFullWidth: {
true: "w-full",
false: ""
},
variant: {
filled: ["bg-bunker-800", "text-gray-400"],
outline: ["bg-transparent"],
plain: "border-none"
}
}
});
type Props = Omit<
InputHTMLAttributes<HTMLDivElement>,
"size" | "onChange" | "placeholder" | "aria-placeholder"
> &
VariantProps<typeof inputVariants> & {
children?: ReactNode;
namespace?: string;
placeholder?: string;
isFullWidth?: boolean;
isRequired?: boolean;
leftIcon?: ReactNode;
rightIcon?: ReactNode;
isDisabled?: boolean;
isReadOnly?: boolean;
containerClassName?: string;
onChange: (editorState: EditorState, editor: LexicalEditor, tags: Set<string>) => void;
initialValue?: string;
};
export const Editor = forwardRef<HTMLDivElement, Props>(
(
{
children,
namespace = "infisical-editor",
className,
containerClassName,
isRounded = true,
isFullWidth = true,
isDisabled,
isError = false,
isRequired,
leftIcon,
rightIcon,
variant = "filled",
size = "md",
isReadOnly,
placeholder,
onChange,
...props
},
ref
) => {
const initialConfig: InitialConfigType = {
namespace,
onError,
nodes: [HighlightNode]
};
return (
<div
className={inputParentContainerVariants({
isRounded,
isError,
isFullWidth,
variant,
className: containerClassName
})}
>
{leftIcon && <span className="absolute left-0 ml-3 text-sm">{leftIcon}</span>}
<LexicalComposer initialConfig={initialConfig}>
<PlainTextPlugin
contentEditable={
<ContentEditable
ref={ref}
aria-required={isRequired}
readOnly={isReadOnly}
disabled={isDisabled}
className={twMerge(
leftIcon ? "pl-10" : "pl-2.5",
rightIcon ? "pr-10" : "pr-2.5",
inputVariants({ className, isError, size, isRounded, variant })
)}
{...props}
placeholder={null}
/>
}
ErrorBoundary={LexicalErrorBoundary}
/>
<OnChangePlugin onChange={onChange} />
<EditorPlaceholderPlugin placeholder={placeholder} />
{children}
</LexicalComposer>
{rightIcon && <span className="absolute right-0 mr-3">{rightIcon}</span>}
</div>
);
}
);
@@ -0,0 +1,127 @@
/* eslint-disable no-underscore-dangle,@typescript-eslint/class-methods-use-this */
import { useCallback, useEffect } from "react";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { useLexicalTextEntity } from "@lexical/react/useLexicalTextEntity";
import {
$applyNodeReplacement,
EditorConfig,
LexicalNode,
SerializedTextNode,
Spread,
TextNode
} from "lexical";
type HighlightTheme = { contentClassName: string };
type Trigger = { startTrigger: string; endTrigger: string };
export type SerializedHighlightNode = Spread<
{
__highlightTheme: HighlightTheme;
__trigger: Trigger;
},
SerializedTextNode
>;
export class HighlightNode extends TextNode {
__highlightTheme: HighlightTheme;
__trigger: Trigger;
constructor(
text: string,
highlightTheme: HighlightTheme = {
contentClassName: "ph-no-capture text-yellow-200/80"
},
trigger: Trigger = { startTrigger: "${", endTrigger: "}" },
key?: string
) {
super(text, key);
this.__highlightTheme = highlightTheme;
this.__trigger = trigger;
}
static getType(): string {
return "highlight";
}
static clone(node: HighlightNode): HighlightNode {
return new HighlightNode(node.__text, node.__highlightTheme, node.__trigger, node.__key);
}
static importJSON(serializedNode: SerializedHighlightNode): HighlightNode {
return $applyNodeReplacement(new HighlightNode("")).updateFromJSON(serializedNode);
}
createDOM(config: EditorConfig): HTMLElement {
const dom = super.createDOM(config);
dom.style.cursor = "default";
dom.className = this.__highlightTheme.contentClassName;
return dom;
}
canInsertTextBefore(): boolean {
return false;
}
canInsertTextAfter(): boolean {
return false;
}
isTextEntity(): true {
return true;
}
}
export function $createKeywordNode(keyword: string = ""): HighlightNode {
return $applyNodeReplacement(new HighlightNode(keyword));
}
export function $isKeywordNode(node: LexicalNode | null | undefined): boolean {
return node instanceof HighlightNode;
}
type Props = {
contentClassName?: string;
startTrigger?: string;
endTrigger?: string;
};
export const EditorHighlightPlugin = ({
endTrigger = "}",
startTrigger = "${",
contentClassName = "ph-no-capture text-yellow-200/80"
}: Props) => {
const [editor] = useLexicalComposerContext();
useEffect(() => {
if (!editor.hasNodes([HighlightNode])) {
throw new Error("HighlightsPlugin: HighlightsNode not registered on editor");
}
}, [editor]);
const createKeywordNode = useCallback((textNode: TextNode): HighlightNode => {
return $applyNodeReplacement(
new HighlightNode(
textNode.getTextContent(),
{ contentClassName },
{ startTrigger, endTrigger }
)
);
}, []);
const getKeywordMatch = useCallback((text: string) => {
for (let i = 0; i < text.length; i += 1) {
if (text.slice(i, i + 2) === startTrigger) {
const closingBracketIndex = text.indexOf(endTrigger, i + 2);
if (closingBracketIndex !== -1) {
return { start: i, end: closingBracketIndex + 1 };
}
return null;
}
}
return null;
}, []);
useLexicalTextEntity<HighlightNode>(getKeywordMatch, HighlightNode, createKeywordNode);
return null;
};
@@ -0,0 +1,22 @@
import { useEffect } from "react";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { useLexicalIsTextContentEmpty } from "@lexical/react/useLexicalIsTextContentEmpty";
export const EditorPlaceholderPlugin = ({ placeholder }: { placeholder: string | undefined }) => {
const [editor] = useLexicalComposerContext();
const isEmpty = useLexicalIsTextContentEmpty(editor);
/* Set the placeholder on root. */
useEffect(() => {
const rootElement = editor.getRootElement() as HTMLElement;
if (rootElement) {
if (isEmpty && placeholder) {
rootElement.setAttribute("placeholder", placeholder);
} else {
rootElement.removeAttribute("placeholder");
}
}
}, [editor, isEmpty]); // eslint-disable-line
return null;
};
@@ -0,0 +1,2 @@
export { Editor } from "./Editor";
export { EditorHighlightPlugin } from "./EditorHighlight";
+1
View File
@@ -11,6 +11,7 @@ export * from "./DatePicker";
export * from "./DeleteActionModal";
export * from "./Drawer";
export * from "./Dropdown";
export * from "./Editor";
export * from "./EmailServiceSetupModal";
export * from "./EmptyState";
export * from "./FilterableSelect";