Initial commit: Plane
Some checks failed
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Admin Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Sync Repositories / sync_changes (push) Has been cancelled

Synced from upstream: 8853637e981ed7d8a6cff32bd98e7afe20f54362
This commit is contained in:
chuan
2025-11-07 00:00:52 +08:00
commit 8ebde8aa05
4886 changed files with 462270 additions and 0 deletions

View File

@@ -0,0 +1,245 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { Button } from "./button";
const meta = {
title: "Components/Button",
component: Button,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
args: {
children: "Button",
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {};
export const Primary: Story = {
args: {
variant: "primary",
children: "Primary Button",
},
};
export const AccentPrimary: Story = {
args: {
variant: "accent-primary",
children: "Accent Primary Button",
},
};
export const OutlinePrimary: Story = {
args: {
variant: "outline-primary",
children: "Outline Primary Button",
},
};
export const NeutralPrimary: Story = {
args: {
variant: "neutral-primary",
children: "Neutral Primary Button",
},
};
export const LinkPrimary: Story = {
args: {
variant: "link-primary",
children: "Link Primary Button",
},
};
export const Danger: Story = {
args: {
variant: "danger",
children: "Danger Button",
},
};
export const AccentDanger: Story = {
args: {
variant: "accent-danger",
children: "Accent Danger Button",
},
};
export const OutlineDanger: Story = {
args: {
variant: "outline-danger",
children: "Outline Danger Button",
},
};
export const LinkDanger: Story = {
args: {
variant: "link-danger",
children: "Link Danger Button",
},
};
export const TertiaryDanger: Story = {
args: {
variant: "tertiary-danger",
children: "Tertiary Danger Button",
},
};
export const LinkNeutral: Story = {
args: {
variant: "link-neutral",
children: "Link Neutral Button",
},
};
export const Small: Story = {
args: {
size: "sm",
children: "Small Button",
},
};
export const Medium: Story = {
args: {
size: "md",
children: "Medium Button",
},
};
export const Large: Story = {
args: {
size: "lg",
children: "Large Button",
},
};
export const ExtraLarge: Story = {
args: {
size: "xl",
children: "Extra Large Button",
},
};
export const Loading: Story = {
args: {
loading: true,
children: "Loading Button",
},
};
export const Disabled: Story = {
args: {
disabled: true,
children: "Disabled Button",
},
};
export const WithPrependIcon: Story = {
args: {
prependIcon: (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M12 5v14m-7-7h14" />
</svg>
),
children: "With Prepend Icon",
},
};
export const WithAppendIcon: Story = {
args: {
appendIcon: (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="M9 5l7 7-7 7" />
</svg>
),
children: "With Append Icon",
},
};
export const AllVariants: Story = {
render() {
return (
<div className="space-y-4">
<div className="space-y-2">
<h3 className="text-lg font-semibold">Primary Variants</h3>
<div className="flex flex-wrap gap-2">
<Button variant="primary">Primary</Button>
<Button variant="accent-primary">Accent Primary</Button>
<Button variant="outline-primary">Outline Primary</Button>
<Button variant="neutral-primary">Neutral Primary</Button>
<Button variant="link-primary">Link Primary</Button>
</div>
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold">Danger Variants</h3>
<div className="flex flex-wrap gap-2">
<Button variant="danger">Danger</Button>
<Button variant="accent-danger">Accent Danger</Button>
<Button variant="outline-danger">Outline Danger</Button>
<Button variant="link-danger">Link Danger</Button>
<Button variant="tertiary-danger">Tertiary Danger</Button>
</div>
</div>
<div className="space-y-2">
<h3 className="text-lg font-semibold">Other Variants</h3>
<div className="flex flex-wrap gap-2">
<Button variant="link-neutral">Link Neutral</Button>
</div>
</div>
</div>
);
},
};
export const AllSizes: Story = {
render() {
return (
<div className="space-y-4">
<div className="flex items-center gap-2">
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
<Button size="xl">Extra Large</Button>
</div>
</div>
);
},
};
export const AllStates: Story = {
render() {
return (
<div className="space-y-4">
<div className="space-y-2">
<h3 className="text-lg font-semibold">Button States</h3>
<div className="flex flex-wrap gap-2">
<Button>Default</Button>
<Button loading>Loading</Button>
<Button disabled>Disabled</Button>
</div>
</div>
</div>
);
},
};

View File

@@ -0,0 +1,44 @@
import * as React from "react";
import { cn } from "../utils";
import { getIconStyling, getButtonStyling, TButtonVariant, TButtonSizes } from "./helper";
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: TButtonVariant;
size?: TButtonSizes;
className?: string;
loading?: boolean;
disabled?: boolean;
appendIcon?: any;
prependIcon?: any;
children: React.ReactNode;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
const {
variant = "primary",
size = "md",
className = "",
type = "button",
loading = false,
disabled = false,
prependIcon = null,
appendIcon = null,
children,
...rest
} = props;
const buttonStyle = getButtonStyling(variant, size, disabled || loading);
const buttonIconStyle = getIconStyling(size);
return (
<button ref={ref} type={type} className={cn(buttonStyle, className)} disabled={disabled || loading} {...rest}>
{prependIcon && <div className={buttonIconStyle}>{React.cloneElement(prependIcon, { strokeWidth: 2 })}</div>}
{children}
{appendIcon && <div className={buttonIconStyle}>{React.cloneElement(appendIcon, { strokeWidth: 2 })}</div>}
</button>
);
});
Button.displayName = "plane-ui-button";
export { Button };

View File

@@ -0,0 +1,126 @@
export type TButtonVariant =
| "primary"
| "accent-primary"
| "outline-primary"
| "neutral-primary"
| "link-primary"
| "danger"
| "accent-danger"
| "outline-danger"
| "link-danger"
| "tertiary-danger"
| "link-neutral";
export type TButtonSizes = "sm" | "md" | "lg" | "xl";
export interface IButtonStyling {
[key: string]: {
default: string;
hover: string;
pressed: string;
disabled: string;
};
}
enum buttonSizeStyling {
sm = `px-3 py-1.5 font-medium text-xs rounded flex items-center gap-1.5 whitespace-nowrap transition-all justify-center`,
md = `px-4 py-1.5 font-medium text-sm rounded flex items-center gap-1.5 whitespace-nowrap transition-all justify-center`,
lg = `px-5 py-2 font-medium text-sm rounded flex items-center gap-1.5 whitespace-nowrap transition-all justify-center`,
xl = `px-5 py-3.5 font-medium text-sm rounded flex items-center gap-1.5 whitespace-nowrap transition-all justify-center`,
}
enum buttonIconStyling {
sm = "h-3 w-3 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0",
md = "h-3.5 w-3.5 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0",
lg = "h-4 w-4 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0",
xl = "h-4 w-4 flex justify-center items-center overflow-hidden my-0.5 flex-shrink-0 ",
}
export const buttonStyling: IButtonStyling = {
primary: {
default: `text-white bg-custom-primary-100`,
hover: `hover:bg-custom-primary-200`,
pressed: `focus:text-custom-brand-40 focus:bg-custom-primary-200`,
disabled: `cursor-not-allowed !bg-custom-primary-60 hover:bg-custom-primary-60`,
},
"accent-primary": {
default: `bg-custom-primary-100/20 text-custom-primary-100`,
hover: `hover:bg-custom-primary-100/10 hover:text-custom-primary-200`,
pressed: `focus:bg-custom-primary-100/10`,
disabled: `cursor-not-allowed !text-custom-primary-60`,
},
"outline-primary": {
default: `text-custom-primary-100 bg-transparent border border-custom-primary-100`,
hover: `hover:bg-custom-primary-100/20`,
pressed: `focus:text-custom-primary-100 focus:bg-custom-primary-100/30`,
disabled: `cursor-not-allowed !text-custom-primary-60 !border-custom-primary-60 `,
},
"neutral-primary": {
default: `text-custom-text-200 bg-custom-background-100 border border-custom-border-200`,
hover: `hover:bg-custom-background-90`,
pressed: `focus:text-custom-text-300 focus:bg-custom-background-90`,
disabled: `cursor-not-allowed !text-custom-text-400`,
},
"link-primary": {
default: `text-custom-primary-100 bg-custom-background-100`,
hover: `hover:text-custom-primary-200`,
pressed: `focus:text-custom-primary-80 `,
disabled: `cursor-not-allowed !text-custom-primary-60`,
},
danger: {
default: `text-white bg-red-500`,
hover: ` hover:bg-red-600`,
pressed: `focus:text-red-200 focus:bg-red-600`,
disabled: `cursor-not-allowed !bg-red-300`,
},
"accent-danger": {
default: `text-red-500 bg-red-50`,
hover: `hover:text-red-600 hover:bg-red-100`,
pressed: `focus:text-red-500 focus:bg-red-100`,
disabled: `cursor-not-allowed !text-red-300`,
},
"outline-danger": {
default: `text-red-500 bg-transparent border border-red-500`,
hover: `hover:text-red-400 hover:border-red-400`,
pressed: `focus:text-red-400 focus:border-red-400`,
disabled: `cursor-not-allowed !text-red-300 !border-red-300`,
},
"link-danger": {
default: `text-red-500 bg-custom-background-100`,
hover: `hover:text-red-400`,
pressed: `focus:text-red-400`,
disabled: `cursor-not-allowed !text-red-300`,
},
"tertiary-danger": {
default: `text-red-500 bg-custom-background-100 border border-red-200`,
hover: `hover:bg-red-50 hover:border-red-300`,
pressed: `focus:text-red-400`,
disabled: `cursor-not-allowed !text-red-300`,
},
"link-neutral": {
default: `text-custom-text-300`,
hover: `hover:text-custom-text-200`,
pressed: `focus:text-custom-text-100`,
disabled: `cursor-not-allowed !text-custom-text-400`,
},
};
export const getButtonStyling = (variant: TButtonVariant, size: TButtonSizes, disabled: boolean = false): string => {
let tempVariant: string = ``;
const currentVariant = buttonStyling[variant];
tempVariant = `${currentVariant.default} ${disabled ? currentVariant.disabled : currentVariant.hover} ${
currentVariant.pressed
}`;
let tempSize: string = ``;
if (size) tempSize = buttonSizeStyling[size];
return `${tempVariant} ${tempSize}`;
};
export const getIconStyling = (size: TButtonSizes): string => {
let icon: string = ``;
if (size) icon = buttonIconStyling[size];
return icon;
};

View File

@@ -0,0 +1,3 @@
export { Button } from "./button";
export * from "./helper";
export type { ButtonProps } from "./button";