mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
style: give project machine identity page facelift
This commit is contained in:
@@ -32,7 +32,7 @@ export const PageHeader = ({ title, description, children, className, scope }: P
|
||||
<div className="mr-4 flex w-full items-center">
|
||||
<h1
|
||||
className={twMerge(
|
||||
"text-3xl font-medium text-white capitalize underline decoration-2 underline-offset-4",
|
||||
"text-2xl font-medium text-white capitalize underline decoration-2 underline-offset-4",
|
||||
scope === "org" && "decoration-org/90",
|
||||
scope === "instance" && "decoration-neutral/90",
|
||||
scope === "namespace" && "decoration-sub-org/90",
|
||||
@@ -51,6 +51,6 @@ export const PageHeader = ({ title, description, children, className, scope }: P
|
||||
</div>
|
||||
<div className="flex items-center gap-2">{children}</div>
|
||||
</div>
|
||||
<div className="mt-2 text-gray-400">{description}</div>
|
||||
<div className="mt-1.5 text-mineshaft-300">{description}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
62
frontend/src/components/v3/generic/Alert/Alert.tsx
Normal file
62
frontend/src/components/v3/generic/Alert/Alert.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "cva";
|
||||
|
||||
import { cn } from "../../utils";
|
||||
|
||||
const alertVariants = cva(
|
||||
"relative w-full rounded-sm border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-container text-card-foreground",
|
||||
info: "bg-info/10 text-info border-info/20",
|
||||
org: "bg-org/10 text-org border-org/20"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function UnstableAlert({
|
||||
className,
|
||||
variant,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
||||
return (
|
||||
<div
|
||||
data-slot="alert"
|
||||
role="alert"
|
||||
className={cn(alertVariants({ variant }), className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableAlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="alert-title"
|
||||
className={cn("col-start-2 line-clamp-1 min-h-4 tracking-tight text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableAlertDescription({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="alert-description"
|
||||
className={cn(
|
||||
"col-start-2 grid justify-items-start gap-1 text-sm text-foreground/75 [&_p]:leading-relaxed",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { UnstableAlert, UnstableAlertDescription, UnstableAlertTitle };
|
||||
1
frontend/src/components/v3/generic/Alert/index.ts
Normal file
1
frontend/src/components/v3/generic/Alert/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./Alert";
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from "lucide-react";
|
||||
|
||||
import { OrgIcon, ProjectIcon, SubOrgIcon } from "../../platform";
|
||||
import { UnstableButtonGroup } from "../ButtonGroup";
|
||||
import { Badge } from "./Badge";
|
||||
|
||||
/**
|
||||
@@ -33,13 +34,34 @@ const meta = {
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: "select",
|
||||
options: ["neutral", "success", "info", "warning", "danger", "project", "org", "sub-org"]
|
||||
options: [
|
||||
"default",
|
||||
"outline",
|
||||
"neutral",
|
||||
"success",
|
||||
"info",
|
||||
"warning",
|
||||
"danger",
|
||||
"project",
|
||||
"org",
|
||||
"sub-org"
|
||||
]
|
||||
},
|
||||
isTruncatable: {
|
||||
table: {
|
||||
disable: true
|
||||
}
|
||||
},
|
||||
isFullWidth: {
|
||||
table: {
|
||||
disable: true
|
||||
}
|
||||
},
|
||||
isSquare: {
|
||||
table: {
|
||||
disable: true
|
||||
}
|
||||
},
|
||||
asChild: {
|
||||
table: {
|
||||
disable: true
|
||||
@@ -57,6 +79,38 @@ const meta = {
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
name: "Variant: Default",
|
||||
args: {
|
||||
variant: "default",
|
||||
children: <>Default</>
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
"Use this variant when other badge variants are not applicable or as the key when displaying key-value pairs with ButtonGroup."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const Outline: Story = {
|
||||
name: "Variant: Outline",
|
||||
args: {
|
||||
variant: "outline",
|
||||
children: <>Outline</>
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
"Use this variant when other badge variants are not applicable or as the value when displaying key-value pairs with ButtonGroup."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const Neutral: Story = {
|
||||
name: "Variant: Neutral",
|
||||
args: {
|
||||
@@ -71,8 +125,7 @@ export const Neutral: Story = {
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
"Use this variant when indicating neutral or disabled states or when linking to external documents."
|
||||
story: "Use this variant when indicating neutral or disabled states."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,7 +186,8 @@ export const Info: Story = {
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Use this variant when indicating informational states."
|
||||
story:
|
||||
"Use this variant when indicating informational states or when linking to external documentation."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -374,3 +428,22 @@ export const IsFullWidth: Story = {
|
||||
</div>
|
||||
)
|
||||
};
|
||||
|
||||
export const KeyValuePair: Story = {
|
||||
name: "Example: Key-Value Pair",
|
||||
args: {},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
"Use a default and outline badge in conjunction with the `<ButtonGroup />` component to display key-value pairs."
|
||||
}
|
||||
}
|
||||
},
|
||||
decorators: () => (
|
||||
<UnstableButtonGroup>
|
||||
<Badge>Key</Badge>
|
||||
<Badge variant="outline">Value</Badge>
|
||||
</UnstableButtonGroup>
|
||||
)
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { cn } from "@app/components/v3/utils";
|
||||
|
||||
const badgeVariants = cva(
|
||||
[
|
||||
"select-none items-center align-middle rounded-sm h-4.5 px-1.5 text-xs",
|
||||
"select-none border items-center align-middle rounded-sm h-4.5 px-1.5 text-xs",
|
||||
"gap-x-1 [a&,button&]:cursor-pointer inline-flex font-normal",
|
||||
"[&>svg]:pointer-events-none [&>svg]:shrink-0 [&>svg]:stroke-[2.25] [&_svg:not([class*='size-'])]:size-3",
|
||||
"transition duration-200 ease-in-out"
|
||||
@@ -24,19 +24,22 @@ const badgeVariants = cva(
|
||||
true: "w-4.5 justify-center px-0.5"
|
||||
},
|
||||
variant: {
|
||||
ghost: "text-mineshaft-200 gap-x-2",
|
||||
neutral: "bg-neutral/25 text-neutral [a&,button&]:hover:bg-neutral/35",
|
||||
success: "bg-success/25 text-success [a&,button&]:hover:bg-success/35",
|
||||
info: "bg-info/25 text-info [a&,button&]:hover:bg-info/35",
|
||||
warning: "bg-warning/25 text-warning [a&,button&]:hover:bg-warning/35",
|
||||
danger: "bg-danger/25 text-danger [a&,button&]:hover:bg-danger/35",
|
||||
project: "bg-project/25 text-project [a&,button&]:hover:bg-project/35",
|
||||
org: "bg-org/25 text-org [a&,button&]:hover:bg-org/35",
|
||||
"sub-org": "bg-sub-org/25 text-sub-org [a&,button&]:hover:bg-sub-org/35"
|
||||
ghost: "text-foreground border-none",
|
||||
default: "bg-foreground text-background [a&,button&]:hover:bg-primary/35",
|
||||
outline: "text-foreground border-foreground border",
|
||||
neutral: "bg-neutral/15 border-neutral/10 text-neutral [a&,button&]:hover:bg-neutral/35",
|
||||
success: "bg-success/15 border-success/10 text-success [a&,button&]:hover:bg-success/35",
|
||||
info: "bg-info/15 border-info/10 border text-info [a&,button&]:hover:bg-info/35",
|
||||
warning: "bg-warning/15 border-warning/10 text-warning [a&,button&]:hover:bg-warning/35",
|
||||
danger: "bg-danger/15 border-danger/10 text-danger border [a&,button&]:hover:bg-danger/35",
|
||||
project:
|
||||
"bg-project/15 text-project border-project/10 border [a&,button&]:hover:bg-project/35",
|
||||
org: "bg-org/15 border border-org/10 text-org [a&,button&]:hover:bg-org/35",
|
||||
"sub-org": "bg-sub-org/15 border-sub-org/10 text-sub-org [a&,button&]:hover:bg-sub-org/35"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "neutral"
|
||||
variant: "default"
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -44,7 +47,6 @@ const badgeVariants = cva(
|
||||
type TBadgeProps = VariantProps<typeof badgeVariants> &
|
||||
React.ComponentProps<"span"> & {
|
||||
asChild?: boolean;
|
||||
variant: NonNullable<VariantProps<typeof badgeVariants>["variant"]>; // TODO: REMOVE
|
||||
};
|
||||
|
||||
const Badge = forwardRef<HTMLSpanElement, TBadgeProps>(
|
||||
|
||||
357
frontend/src/components/v3/generic/Button/Button.stories.tsx
Normal file
357
frontend/src/components/v3/generic/Button/Button.stories.tsx
Normal file
@@ -0,0 +1,357 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import {
|
||||
AsteriskIcon,
|
||||
BanIcon,
|
||||
CheckIcon,
|
||||
CircleXIcon,
|
||||
ExternalLinkIcon,
|
||||
InfoIcon,
|
||||
RadarIcon,
|
||||
TriangleAlertIcon,
|
||||
UserIcon
|
||||
} from "lucide-react";
|
||||
|
||||
import { OrgIcon, ProjectIcon, SubOrgIcon } from "../../platform";
|
||||
import { UnstableButton } from "./Button";
|
||||
|
||||
/**
|
||||
* Buttons act as an indicator that can optionally be made interactable.
|
||||
* You can place text and icons inside a Button.
|
||||
* Buttons are often used for the indication of a status, state or scope.
|
||||
*/
|
||||
const meta = {
|
||||
title: "Generic/Button",
|
||||
component: UnstableButton,
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: "select",
|
||||
options: [
|
||||
"default",
|
||||
"outline",
|
||||
"neutral",
|
||||
"success",
|
||||
"info",
|
||||
"warning",
|
||||
"danger",
|
||||
"project",
|
||||
"org",
|
||||
"sub-org"
|
||||
]
|
||||
},
|
||||
size: {
|
||||
control: "select",
|
||||
options: ["xs", "sm", "md", "lg"]
|
||||
},
|
||||
isPending: {
|
||||
control: "boolean"
|
||||
},
|
||||
isFullWidth: {
|
||||
control: "boolean"
|
||||
},
|
||||
isDisabled: {
|
||||
control: "boolean"
|
||||
},
|
||||
as: {
|
||||
table: {
|
||||
disable: true
|
||||
}
|
||||
},
|
||||
children: {
|
||||
table: {
|
||||
disable: true
|
||||
}
|
||||
}
|
||||
},
|
||||
args: { children: "Button", isPending: false, isDisabled: false, isFullWidth: false, size: "md" }
|
||||
} satisfies Meta<typeof UnstableButton>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Default: Story = {
|
||||
name: "Variant: Default",
|
||||
args: {
|
||||
variant: "default",
|
||||
children: <>Default</>
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
"Use this variant when other Button variants are not applicable or as the key when displaying key-value pairs with ButtonGroup."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const Outline: Story = {
|
||||
name: "Variant: Outline",
|
||||
args: {
|
||||
variant: "outline",
|
||||
children: <>Outline</>
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
"Use this variant when other Button variants are not applicable or as the value when displaying key-value pairs with ButtonGroup."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const Neutral: Story = {
|
||||
name: "Variant: Neutral",
|
||||
args: {
|
||||
variant: "neutral",
|
||||
children: (
|
||||
<>
|
||||
<BanIcon />
|
||||
Disabled
|
||||
</>
|
||||
)
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Use this variant when indicating neutral or disabled states."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const Ghost: Story = {
|
||||
name: "Variant: Ghost",
|
||||
args: {
|
||||
variant: "ghost",
|
||||
children: (
|
||||
<>
|
||||
<UserIcon />
|
||||
User
|
||||
</>
|
||||
)
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
"Use this variant when indicating a configuration or property value. Avoid using this variant as an interactive element as it is not intuitive to interact with."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const Success: Story = {
|
||||
name: "Variant: Success",
|
||||
args: {
|
||||
variant: "success",
|
||||
children: (
|
||||
<>
|
||||
<CheckIcon />
|
||||
Success
|
||||
</>
|
||||
)
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Use this variant when indicating successful or healthy states."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const Info: Story = {
|
||||
name: "Variant: Info",
|
||||
args: {
|
||||
variant: "info",
|
||||
children: (
|
||||
<>
|
||||
<InfoIcon />
|
||||
Info
|
||||
</>
|
||||
)
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
"Use this variant when indicating informational states or when linking to external documentation."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const Warning: Story = {
|
||||
name: "Variant: Warning",
|
||||
args: {
|
||||
variant: "warning",
|
||||
children: (
|
||||
<>
|
||||
<TriangleAlertIcon />
|
||||
Warning
|
||||
</>
|
||||
)
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Use this variant when indicating activity or attention warranting states."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const Danger: Story = {
|
||||
name: "Variant: Danger",
|
||||
args: {
|
||||
variant: "danger",
|
||||
children: (
|
||||
<>
|
||||
<CircleXIcon />
|
||||
Danger
|
||||
</>
|
||||
)
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Use this variant when indicating destructive or error states."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const Organization: Story = {
|
||||
name: "Variant: Organization",
|
||||
args: {
|
||||
variant: "org",
|
||||
children: (
|
||||
<>
|
||||
<OrgIcon />
|
||||
Organization
|
||||
</>
|
||||
)
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Use this variant when indicating organization scope or links."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const SubOrganization: Story = {
|
||||
name: "Variant: Sub-Organization",
|
||||
args: {
|
||||
variant: "sub-org",
|
||||
children: (
|
||||
<>
|
||||
<SubOrgIcon />
|
||||
Sub-Organization
|
||||
</>
|
||||
)
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Use this variant when indicating sub-organization scope or links."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const Project: Story = {
|
||||
name: "Variant: Project",
|
||||
args: {
|
||||
variant: "project",
|
||||
children: (
|
||||
<>
|
||||
<ProjectIcon />
|
||||
Project
|
||||
</>
|
||||
)
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Use this variant when indicating project scope or links."
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const AsExternalLink: Story = {
|
||||
name: "Example: As External Link",
|
||||
args: {
|
||||
variant: "info",
|
||||
as: "a",
|
||||
href: "https://www.infisical.com",
|
||||
children: (
|
||||
<>
|
||||
Link <ExternalLinkIcon />
|
||||
</>
|
||||
)
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Use the `as="a"` prop to use a Button as an external `a` tag component.'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const AsRouterLink: Story = {
|
||||
name: "Example: As Router Link",
|
||||
args: {
|
||||
variant: "project",
|
||||
as: "link",
|
||||
children: (
|
||||
<>
|
||||
<RadarIcon />
|
||||
Secret Scanning
|
||||
</>
|
||||
)
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Use the `as="link"` prop to use a Button as an internal `Link` component.'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const IsFullWidth: Story = {
|
||||
name: "Example: isFullWidth",
|
||||
args: {
|
||||
variant: "neutral",
|
||||
isFullWidth: true,
|
||||
|
||||
children: (
|
||||
<>
|
||||
<AsteriskIcon />
|
||||
Secret Value
|
||||
</>
|
||||
)
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
"Use the `isFullWidth` prop to expand the Buttons width to fill it's parent container."
|
||||
}
|
||||
}
|
||||
},
|
||||
decorators: (Story) => (
|
||||
<div className="w-32">
|
||||
<Story />
|
||||
</div>
|
||||
)
|
||||
};
|
||||
139
frontend/src/components/v3/generic/Button/Button.tsx
Normal file
139
frontend/src/components/v3/generic/Button/Button.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import * as React from "react";
|
||||
import { forwardRef } from "react";
|
||||
import { Link, LinkProps } from "@tanstack/react-router";
|
||||
import { cva, type VariantProps } from "cva";
|
||||
|
||||
import { Lottie } from "@app/components/v2";
|
||||
import { cn } from "@app/components/v3/utils";
|
||||
|
||||
const buttonVariants = cva(
|
||||
cn(
|
||||
"inline-flex items-center active:scale-[0.95] justify-center border cursor-pointer whitespace-nowrap",
|
||||
" text-sm transition-all disabled:pointer-events-none disabled:opacity-75 shrink-0",
|
||||
"[&>svg]:pointer-events-none [&>svg]:shrink-0",
|
||||
"focus-visible:ring-ring outline-0 focus-visible:ring-2 select-none"
|
||||
),
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-foreground bg-foreground text-background hover:bg-foreground/90 hover:border-foreground/90",
|
||||
neutral:
|
||||
"border-neutral/10 bg-neutral/40 text-foreground hover:bg-neutral/50 hover:border-neutral/20",
|
||||
outline: "text-foreground hover:bg-foreground/10 border-border hover:border-foreground/20",
|
||||
ghost: "text-foreground hover:bg-foreground/10 border-transparent",
|
||||
project:
|
||||
"border-project/25 bg-project/15 text-foreground hover:bg-project/30 hover:border-project/30",
|
||||
org: "border-org/25 bg-org/15 text-foreground hover:bg-org/30 hover:border-org/30",
|
||||
"sub-org":
|
||||
"border-sub-org/25 bg-sub-org/15 text-foreground hover:bg-sub-org/30 hover:border-sub-org/30",
|
||||
success:
|
||||
"border-success/25 bg-success/15 text-foreground hover:bg-success/30 hover:border-success/30",
|
||||
info: "border-info/25 bg-info/15 text-foreground hover:bg-info/30 hover:border-info/30",
|
||||
warning:
|
||||
"border-warning/25 bg-warning/15 text-foreground hover:bg-warning/30 hover:border-warning/30",
|
||||
danger:
|
||||
"border-danger/25 bg-danger/15 text-foreground hover:bg-danger/30 hover:border-danger/30"
|
||||
},
|
||||
size: {
|
||||
xs: "h-7 px-2 rounded-[3px] text-xs [&>svg]:size-3 gap-1.5",
|
||||
sm: "h-8 px-2.5 rounded-[4px] text-sm [&>svg]:size-3 gap-1.5",
|
||||
md: "h-9 px-3 rounded-[5px] text-sm [&>svg]:size-3.5 gap-1.5",
|
||||
lg: "h-10 px-3 rounded-[6px] text-sm [&>svg]:size-3.5 gap-1.5"
|
||||
},
|
||||
isPending: {
|
||||
true: "text-transparent"
|
||||
},
|
||||
isFullWidth: {
|
||||
true: "w-full",
|
||||
false: "w-fit"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "md"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
type UnstableButtonProps = (VariantProps<typeof buttonVariants> & {
|
||||
isPending?: boolean;
|
||||
isFullWidth?: boolean;
|
||||
isDisabled?: boolean;
|
||||
}) &
|
||||
(
|
||||
| ({ as?: "button" | undefined } & React.ComponentProps<"button">)
|
||||
| ({ as: "link"; className?: string } & LinkProps)
|
||||
| ({ as: "a" } & React.ComponentProps<"a">)
|
||||
);
|
||||
|
||||
const UnstableButton = forwardRef<HTMLButtonElement | HTMLAnchorElement, UnstableButtonProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
variant = "default",
|
||||
size = "md",
|
||||
isPending = false,
|
||||
isFullWidth = false,
|
||||
isDisabled = false,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
): JSX.Element => {
|
||||
const sharedProps = {
|
||||
"data-slot": "button",
|
||||
className: cn(buttonVariants({ variant, size, isPending, isFullWidth }), className)
|
||||
};
|
||||
|
||||
const child = (
|
||||
<>
|
||||
{children}
|
||||
{isPending && (
|
||||
<Lottie
|
||||
icon={variant === "default" ? "infisical_loading_bw" : "infisical_loading_white"}
|
||||
isAutoPlay
|
||||
className="absolute w-8 rounded-xl"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
switch (props.as) {
|
||||
case "a":
|
||||
return (
|
||||
<a
|
||||
ref={ref as React.Ref<HTMLAnchorElement>}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
{...props}
|
||||
{...sharedProps}
|
||||
>
|
||||
{child}
|
||||
</a>
|
||||
);
|
||||
case "link":
|
||||
return (
|
||||
<Link ref={ref as React.Ref<HTMLAnchorElement>} {...props} {...sharedProps}>
|
||||
{child}
|
||||
</Link>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<button
|
||||
ref={ref as React.Ref<HTMLButtonElement>}
|
||||
type="button"
|
||||
disabled={isPending || isDisabled}
|
||||
{...props}
|
||||
{...sharedProps}
|
||||
>
|
||||
{child}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
UnstableButton.displayName = "Button";
|
||||
|
||||
export { buttonVariants, UnstableButton, type UnstableButtonProps };
|
||||
1
frontend/src/components/v3/generic/Button/index.ts
Normal file
1
frontend/src/components/v3/generic/Button/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./Button";
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "cva";
|
||||
|
||||
import { cn } from "../../utils";
|
||||
import { UnstableSeparator } from "../Separator";
|
||||
|
||||
const buttonGroupVariants = cva(
|
||||
"flex w-fit items-stretch [&>*]:focus-visible:z-10 [&>*]:focus-visible:relative [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md has-[>[data-slot=button-group]]:gap-2",
|
||||
{
|
||||
variants: {
|
||||
orientation: {
|
||||
horizontal:
|
||||
"[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
|
||||
vertical:
|
||||
"flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
orientation: "horizontal"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function UnstableButtonGroup({
|
||||
className,
|
||||
orientation,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & VariantProps<typeof buttonGroupVariants>) {
|
||||
return (
|
||||
<div
|
||||
role="group"
|
||||
data-slot="button-group"
|
||||
data-orientation={orientation}
|
||||
className={cn(buttonGroupVariants({ orientation }), className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableButtonGroupText({
|
||||
className,
|
||||
asChild = false,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & {
|
||||
asChild?: boolean;
|
||||
}) {
|
||||
const Comp = asChild ? Slot : "div";
|
||||
|
||||
return (
|
||||
<Comp
|
||||
className={cn(
|
||||
"flex items-center gap-2 rounded-md border bg-muted px-4 text-sm font-medium shadow-xs [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableButtonGroupSeparator({
|
||||
className,
|
||||
orientation = "vertical",
|
||||
...props
|
||||
}: React.ComponentProps<typeof UnstableSeparator>) {
|
||||
return (
|
||||
<UnstableSeparator
|
||||
data-slot="button-group-separator"
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
buttonGroupVariants,
|
||||
UnstableButtonGroup,
|
||||
UnstableButtonGroupSeparator,
|
||||
UnstableButtonGroupText
|
||||
};
|
||||
1
frontend/src/components/v3/generic/ButtonGroup/index.ts
Normal file
1
frontend/src/components/v3/generic/ButtonGroup/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./ButtonGroup";
|
||||
88
frontend/src/components/v3/generic/Card/Card.tsx
Normal file
88
frontend/src/components/v3/generic/Card/Card.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "../../utils";
|
||||
|
||||
function UnstableCard({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card"
|
||||
className={cn("flex h-fit flex-col gap-6 rounded-[6px] text-foreground shadow-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableCardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-header"
|
||||
className={cn(
|
||||
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-center gap-1 border-mineshaft-400/60 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableCardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-title"
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 leading-none font-semibold [&>svg]:inline-block [&>svg]:size-[18px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-description"
|
||||
className={cn(
|
||||
"flex items-center gap-1 text-sm text-accent [&>svg]:inline-block [&>svg]:size-[12px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableCardAction({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-action"
|
||||
className={cn("col-start-2 row-span-2 row-start-1 self-start justify-self-end", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableCardContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return <div data-slot="card-content" className={cn("", className)} {...props} />;
|
||||
}
|
||||
|
||||
function UnstableCardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="card-footer"
|
||||
className={cn("flex items-center border-border [.border-t]:pt-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
UnstableCard,
|
||||
UnstableCardAction,
|
||||
UnstableCardContent,
|
||||
CardDescription as UnstableCardDescription,
|
||||
UnstableCardFooter,
|
||||
UnstableCardHeader,
|
||||
UnstableCardTitle
|
||||
};
|
||||
1
frontend/src/components/v3/generic/Card/index.ts
Normal file
1
frontend/src/components/v3/generic/Card/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./Card";
|
||||
23
frontend/src/components/v3/generic/Detail/Detail.tsx
Normal file
23
frontend/src/components/v3/generic/Detail/Detail.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { cn } from "../../utils";
|
||||
|
||||
function Detail({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return <div data-slot="detail" className={cn("flex flex-col gap-y-1", className)} {...props} />;
|
||||
}
|
||||
|
||||
function DetailLabel({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div data-slot="detail-label" className={cn("text-xs text-label", className)} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function DetailValue({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return <div data-slot="detail-value" className={cn("text-sm", className)} {...props} />;
|
||||
}
|
||||
|
||||
function DetailGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div data-slot="detail-group" className={cn("flex flex-col gap-y-4", className)} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export { Detail, DetailGroup, DetailLabel, DetailValue };
|
||||
1
frontend/src/components/v3/generic/Detail/index.ts
Normal file
1
frontend/src/components/v3/generic/Detail/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./Detail";
|
||||
254
frontend/src/components/v3/generic/Dropdown/Dropdown.tsx
Normal file
254
frontend/src/components/v3/generic/Dropdown/Dropdown.tsx
Normal file
@@ -0,0 +1,254 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
import * as React from "react";
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@app/components/v3/utils";
|
||||
|
||||
function UnstableDropdownMenu({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
||||
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
||||
}
|
||||
|
||||
function UnstableDropdownMenuPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
||||
return <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />;
|
||||
}
|
||||
|
||||
function UnstableDropdownMenuTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
||||
return <DropdownMenuPrimitive.Trigger data-slot="dropdown-menu-trigger" {...props} />;
|
||||
}
|
||||
|
||||
function UnstableDropdownMenuContent({
|
||||
className,
|
||||
sideOffset = 4,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
data-slot="dropdown-menu-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"max-h-(--radix-dropdown-menu-content-available-height) origin-(--radix-dropdown-menu-content-transform-origin)",
|
||||
"z-50 overflow-x-hidden overflow-y-auto rounded-[6px] border border-border/50 bg-popover p-1 text-xs text-foreground shadow-md",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableDropdownMenuGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
||||
return <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />;
|
||||
}
|
||||
|
||||
function UnstableDropdownMenuItem({
|
||||
className,
|
||||
inset,
|
||||
variant = "default",
|
||||
isDisabled,
|
||||
...props
|
||||
}: Omit<React.ComponentProps<typeof DropdownMenuPrimitive.Item>, "disabled"> & {
|
||||
inset?: boolean;
|
||||
variant?: "default" | "danger";
|
||||
isDisabled?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Item
|
||||
data-slot="dropdown-menu-item"
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
className={cn(
|
||||
"text-xs",
|
||||
"data-[variant=danger]:text-danger data-[variant=danger]:focus:bg-danger/10 data-[variant=danger]:*:[svg]:!text-danger",
|
||||
"relative flex cursor-pointer items-center gap-2 rounded-sm px-2 pt-2 pb-1.5 outline-0 select-none focus:bg-foreground/10",
|
||||
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8",
|
||||
"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3",
|
||||
className
|
||||
)}
|
||||
disabled={isDisabled}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableDropdownMenuCheckboxItem({
|
||||
className,
|
||||
children,
|
||||
checked,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
className={cn(
|
||||
"relative flex cursor-pointer items-center gap-2 rounded-sm pt-2 pr-8 pb-1.5 pl-2 text-xs outline-0 select-none",
|
||||
"focus:bg-foreground/10",
|
||||
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
"[&_svg]:pointer-events-none [&_svg]:mb-0.5 [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
||||
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute right-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableDropdownMenuRadioGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
||||
return <DropdownMenuPrimitive.RadioGroup data-slot="dropdown-menu-radio-group" {...props} />;
|
||||
}
|
||||
|
||||
function UnstableDropdownMenuRadioItem({
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
className={cn(
|
||||
"relative flex cursor-pointer items-center gap-2 rounded-sm pt-2 pr-8 pb-1.5 pl-2 text-xs outline-0 select-none",
|
||||
"focus:bg-foreground/10",
|
||||
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
"[&_svg]:pointer-events-none [&_svg]:mb-0.5 [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3.5",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute right-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<CircleIcon className="size-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableDropdownMenuLabel({
|
||||
className,
|
||||
inset,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Label
|
||||
data-slot="dropdown-menu-label"
|
||||
data-inset={inset}
|
||||
className={cn("px-2 py-1.5 text-xs font-medium text-accent data-[inset]:pl-8", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableDropdownMenuSeparator({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
data-slot="dropdown-menu-separator"
|
||||
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableDropdownMenuShortcut({ className, ...props }: React.ComponentProps<"span">) {
|
||||
return (
|
||||
<span
|
||||
data-slot="dropdown-menu-shortcut"
|
||||
className={cn("ml-auto text-xs tracking-widest text-accent", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableDropdownMenuSub({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
||||
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
|
||||
}
|
||||
|
||||
function UnstableDropdownMenuSubTrigger({
|
||||
className,
|
||||
inset,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
className={cn(
|
||||
"flex cursor-default items-center rounded-sm px-2 py-1.5 text-xs outline-0 select-none focus:bg-foreground/10 data-[inset]:pl-8 data-[state=open]:bg-foreground/10",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto size-3.5" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableDropdownMenuSubContent({
|
||||
className,
|
||||
sideOffset = 8,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-[6px] border border-border bg-popover p-1 text-foreground shadow-lg",
|
||||
className
|
||||
)}
|
||||
sideOffset={sideOffset}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
type UnstableDropdownMenuChecked = DropdownMenuPrimitive.DropdownMenuCheckboxItemProps["checked"];
|
||||
|
||||
export {
|
||||
UnstableDropdownMenu,
|
||||
UnstableDropdownMenuCheckboxItem,
|
||||
type UnstableDropdownMenuChecked,
|
||||
UnstableDropdownMenuContent,
|
||||
UnstableDropdownMenuGroup,
|
||||
UnstableDropdownMenuItem,
|
||||
UnstableDropdownMenuLabel,
|
||||
UnstableDropdownMenuPortal,
|
||||
UnstableDropdownMenuRadioGroup,
|
||||
UnstableDropdownMenuRadioItem,
|
||||
UnstableDropdownMenuSeparator,
|
||||
UnstableDropdownMenuShortcut,
|
||||
UnstableDropdownMenuSub,
|
||||
UnstableDropdownMenuSubContent,
|
||||
UnstableDropdownMenuSubTrigger,
|
||||
UnstableDropdownMenuTrigger
|
||||
};
|
||||
1
frontend/src/components/v3/generic/Dropdown/index.ts
Normal file
1
frontend/src/components/v3/generic/Dropdown/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./Dropdown";
|
||||
100
frontend/src/components/v3/generic/Empty/Empty.tsx
Normal file
100
frontend/src/components/v3/generic/Empty/Empty.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { cn } from "../../utils";
|
||||
|
||||
function UnstableEmpty({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="empty"
|
||||
className={cn(
|
||||
"flex min-w-0 flex-1 flex-col items-center justify-center gap-6 border-dashed border-border bg-container p-6 text-center text-balance md:p-12",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableEmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="empty-header"
|
||||
className={cn("flex max-w-sm flex-col items-center gap-2 text-center", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// scott: TODO
|
||||
|
||||
// const emptyMediaVariants = cva(
|
||||
// "flex shrink-0 items-center justify-center mb-2 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
// {
|
||||
// variants: {
|
||||
// variant: {
|
||||
// default: "bg-transparent",
|
||||
// icon: "bg-bunker-900 rounded text-foreground flex size-10 shrink-0 items-center justify-center [&_svg:not([class*='size-'])]:size-6"
|
||||
// }
|
||||
// },
|
||||
// defaultVariants: {
|
||||
// variant: "default"
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
// function EmptyMedia({
|
||||
// className,
|
||||
// variant = "default",
|
||||
// ...props
|
||||
// }: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
|
||||
// return (
|
||||
// <div
|
||||
// data-slot="empty-icon"
|
||||
// data-variant={variant}
|
||||
// className={cn(emptyMediaVariants({ variant, className }))}
|
||||
// {...props}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
||||
function UnstableEmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="empty-title"
|
||||
className={cn("text-sm font-medium tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableEmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="empty-description"
|
||||
className={cn(
|
||||
"text-xs/relaxed text-muted [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableEmptyContent({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="empty-content"
|
||||
className={cn(
|
||||
"flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
UnstableEmpty,
|
||||
UnstableEmptyContent,
|
||||
UnstableEmptyDescription,
|
||||
UnstableEmptyHeader,
|
||||
UnstableEmptyTitle
|
||||
};
|
||||
1
frontend/src/components/v3/generic/Empty/index.ts
Normal file
1
frontend/src/components/v3/generic/Empty/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./Empty";
|
||||
112
frontend/src/components/v3/generic/IconButton/IconButton.tsx
Normal file
112
frontend/src/components/v3/generic/IconButton/IconButton.tsx
Normal file
@@ -0,0 +1,112 @@
|
||||
import * as React from "react";
|
||||
import { forwardRef } from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { cva, type VariantProps } from "cva";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { Lottie } from "@app/components/v2";
|
||||
import { UnstableButton } from "@app/components/v3/generic";
|
||||
import { cn } from "@app/components/v3/utils";
|
||||
|
||||
const iconButtonVariants = cva(
|
||||
cn(
|
||||
"inline-flex items-center active:scale-[0.99] justify-center border cursor-pointer whitespace-nowrap rounded-[4px] text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-75 [&_svg]:pointer-events-none shrink-0 [&>svg]:shrink-0",
|
||||
"focus-visible:ring-ring outline-0 focus-visible:ring-2"
|
||||
),
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-foreground bg-foreground text-background hover:bg-foreground/90 hover:border-foreground/90",
|
||||
accent:
|
||||
"border-accent/10 bg-accent/40 text-foreground hover:bg-accent/50 hover:border-accent/20",
|
||||
outline: "text-foreground hover:bg-foreground/20 border-border hover:border-foreground/50",
|
||||
ghost: "text-foreground hover:bg-foreground/40 border-transparent",
|
||||
project:
|
||||
"border-project/75 bg-project/40 text-foreground hover:bg-project/50 hover:border-kms",
|
||||
org: "border-org/75 bg-org/40 text-foreground hover:bg-org/50 hover:border-org",
|
||||
"sub-org":
|
||||
"border-sub-org/75 bg-sub-org/40 text-foreground hover:bg-sub-org/50 hover:border-namespace",
|
||||
success:
|
||||
"border-success/75 bg-success/40 text-foreground hover:bg-success/50 hover:border-success",
|
||||
info: "border-info/75 bg-info/40 text-foreground hover:bg-info/50 hover:border-info",
|
||||
warning:
|
||||
"border-warning/75 bg-warning/40 text-foreground hover:bg-warning/50 hover:border-warning",
|
||||
danger:
|
||||
"border-danger/75 bg-danger/40 text-foreground hover:bg-danger/50 hover:border-danger"
|
||||
},
|
||||
size: {
|
||||
xs: "h-6 w-6 [&>svg]:size-4 rounded-[5px] [&>svg]:stroke-[1.75]",
|
||||
sm: "h-8 w-8 [&>svg]:size-5 [&>svg]:stroke-[1.5]",
|
||||
md: "h-9 w-9 [&>svg]:size-6 [&>svg]:stroke-[1.5]",
|
||||
lg: "h-10 w-10 [&>svg]:size-7 [&>svg]:stroke-[1.5]"
|
||||
},
|
||||
isPending: {
|
||||
true: "text-transparent"
|
||||
},
|
||||
isFullWidth: {
|
||||
true: "w-full",
|
||||
false: "w-fit"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "md"
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
type UnstableIconButtonProps = React.ComponentProps<"button"> &
|
||||
VariantProps<typeof iconButtonVariants> & {
|
||||
asChild?: boolean;
|
||||
isPending?: boolean;
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
const UnstableIconButton = forwardRef<HTMLButtonElement, UnstableIconButtonProps>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
variant = "default",
|
||||
size = "md",
|
||||
asChild = false,
|
||||
isPending = false,
|
||||
disabled = false,
|
||||
isDisabled = false,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
): JSX.Element => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
data-slot="button"
|
||||
className={cn(iconButtonVariants({ variant, size, isPending }), className)}
|
||||
disabled={isPending || disabled || isDisabled}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
{isPending && (
|
||||
<Lottie
|
||||
icon={variant === "default" ? "infisical_loading_bw" : "infisical_loading_white"}
|
||||
isAutoPlay
|
||||
className={twMerge(
|
||||
"absolute rounded-xl",
|
||||
size === "xs" && "w-6",
|
||||
size === "sm" && "w-7",
|
||||
size === "md" && "w-8",
|
||||
size === "lg" && "w-9"
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Comp>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
UnstableButton.displayName = "IconButton";
|
||||
|
||||
export { iconButtonVariants, UnstableIconButton, type UnstableIconButtonProps };
|
||||
1
frontend/src/components/v3/generic/IconButton/index.ts
Normal file
1
frontend/src/components/v3/generic/IconButton/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./IconButton";
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Lottie } from "@app/components/v2";
|
||||
|
||||
export function UnstablePageLoader() {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Lottie icon="infisical_loading" isAutoPlay className="w-24" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
frontend/src/components/v3/generic/PageLoader/index.ts
Normal file
1
frontend/src/components/v3/generic/PageLoader/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./PageLoader";
|
||||
28
frontend/src/components/v3/generic/Separator/Separator.tsx
Normal file
28
frontend/src/components/v3/generic/Separator/Separator.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
import * as React from "react";
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||
|
||||
import { cn } from "../../utils";
|
||||
|
||||
function UnstableSeparator({
|
||||
className,
|
||||
orientation = "horizontal",
|
||||
decorative = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
||||
return (
|
||||
<SeparatorPrimitive.Root
|
||||
data-slot="separator"
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export { UnstableSeparator };
|
||||
1
frontend/src/components/v3/generic/Separator/index.ts
Normal file
1
frontend/src/components/v3/generic/Separator/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./Separator";
|
||||
138
frontend/src/components/v3/generic/Table/Table.stories.tsx
Normal file
138
frontend/src/components/v3/generic/Table/Table.stories.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import { CopyIcon, EditIcon, MoreHorizontalIcon, TrashIcon } from "lucide-react";
|
||||
|
||||
import {
|
||||
Badge,
|
||||
UnstableDropdownMenu,
|
||||
UnstableDropdownMenuContent,
|
||||
UnstableDropdownMenuItem,
|
||||
UnstableDropdownMenuTrigger,
|
||||
UnstableIconButton
|
||||
} from "@app/components/v3/generic";
|
||||
import { ProjectIcon } from "@app/components/v3/platform";
|
||||
|
||||
import {
|
||||
UnstableTable,
|
||||
UnstableTableBody,
|
||||
UnstableTableCell,
|
||||
UnstableTableHead,
|
||||
UnstableTableHeader,
|
||||
UnstableTableRow
|
||||
} from "./Table";
|
||||
|
||||
const identities: {
|
||||
name: string;
|
||||
role: string;
|
||||
managedBy?: { scope: "org" | "namespace"; name: string };
|
||||
}[] = [
|
||||
{
|
||||
name: "machine-one",
|
||||
role: "Admin",
|
||||
managedBy: {
|
||||
scope: "org",
|
||||
name: "infisical"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "machine-two",
|
||||
role: "Viewer",
|
||||
managedBy: {
|
||||
scope: "namespace",
|
||||
name: "engineering"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "machine-three",
|
||||
role: "Developer"
|
||||
},
|
||||
{
|
||||
name: "machine-four",
|
||||
role: "Admin",
|
||||
managedBy: {
|
||||
scope: "namespace",
|
||||
name: "dev-ops"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "machine-five",
|
||||
role: "Viewer",
|
||||
managedBy: {
|
||||
scope: "org",
|
||||
name: "infisical"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "machine-six",
|
||||
role: "Developer"
|
||||
}
|
||||
];
|
||||
|
||||
function TableDemo() {
|
||||
return (
|
||||
<UnstableTable className="w-[800px]">
|
||||
<UnstableTableHeader>
|
||||
<UnstableTableRow>
|
||||
<UnstableTableHead className="w-1/3">Name</UnstableTableHead>
|
||||
<UnstableTableHead className="w-1/3">Role</UnstableTableHead>
|
||||
<UnstableTableHead className="w-1/3">Managed By</UnstableTableHead>
|
||||
<UnstableTableHead className="text-right" />
|
||||
</UnstableTableRow>
|
||||
</UnstableTableHeader>
|
||||
<UnstableTableBody>
|
||||
{identities.map((identity) => (
|
||||
<UnstableTableRow key={identity.name}>
|
||||
<UnstableTableCell className="font-medium">{identity.name}</UnstableTableCell>
|
||||
<UnstableTableCell>{identity.role}</UnstableTableCell>
|
||||
<UnstableTableCell>
|
||||
<Badge variant="project">
|
||||
<ProjectIcon />
|
||||
Project
|
||||
</Badge>
|
||||
</UnstableTableCell>
|
||||
<UnstableTableCell className="text-right">
|
||||
<UnstableDropdownMenu>
|
||||
<UnstableDropdownMenuTrigger asChild>
|
||||
<UnstableIconButton variant="ghost" size="xs">
|
||||
<MoreHorizontalIcon />
|
||||
</UnstableIconButton>
|
||||
</UnstableDropdownMenuTrigger>
|
||||
<UnstableDropdownMenuContent align="end" className="w-36">
|
||||
<UnstableDropdownMenuItem>
|
||||
<CopyIcon />
|
||||
Copy ID
|
||||
</UnstableDropdownMenuItem>
|
||||
<UnstableDropdownMenuItem>
|
||||
<EditIcon />
|
||||
Edit Identity
|
||||
</UnstableDropdownMenuItem>
|
||||
<UnstableDropdownMenuItem variant="danger">
|
||||
<TrashIcon />
|
||||
Delete Identity
|
||||
</UnstableDropdownMenuItem>
|
||||
</UnstableDropdownMenuContent>
|
||||
</UnstableDropdownMenu>
|
||||
</UnstableTableCell>
|
||||
</UnstableTableRow>
|
||||
))}
|
||||
</UnstableTableBody>
|
||||
</UnstableTable>
|
||||
);
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: "Generic/Table",
|
||||
component: TableDemo,
|
||||
parameters: {
|
||||
layout: "centered"
|
||||
},
|
||||
tags: ["autodocs"],
|
||||
argTypes: {}
|
||||
} satisfies Meta<typeof TableDemo>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const KitchenSInk: Story = {
|
||||
name: "Example: Kitchen Sink",
|
||||
args: {}
|
||||
};
|
||||
109
frontend/src/components/v3/generic/Table/Table.tsx
Normal file
109
frontend/src/components/v3/generic/Table/Table.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@app/components/v3/utils";
|
||||
|
||||
function UnstableTable({ className, ...props }: React.ComponentProps<"table">) {
|
||||
return (
|
||||
<div
|
||||
data-slot="table-container"
|
||||
className="relative w-full overflow-x-auto border border-border/50 bg-mineshaft-800/50"
|
||||
>
|
||||
<table
|
||||
data-slot="table"
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableTableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
||||
return (
|
||||
<thead
|
||||
data-slot="table-header"
|
||||
className={cn("text-sm [&_tr]:border-b [&_tr]:hover:bg-transparent", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableTableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
||||
return (
|
||||
<tbody data-slot="table-body" className={cn("[&>tr]:last:border-b-0", className)} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableTableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
||||
return (
|
||||
<tfoot
|
||||
data-slot="table-footer"
|
||||
className={cn(
|
||||
"border-t border-border/50 bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableTableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
||||
return (
|
||||
<tr
|
||||
data-slot="table-row"
|
||||
className={cn(
|
||||
"border-b border-border/50 transition-colors hover:bg-foreground/5 data-[state=selected]:bg-foreground/5",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableTableHead({ className, ...props }: React.ComponentProps<"th">) {
|
||||
return (
|
||||
<th
|
||||
data-slot="table-head"
|
||||
className={cn(
|
||||
"h-[30px] border-x-0 border-t-0 border-b border-border/50 px-3 text-left align-middle text-xs whitespace-nowrap text-accent text-mineshaft-400 [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableTableCell({ className, ...props }: React.ComponentProps<"td">) {
|
||||
return (
|
||||
<td
|
||||
data-slot="table-cell"
|
||||
className={cn(
|
||||
"h-[40px] px-3 align-middle whitespace-nowrap text-mineshaft-200 [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function UnstableTableCaption({ className, ...props }: React.ComponentProps<"caption">) {
|
||||
return (
|
||||
<caption
|
||||
data-slot="table-caption"
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
UnstableTable,
|
||||
UnstableTableBody,
|
||||
UnstableTableCaption,
|
||||
UnstableTableCell,
|
||||
UnstableTableFooter,
|
||||
UnstableTableHead,
|
||||
UnstableTableHeader,
|
||||
UnstableTableRow
|
||||
};
|
||||
1
frontend/src/components/v3/generic/Table/index.ts
Normal file
1
frontend/src/components/v3/generic/Table/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./Table";
|
||||
@@ -1 +1,12 @@
|
||||
export * from "./Alert";
|
||||
export * from "./Badge";
|
||||
export * from "./Button";
|
||||
export * from "./ButtonGroup";
|
||||
export * from "./Card";
|
||||
export * from "./Detail";
|
||||
export * from "./Dropdown";
|
||||
export * from "./Empty";
|
||||
export * from "./IconButton";
|
||||
export * from "./PageLoader";
|
||||
export * from "./Separator";
|
||||
export * from "./Table";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { BoxesIcon, BoxIcon, Building2Icon, ServerIcon } from "lucide-react";
|
||||
|
||||
const InstanceIcon = ServerIcon;
|
||||
const OrgIcon = Building2Icon;
|
||||
const SubOrgIcon = BoxesIcon;
|
||||
const ProjectIcon = BoxIcon;
|
||||
|
||||
export { InstanceIcon, OrgIcon, ProjectIcon, SubOrgIcon };
|
||||
export {
|
||||
ServerIcon as InstanceIcon,
|
||||
Building2Icon as OrgIcon,
|
||||
BoxIcon as ProjectIcon,
|
||||
BoxesIcon as SubOrgIcon
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user