mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 21:51:56 +00:00
Merge branch 'main' into gitlab-sso
This commit is contained in:
@@ -1,78 +1,92 @@
|
||||
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Checkbox, PopoverContent } from "@app/components/v2";
|
||||
import { Button, Checkbox, PopoverContent } from "@app/components/v2";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
|
||||
import { WsTag } from "../../hooks/api/tags/types";
|
||||
import { ProjectPermissionCan } from "../permissions";
|
||||
|
||||
interface Props {
|
||||
wsTags: WsTag[] | undefined;
|
||||
secKey: string;
|
||||
selectedTagIds: Record<string, boolean>;
|
||||
handleSelectTag: (wsTag: WsTag) => void;
|
||||
handleTagOnMouseEnter: (wsTag: WsTag) => void;
|
||||
handleTagOnMouseLeave: () => void;
|
||||
checkIfTagIsVisible: (wsTag: WsTag) => boolean;
|
||||
handleOnCreateTagOpen: () => void
|
||||
wsTags: WsTag[] | undefined;
|
||||
secKey: string;
|
||||
selectedTagIds: Record<string, boolean>;
|
||||
handleSelectTag: (wsTag: WsTag) => void;
|
||||
handleTagOnMouseEnter: (wsTag: WsTag) => void;
|
||||
handleTagOnMouseLeave: () => void;
|
||||
checkIfTagIsVisible: (wsTag: WsTag) => boolean;
|
||||
handleOnCreateTagOpen: () => void;
|
||||
}
|
||||
|
||||
const AddTagPopoverContent = ({
|
||||
wsTags,
|
||||
secKey,
|
||||
selectedTagIds,
|
||||
handleSelectTag,
|
||||
handleTagOnMouseEnter,
|
||||
handleTagOnMouseLeave,
|
||||
checkIfTagIsVisible,
|
||||
handleOnCreateTagOpen
|
||||
wsTags,
|
||||
secKey,
|
||||
selectedTagIds,
|
||||
handleSelectTag,
|
||||
handleTagOnMouseEnter,
|
||||
handleTagOnMouseLeave,
|
||||
checkIfTagIsVisible,
|
||||
handleOnCreateTagOpen
|
||||
}: Props) => {
|
||||
return (
|
||||
<PopoverContent
|
||||
side="left"
|
||||
className="relative max-h-96 w-auto min-w-[200px] p-2 overflow-y-auto overflow-x-hidden border border-mineshaft-600 bg-mineshaft-800 text-bunker-200"
|
||||
hideCloseBtn
|
||||
>
|
||||
<div className=" text-center text-sm font-medium text-bunker-200">
|
||||
Add tags to {secKey || "this secret"}
|
||||
return (
|
||||
<PopoverContent
|
||||
side="left"
|
||||
className="relative max-h-96 w-auto min-w-[200px] p-2 overflow-y-auto overflow-x-hidden border border-mineshaft-600 bg-mineshaft-800 text-bunker-200"
|
||||
hideCloseBtn
|
||||
>
|
||||
<div className=" text-center text-sm font-medium text-bunker-200">
|
||||
Add tags to {secKey || "this secret"}
|
||||
</div>
|
||||
<div className="absolute left-0 w-full border-mineshaft-600 border-t mt-2" />
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
{wsTags?.map((wsTag: WsTag) => (
|
||||
<div
|
||||
key={`tag-${wsTag._id}`}
|
||||
className="mt-4 h-[32px] relative flex items-center justify-start hover:border-mineshaft-600 hover:border hover:bg-mineshaft-700 p-2 rounded-md hover:text-bunker-200 bg-none"
|
||||
onClick={() => handleSelectTag(wsTag)}
|
||||
onMouseEnter={() => handleTagOnMouseEnter(wsTag)}
|
||||
onMouseLeave={() => handleTagOnMouseLeave()}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onKeyDown={() => {}}
|
||||
>
|
||||
{(checkIfTagIsVisible(wsTag) || selectedTagIds?.[wsTag.slug]) && (
|
||||
<Checkbox
|
||||
id="autoCapitalization"
|
||||
isChecked={selectedTagIds?.[wsTag.slug]}
|
||||
className="absolute top-[50%] translate-y-[-50%] left-[10px] "
|
||||
checkIndicatorBg={`${
|
||||
!selectedTagIds?.[wsTag.slug] ? "text-transparent" : "text-mineshaft-800"
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
<div className="ml-7 flex items-center gap-3">
|
||||
<div
|
||||
className="w-[10px] h-[10px] rounded-full"
|
||||
style={{ background: wsTag?.tagColor ? wsTag.tagColor : "#bec2c8" }}
|
||||
>
|
||||
{" "}
|
||||
</div>
|
||||
<span>{wsTag.slug}</span>
|
||||
</div>
|
||||
<div className="absolute left-0 w-full border-mineshaft-600 border-t mt-2" />
|
||||
<div className="flex flex-col space-y-1.5">
|
||||
{wsTags?.map((wsTag: WsTag) => (
|
||||
<div key={`tag-${wsTag._id}`} className="mt-4 h-[32px] relative flex items-center justify-start hover:border-mineshaft-600 hover:border hover:bg-mineshaft-700 p-2 rounded-md hover:text-bunker-200 bg-none"
|
||||
onClick={() => handleSelectTag(wsTag)}
|
||||
onMouseEnter={() => handleTagOnMouseEnter(wsTag)}
|
||||
onMouseLeave={() => handleTagOnMouseLeave()}
|
||||
tabIndex={0} role="button"
|
||||
onKeyDown={() => { }}>
|
||||
{
|
||||
</div>
|
||||
))}
|
||||
<ProjectPermissionCan I={ProjectPermissionActions.Create} a={ProjectPermissionSub.Tags}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
onClick={() => handleOnCreateTagOpen()}
|
||||
isDisabled={!isAllowed}
|
||||
size="xs"
|
||||
className="mt-2"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} className="ml-1 mr-2" />}
|
||||
>
|
||||
Add new tag
|
||||
</Button>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
);
|
||||
};
|
||||
|
||||
(checkIfTagIsVisible(wsTag) || selectedTagIds?.[wsTag.slug]) && <Checkbox
|
||||
id="autoCapitalization"
|
||||
isChecked={selectedTagIds?.[wsTag.slug]}
|
||||
className="absolute top-[50%] translate-y-[-50%] left-[10px] "
|
||||
checkIndicatorBg={`${!selectedTagIds?.[wsTag.slug] ? "text-transparent" : "text-mineshaft-800"}`}
|
||||
/>
|
||||
}
|
||||
<div className="ml-7 flex items-center gap-3">
|
||||
<div className="w-[10px] h-[10px] rounded-full" style={{ background: wsTag?.tagColor ? wsTag.tagColor : "#bec2c8" }}> </div>
|
||||
<span >
|
||||
{wsTag.slug}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
className="h-[32px] relative flex items-center cursor-pointer justify-start border-mineshaft-600 border bg-mineshaft-700 p-2 rounded-md hover:text-bunker-200 bg-none"
|
||||
onClick={() => handleOnCreateTagOpen()}
|
||||
tabIndex={0} role="button"
|
||||
onKeyDown={() => { }}>
|
||||
<FontAwesomeIcon icon={faPlus} className="ml-1 mr-2" />
|
||||
<span> Add new tag</span>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
)
|
||||
}
|
||||
|
||||
export default AddTagPopoverContent
|
||||
export default AddTagPopoverContent;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { useState } from "react";
|
||||
import { faInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { Tooltip } from "@app/components/v2/Tooltip";
|
||||
|
||||
const GlobPatternExamples = () => {
|
||||
const [showTip, setShowTip] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
isOpen={showTip}
|
||||
onOpenChange={setShowTip}
|
||||
content={
|
||||
<div>
|
||||
<h4 className="mb-2">Here are some examples of glob patterns:</h4>
|
||||
<div className="ol-listStyleType">
|
||||
<li>
|
||||
<code className="text-primary">/</code> - Matches all files and directories in the
|
||||
current directory
|
||||
</li>
|
||||
<li>
|
||||
<code className="text-primary">**/*</code> - Matches all files and directories in the
|
||||
current directory and its subdirectories
|
||||
</li>
|
||||
<li>
|
||||
<code className="text-primary">{"/{dir1,dir2}"}</code> - Matches all files and
|
||||
directories in dir1 and dir2
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
position="right"
|
||||
className="text-xs"
|
||||
>
|
||||
<div
|
||||
className="flex h-3.5 w-3.5 items-center justify-center rounded-full border border-[1px] border-mineshaft-300"
|
||||
onMouseEnter={() => setShowTip(true)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faInfo} className="h-2 w-2" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobPatternExamples;
|
||||
@@ -0,0 +1,49 @@
|
||||
import { FunctionComponent, ReactNode } from "react";
|
||||
import { BoundCanProps, Can } from "@casl/react";
|
||||
|
||||
import { TOrgPermission, useOrgPermission } from "@app/context/OrgPermissionContext";
|
||||
|
||||
import { Tooltip } from "../v2";
|
||||
|
||||
type Props = {
|
||||
label?: ReactNode;
|
||||
// this prop is used when there exist already a tooltip as helper text for users
|
||||
// so when permission is allowed same tooltip will be reused to show helpertext
|
||||
renderTooltip?: boolean;
|
||||
allowedLabel?: string;
|
||||
} & BoundCanProps<TOrgPermission>;
|
||||
|
||||
export const OrgPermissionCan: FunctionComponent<Props> = ({
|
||||
label = "Access restricted",
|
||||
children,
|
||||
passThrough = true,
|
||||
renderTooltip,
|
||||
allowedLabel,
|
||||
...props
|
||||
}) => {
|
||||
const permission = useOrgPermission();
|
||||
|
||||
return (
|
||||
<Can {...props} passThrough={passThrough} ability={props?.ability || permission}>
|
||||
{(isAllowed, ability) => {
|
||||
// akhilmhdh: This is set as type due to error in casl react type.
|
||||
const finalChild =
|
||||
typeof children === "function"
|
||||
? children(isAllowed, ability as TOrgPermission)
|
||||
: children;
|
||||
|
||||
if (!isAllowed && passThrough) {
|
||||
return <Tooltip content={label}>{finalChild}</Tooltip>;
|
||||
}
|
||||
|
||||
if (isAllowed && renderTooltip) {
|
||||
return <Tooltip content={allowedLabel}>{finalChild}</Tooltip>;
|
||||
}
|
||||
|
||||
if (!isAllowed) return null;
|
||||
|
||||
return finalChild;
|
||||
}}
|
||||
</Can>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ReactNode } from "react";
|
||||
import { faLock } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
type Props = {
|
||||
containerClassName?: string;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export const PermissionDeniedBanner = ({ containerClassName, className, children }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"container h-full mx-auto flex justify-center items-center",
|
||||
containerClassName
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
"rounded-md bg-mineshaft-800 text-bunker-300 p-16 flex space-x-12 items-end",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faLock} size="6x" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-4xl font-medium mb-2">Access Restricted</div>
|
||||
{children || (
|
||||
<div className="text-sm">
|
||||
Your role has limited permissions, please <br/> contact your administrator to gain access
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import { FunctionComponent, ReactNode } from "react";
|
||||
import { BoundCanProps, Can } from "@casl/react";
|
||||
|
||||
import { TProjectPermission, useProjectPermission } from "@app/context/ProjectPermissionContext";
|
||||
|
||||
import { Tooltip } from "../v2";
|
||||
|
||||
type Props = {
|
||||
label?: ReactNode;
|
||||
// this prop is used when there exist already a tooltip as helper text for users
|
||||
// so when permission is allowed same tooltip will be reused to show helpertext
|
||||
renderTooltip?: boolean;
|
||||
allowedLabel?: string;
|
||||
// BUG(akhilmhdh): As a workaround for now i put any but this should be TProjectPermission
|
||||
// For some reason when i put TProjectPermission in a wrapper component it just wont work causes a weird ts error
|
||||
// tried a lot combinations
|
||||
// REF: https://github.com/stalniy/casl/blob/ac081a34f56366a7eaaed05d21689d27041ef005/packages/casl-react/src/factory.ts#L15
|
||||
} & BoundCanProps<any>;
|
||||
|
||||
export const ProjectPermissionCan: FunctionComponent<Props> = ({
|
||||
label = "Access restricted",
|
||||
children,
|
||||
passThrough = true,
|
||||
renderTooltip,
|
||||
allowedLabel,
|
||||
...props
|
||||
}) => {
|
||||
const permission = useProjectPermission();
|
||||
return (
|
||||
<Can {...props} passThrough={passThrough} ability={props?.ability || permission}>
|
||||
{(isAllowed, ability) => {
|
||||
// akhilmhdh: This is set as type due to error in casl react type.
|
||||
const finalChild =
|
||||
typeof children === "function"
|
||||
? children(isAllowed, ability as TProjectPermission)
|
||||
: children;
|
||||
|
||||
if (!isAllowed && passThrough) {
|
||||
return <Tooltip content={label}>{finalChild}</Tooltip>;
|
||||
}
|
||||
|
||||
if (isAllowed && renderTooltip) {
|
||||
return <Tooltip content={allowedLabel}>{finalChild}</Tooltip>;
|
||||
}
|
||||
|
||||
if (!isAllowed) return null;
|
||||
|
||||
return finalChild;
|
||||
}}
|
||||
</Can>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export { OrgPermissionCan } from "./OrgPermissionCan";
|
||||
export { PermissionDeniedBanner } from "./PermissionDeniedBanner";
|
||||
export { ProjectPermissionCan } from "./ProjectPermissionCan";
|
||||
@@ -1,5 +1,5 @@
|
||||
const LINE =
|
||||
/(?:^|^)\s*(?:export\s+)?([\w.-:]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
|
||||
/(?:^|^)\s*(?:export\s+)?([\w.:-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/gm;
|
||||
|
||||
/**
|
||||
* Return text that is the buffer parsed
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "./Accordion";
|
||||
|
||||
const meta: Meta<typeof Accordion> = {
|
||||
title: "Components/Accordion",
|
||||
component: Accordion,
|
||||
tags: ["v2"],
|
||||
argTypes: {}
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Accordion>;
|
||||
|
||||
export const Basic: Story = {
|
||||
render: (args) => (
|
||||
<div className="flex justify-center w-full">
|
||||
<Accordion {...args}>
|
||||
<AccordionItem value="section-1">
|
||||
<AccordionTrigger>Section 1</AccordionTrigger>
|
||||
<AccordionContent>Description of Section 1</AccordionContent>
|
||||
</AccordionItem>
|
||||
<AccordionItem value="section-2">
|
||||
<AccordionTrigger>Section 2</AccordionTrigger>
|
||||
<AccordionContent>Description of Section 2</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
import { forwardRef } from "react";
|
||||
import { faChevronDown } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export const AccordionItem = forwardRef<HTMLDivElement, AccordionPrimitive.AccordionItemProps>(
|
||||
({ children, className, ...props }, forwardedRef) => (
|
||||
<AccordionPrimitive.Item
|
||||
className={twMerge(
|
||||
"mt-px overflow-hidden first:mt-0 data-[state=open]:border-l data-[state=open]:border-primary transition-all border-transparent",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={forwardedRef}
|
||||
>
|
||||
{children}
|
||||
</AccordionPrimitive.Item>
|
||||
)
|
||||
);
|
||||
AccordionItem.displayName = "AccordionItem";
|
||||
|
||||
export const AccordionTrigger = forwardRef<
|
||||
HTMLButtonElement,
|
||||
AccordionPrimitive.AccordionTriggerProps
|
||||
>(({ children, className, ...props }, forwardedRef) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
className={twMerge(
|
||||
"py-2 px-4 group data-[state=open]:text-primary h-11 hover:text-primary flex flex-1 outline-none items-center justify-between ",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={forwardedRef}
|
||||
>
|
||||
{children}
|
||||
<FontAwesomeIcon
|
||||
icon={faChevronDown}
|
||||
className="ease-[cubic-bezier(0.87,_0,_0.13,_1)] transition-transform duration-300 group-data-[state=open]:rotate-180 text-sm"
|
||||
aria-hidden
|
||||
/>
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
));
|
||||
|
||||
AccordionTrigger.displayName = "AccordionTrigger";
|
||||
|
||||
export const AccordionContent = forwardRef<
|
||||
HTMLDivElement,
|
||||
AccordionPrimitive.AccordionContentProps
|
||||
>(({ children, className, ...props }, forwardedRef) => (
|
||||
<AccordionPrimitive.Content
|
||||
className={twMerge(
|
||||
"data-[state=open]:animate-slideDown data-[state=closed]:animate-slideUp overflow-hidden",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={forwardedRef}
|
||||
>
|
||||
<div className="text-sm py-2 px-4">{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
));
|
||||
|
||||
AccordionContent.displayName = "AccordionContent";
|
||||
|
||||
// ref: https://www.radix-ui.com/primitives/docs/components/accordion#root
|
||||
export const Accordion = ({
|
||||
children,
|
||||
...props
|
||||
}: AccordionPrimitive.AccordionSingleProps | AccordionPrimitive.AccordionMultipleProps) => (
|
||||
<AccordionPrimitive.Root {...props} className={twMerge("w-80 text-bunker-300", props.className)}>
|
||||
{children}
|
||||
</AccordionPrimitive.Root>
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
export { Accordion, AccordionContent, AccordionItem,AccordionTrigger } from "./Accordion";
|
||||
@@ -45,7 +45,7 @@ export const Checkbox = ({
|
||||
<FontAwesomeIcon icon={faCheck} size="sm" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
<label className="text-sm" htmlFor={id}>
|
||||
<label className="text-sm whitespace-nowrap" htmlFor={id}>
|
||||
{children}
|
||||
{isRequired && <span className="pl-1 text-red">*</span>}
|
||||
</label>
|
||||
|
||||
@@ -8,12 +8,17 @@ export type FormLabelProps = {
|
||||
id?: string;
|
||||
isRequired?: boolean;
|
||||
label?: ReactNode;
|
||||
icon?: ReactNode;
|
||||
};
|
||||
|
||||
export const FormLabel = ({ id, label, isRequired }: FormLabelProps) => (
|
||||
<Label.Root className="mb-0.5 ml-1 block text-sm font-normal text-mineshaft-400" htmlFor={id}>
|
||||
export const FormLabel = ({ id, label, isRequired, icon }: FormLabelProps) => (
|
||||
<Label.Root
|
||||
className="mb-0.5 ml-1 block flex items-center text-sm font-normal text-mineshaft-400"
|
||||
htmlFor={id}
|
||||
>
|
||||
{label}
|
||||
{isRequired && <span className="ml-1 text-red">*</span>}
|
||||
{icon && <span className="ml-2 text-mineshaft-300 hover:text-mineshaft-200 cursor-default">{icon}</span>}
|
||||
</Label.Root>
|
||||
);
|
||||
|
||||
@@ -47,6 +52,7 @@ export type FormControlProps = {
|
||||
errorText?: ReactNode;
|
||||
children: JSX.Element;
|
||||
className?: string;
|
||||
icon?: ReactNode;
|
||||
};
|
||||
|
||||
export const FormControl = ({
|
||||
@@ -57,12 +63,13 @@ export const FormControl = ({
|
||||
errorText,
|
||||
id,
|
||||
isError,
|
||||
icon,
|
||||
className
|
||||
}: FormControlProps): JSX.Element => {
|
||||
return (
|
||||
<div className={twMerge("mb-4", className)}>
|
||||
{typeof label === "string" ? (
|
||||
<FormLabel label={label} isRequired={isRequired} id={id} />
|
||||
<FormLabel label={label} isRequired={isRequired} id={id} icon={icon} />
|
||||
) : (
|
||||
label
|
||||
)}
|
||||
|
||||
@@ -57,7 +57,8 @@ const iconButtonVariants = cva(
|
||||
{
|
||||
colorSchema: "primary",
|
||||
variant: "outline_bg",
|
||||
className: "bg-mineshaft-700 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60 text-bunker-200 hover:text-bunker-100 duration-100"
|
||||
className:
|
||||
"bg-mineshaft-700 border border-mineshaft-600 hover:bg-primary/[0.15] hover:border-primary/60 text-bunker-200 hover:text-bunker-100 duration-100"
|
||||
},
|
||||
{
|
||||
colorSchema: "danger",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable react/no-danger */
|
||||
import { forwardRef, HTMLAttributes } from "react";
|
||||
import ContentEditable from "react-contenteditable";
|
||||
import sanitizeHtml, { DisallowedTagsModes } from "sanitize-html";
|
||||
|
||||
import { useToggle } from "@app/hooks";
|
||||
@@ -35,58 +34,52 @@ const syntaxHighlight = (content?: string | null, isVisible?: boolean) => {
|
||||
`<span class="ph-no-capture text-yellow">${<span class="ph-no-capture text-yello-200/80">${b}</span>}</span>`
|
||||
);
|
||||
|
||||
return newContent;
|
||||
// akhilmhdh: Dont remove this br. I am still clueless how this works but weirdly enough
|
||||
// when break is added a line break works properly
|
||||
return `${newContent}<br/>`;
|
||||
};
|
||||
|
||||
type Props = Omit<HTMLAttributes<HTMLDivElement>, "onChange" | "onBlur"> & {
|
||||
type Props = HTMLAttributes<HTMLTextAreaElement> & {
|
||||
value?: string | null;
|
||||
isVisible?: boolean;
|
||||
isDisabled?: boolean;
|
||||
onChange?: (val: string) => void;
|
||||
onBlur?: () => void;
|
||||
};
|
||||
|
||||
export const SecretInput = forwardRef<HTMLDivElement, Props>(
|
||||
({ value, isVisible, onChange, onBlur, isDisabled, ...props }, ref) => {
|
||||
const commonClassName = "font-mono text-sm caret-white border-none outline-none w-full break-all";
|
||||
|
||||
export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
({ value, isVisible, onBlur, isDisabled, onFocus, ...props }, ref) => {
|
||||
const [isSecretFocused, setIsSecretFocused] = useToggle();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="thin-scrollbar relative overflow-y-auto overflow-x-hidden"
|
||||
style={{ maxHeight: `${21 * 7}px` }}
|
||||
>
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: syntaxHighlight(value, isVisible || isSecretFocused)
|
||||
}}
|
||||
className={`absolute top-0 left-0 z-0 h-full w-full inline-block text-ellipsis whitespace-pre-wrap break-all ${
|
||||
!value && value !== "" && "italic text-red-600/70"
|
||||
}`}
|
||||
ref={ref}
|
||||
/>
|
||||
<ContentEditable
|
||||
className="relative z-10 h-full w-full text-ellipsis inline-block whitespace-pre-wrap break-all text-transparent caret-white outline-none"
|
||||
role="textbox"
|
||||
onChange={(evt) => {
|
||||
if (onChange) onChange(evt.currentTarget.innerText.trim());
|
||||
}}
|
||||
onFocus={() => setIsSecretFocused.on()}
|
||||
disabled={isDisabled}
|
||||
spellCheck={false}
|
||||
onBlur={() => {
|
||||
if (onBlur) onBlur();
|
||||
setIsSecretFocused.off();
|
||||
}}
|
||||
html={
|
||||
isVisible || isSecretFocused
|
||||
? sanitizeHtml(
|
||||
value?.replaceAll("<", "<").replaceAll(">", ">") || "",
|
||||
sanitizeConf
|
||||
)
|
||||
: syntaxHighlight(value, false)
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
<div className="overflow-auto w-full" style={{ maxHeight: `${21 * 7}px` }}>
|
||||
<div className="relative overflow-hidden">
|
||||
<pre aria-hidden className="m-0 ">
|
||||
<code className={`inline-block w-full ${commonClassName}`}>
|
||||
<span
|
||||
style={{ whiteSpace: "break-spaces" }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: syntaxHighlight(value, isVisible || isSecretFocused) ?? ""
|
||||
}}
|
||||
/>
|
||||
</code>
|
||||
</pre>
|
||||
<textarea
|
||||
style={{ whiteSpace: "break-spaces" }}
|
||||
aria-label="secret value"
|
||||
ref={ref}
|
||||
className={`absolute inset-0 block h-full resize-none overflow-hidden bg-transparent text-transparent no-scrollbar focus:border-0 ${commonClassName}`}
|
||||
onFocus={() => setIsSecretFocused.on()}
|
||||
disabled={isDisabled}
|
||||
spellCheck={false}
|
||||
onBlur={(evt) => {
|
||||
onBlur?.(evt);
|
||||
setIsSecretFocused.off();
|
||||
}}
|
||||
value={value || ""}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export const Tr = ({
|
||||
}: TrProps): JSX.Element => (
|
||||
<tr
|
||||
className={twMerge(
|
||||
"cursor-default border border-solid border-mineshaft-600",
|
||||
"cursor-default border-b border-solid border-mineshaft-600 last:border-b-0",
|
||||
isHoverable && "hover:bg-mineshaft-600",
|
||||
isSelectable && "cursor-pointer",
|
||||
className
|
||||
@@ -124,7 +124,7 @@ export type TdProps = {
|
||||
} & TdHTMLAttributes<HTMLTableCellElement>;
|
||||
|
||||
export const Td = ({ children, className, ...props }: TdProps): JSX.Element => (
|
||||
<td className={twMerge("px-5 py-2 text-left", className)} {...props}>
|
||||
<td className={twMerge("px-5 py-3 text-left", className)} {...props}>
|
||||
{children}
|
||||
</td>
|
||||
);
|
||||
|
||||
@@ -1,142 +1,46 @@
|
||||
import { useState } from "react";
|
||||
import { faArrowUpRightFromSquare, faCheck, faClipboard } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import * as Tabs from "@radix-ui/react-tabs";
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export type TabsProps = TabsPrimitive.TabsProps;
|
||||
|
||||
export type TabsProps = Tabs.TabsProps & {};
|
||||
export const Tabs = ({ className, children, ...props }: TabsProps) => (
|
||||
<TabsPrimitive.Root className={twMerge("flex flex-col", className)} {...props}>
|
||||
{children}
|
||||
</TabsPrimitive.Root>
|
||||
);
|
||||
|
||||
function copyToClipboard(id: string, setState: (value: boolean) => void) {
|
||||
// Get the text field
|
||||
const copyText = document.getElementById(id) as HTMLInputElement;
|
||||
export type TabListProps = TabsPrimitive.TabsListProps;
|
||||
|
||||
// Select the text field
|
||||
copyText.select();
|
||||
copyText.setSelectionRange(0, 99999); // For mobile devices
|
||||
|
||||
// Copy the text inside the text field
|
||||
navigator.clipboard.writeText(copyText.value);
|
||||
|
||||
setState(true);
|
||||
setTimeout(() => setState(false), 2000);
|
||||
// Alert the copied text
|
||||
// alert("Copied the text: " + copyText.value);
|
||||
}
|
||||
|
||||
const CodeItem = ({isCopied, setIsCopied, textExplanation, code, id}: {isCopied: boolean, setIsCopied: (value: boolean) => void, textExplanation: string, code: string, id: string}) => {
|
||||
return <>
|
||||
<p className="mb-2 mt-4 text-bunker-300 text-sm leading-normal">
|
||||
{textExplanation}
|
||||
</p>
|
||||
<div className='font-mono text-sm px-3 py-2 bg-bunker rounded-md border border-mineshaft-600 flex flex-row items-center justify-between'>
|
||||
<input disabled value={code} id={id} className='w-full bg-transparent text-bunker-200'/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copyToClipboard(id, setIsCopied)}
|
||||
className='h-full pl-3.5 pr-2 text-bunker-300 hover:text-primary-200 duration-200'
|
||||
>
|
||||
{isCopied ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className='pr-0.5'
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faClipboard} />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
export const TabsObject = () => {
|
||||
const [downloadCodeCopied, setDownloadCodeCopied] = useState(false);
|
||||
const [downloadCode2Copied, setDownloadCode2Copied] = useState(false);
|
||||
const [loginCodeCopied, setLoginCodeCopied] = useState(false);
|
||||
const [initCodeCopied, setInitCodeCopied] = useState(false);
|
||||
const [runCodeCopied, setRunCodeCopied] = useState(false);
|
||||
|
||||
return <Tabs.Root
|
||||
className="flex flex-col w-full cursor-default border border-mineshaft-600 rounded-md"
|
||||
defaultValue="tab1"
|
||||
export const TabList = ({ className, children, ...props }: TabListProps) => (
|
||||
<TabsPrimitive.List
|
||||
className={twMerge("flex-shrink-0 flex border-b-2 border-mineshaft-800", className)}
|
||||
{...props}
|
||||
>
|
||||
<Tabs.List className="shrink-0 flex border-b border-mineshaft-600" aria-label="Manage your account">
|
||||
<Tabs.Trigger
|
||||
className="bg-bunker-700 px-5 h-10 flex-1 flex items-center justify-center text-sm leading-none text-bunker-300 select-none first:rounded-tl-md last:rounded-tr-md data-[state=active]:text-primary data-[state=active]:font-medium data-[state=active]:focus:relative data-[state=active]:border-b data-[state=active]:border-primary outline-none cursor-default"
|
||||
value="tab1"
|
||||
>
|
||||
MacOS
|
||||
</Tabs.Trigger>
|
||||
<Tabs.Trigger
|
||||
className="bg-bunker-700 px-5 h-10 flex-1 flex items-center justify-center text-sm leading-none text-bunker-300 select-none first:rounded-tl-md last:rounded-tr-md data-[state=active]:text-primary data-[state=active]:font-medium data-[state=active]:focus:relative data-[state=active]:border-b data-[state=active]:border-primary outline-none cursor-default"
|
||||
value="tab2"
|
||||
>
|
||||
Windows
|
||||
</Tabs.Trigger>
|
||||
{/* <Tabs.Trigger
|
||||
className="bg-bunker-700 px-5 h-10 flex-1 flex items-center justify-center text-sm leading-none text-bunker-300 select-none first:rounded-tl-md last:rounded-tr-md data-[state=active]:text-primary data-[state=active]:font-medium data-[state=active]:focus:relative data-[state=active]:border-b data-[state=active]:border-primary outline-none cursor-default"
|
||||
value="tab3"
|
||||
>
|
||||
Arch Linux
|
||||
</Tabs.Trigger> */}
|
||||
<a
|
||||
target='_blank'
|
||||
rel="noopener noreferrer"
|
||||
className="bg-bunker-700 hover:text-bunker-100 duration-200 px-5 h-10 flex-1 flex items-center justify-center text-sm leading-none text-bunker-300 select-none first:rounded-tl-md last:rounded-tr-md data-[state=active]:text-primary data-[state=active]:font-medium data-[state=active]:focus:relative data-[state=active]:border-b data-[state=active]:border-primary outline-none cursor-default"
|
||||
href='https://infisical.com/docs/cli/overview'
|
||||
>
|
||||
Other Platforms <FontAwesomeIcon icon={faArrowUpRightFromSquare} className='ml-2'/>
|
||||
</a>
|
||||
</Tabs.List>
|
||||
<Tabs.Content
|
||||
className="grow p-5 pt-0 bg-bunker-700 rounded-b-md outline-none cursor-default"
|
||||
value="tab1"
|
||||
>
|
||||
<CodeItem isCopied={downloadCodeCopied} setIsCopied={setDownloadCodeCopied} textExplanation="1. Download CLI" code="brew install infisical/get-cli/infisical" id="downloadCode" />
|
||||
<CodeItem isCopied={loginCodeCopied} setIsCopied={setLoginCodeCopied} textExplanation="2. Login" code="infisical login" id="loginCode" />
|
||||
<CodeItem isCopied={initCodeCopied} setIsCopied={setInitCodeCopied} textExplanation="3. Choose Project" code="infisical init" id="initCode" />
|
||||
<CodeItem isCopied={runCodeCopied} setIsCopied={setRunCodeCopied} textExplanation="4. Done! Now, you can prepend your usual start script with:" code="infisical run -- [YOUR USUAL CODE START SCRIPT GOES HERE]" id="runCode" />
|
||||
<p className='text-bunker-300 text-sm mt-2'>You can find example of start commands for different frameworks <a
|
||||
className='text-primary underline underline-offset-2'
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href='https://infisical.com/docs/integrations/overview'
|
||||
>
|
||||
here
|
||||
</a>. </p>
|
||||
</Tabs.Content>
|
||||
<Tabs.Content
|
||||
className="grow p-5 pt-0 bg-bunker-700 rounded-b-md outline-none"
|
||||
value="tab2"
|
||||
>
|
||||
<CodeItem isCopied={downloadCodeCopied} setIsCopied={setDownloadCodeCopied} textExplanation="1. Download CLI" code="scoop bucket add org https://github.com/Infisical/scoop-infisical.git" id="downloadCodeW" />
|
||||
<div className='font-mono text-sm px-3 py-2 mt-2 bg-bunker rounded-md border border-mineshaft-600 flex flex-row items-center justify-between'>
|
||||
<input disabled value="scoop install infisical" id="downloadCodeW2" className='w-full bg-transparent text-bunker-200'/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => copyToClipboard("downloadCodeW2", setDownloadCode2Copied)}
|
||||
className='h-full pl-3.5 pr-2 text-bunker-300 hover:text-primary-200 duration-200'
|
||||
>
|
||||
{downloadCode2Copied ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className='pr-0.5'
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faClipboard} />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
<CodeItem isCopied={loginCodeCopied} setIsCopied={setLoginCodeCopied} textExplanation="2. Login" code="infisical login" id="loginCodeW" />
|
||||
<CodeItem isCopied={initCodeCopied} setIsCopied={setInitCodeCopied} textExplanation="3. Choose Project" code="infisical init" id="initCodeW" />
|
||||
<CodeItem isCopied={runCodeCopied} setIsCopied={setRunCodeCopied} textExplanation="4. Done! Now, you can prepend your usual start script with:" code="infisical run -- [YOUR USUAL CODE START SCRIPT GOES HERE]" id="runCodeW" />
|
||||
<p className='text-bunker-300 text-sm mt-2'>You can find example of start commands for different frameworks <a
|
||||
className='text-primary underline underline-offset-2'
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href='https://infisical.com/docs/integrations/overview'
|
||||
>
|
||||
here
|
||||
</a>. </p>
|
||||
</Tabs.Content>
|
||||
</Tabs.Root>
|
||||
};
|
||||
{children}
|
||||
</TabsPrimitive.List>
|
||||
);
|
||||
|
||||
export type TabProps = TabsPrimitive.TabsTriggerProps;
|
||||
|
||||
export const Tab = ({ className, children, ...props }: TabProps) => (
|
||||
<TabsPrimitive.Trigger
|
||||
className={twMerge(
|
||||
"px-3 h-10 font-medium text-sm flex items-center justify-center select-none first:rounded-tl-md last:rounded-tr-md hover:text-mineshaft-200 text-mineshaft-400 transition-all data-[state=active]:text-white data-[state=active]:border-b data-[state=active]:border-primary",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</TabsPrimitive.Trigger>
|
||||
);
|
||||
|
||||
export type TabPanelProps = TabsPrimitive.TabsContentProps;
|
||||
|
||||
export const TabPanel = ({ className, children, ...props }: TabPanelProps) => (
|
||||
<TabsPrimitive.Content
|
||||
className={twMerge("outline-none flex-grow py-5 rounded-bl-md rounded-br-md", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</TabsPrimitive.Content>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
export type {
|
||||
TabsProps
|
||||
} from "./Tabs";
|
||||
export { TabsObject } from "./Tabs";
|
||||
export type { TabListProps,TabPanelProps, TabProps, TabsProps } from "./Tabs";
|
||||
export { Tab, TabList, TabPanel, Tabs } from "./Tabs";
|
||||
|
||||
@@ -10,6 +10,7 @@ export type TooltipProps = Omit<TooltipPrimitive.TooltipContentProps, "open" | "
|
||||
asChild?: boolean;
|
||||
onOpenChange?: (isOpen: boolean) => void;
|
||||
defaultOpen?: boolean;
|
||||
position?: "top" | "bottom" | "left" | "right";
|
||||
};
|
||||
|
||||
export const Tooltip = ({
|
||||
@@ -20,6 +21,7 @@ export const Tooltip = ({
|
||||
defaultOpen,
|
||||
className,
|
||||
asChild = true,
|
||||
position = "top",
|
||||
...props
|
||||
}: TooltipProps) => (
|
||||
<TooltipPrimitive.Root
|
||||
@@ -30,7 +32,7 @@ export const Tooltip = ({
|
||||
>
|
||||
<TooltipPrimitive.Trigger asChild={asChild}>{children}</TooltipPrimitive.Trigger>
|
||||
<TooltipPrimitive.Content
|
||||
side="top"
|
||||
side={position}
|
||||
align="center"
|
||||
sideOffset={5}
|
||||
{...props}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./Accordion";
|
||||
export * from "./Button";
|
||||
export * from "./Card";
|
||||
export * from "./Checkbox";
|
||||
@@ -21,6 +22,7 @@ export * from "./Skeleton";
|
||||
export * from "./Spinner";
|
||||
export * from "./Switch";
|
||||
export * from "./Table";
|
||||
export * from "./Tabs";
|
||||
export * from "./Tag";
|
||||
export * from "./TextArea";
|
||||
export * from "./Tooltip";
|
||||
|
||||
Reference in New Issue
Block a user