base
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
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 ErrorFill: Story = {
|
||||
args: {
|
||||
variant: "error-fill",
|
||||
children: "Error Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const ErrorOutline: Story = {
|
||||
args: {
|
||||
variant: "error-outline",
|
||||
children: "Error Outline Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
variant: "secondary",
|
||||
children: "Secondary Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const Tertiary: Story = {
|
||||
args: {
|
||||
variant: "tertiary",
|
||||
children: "Tertiary Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const Ghost: Story = {
|
||||
args: {
|
||||
variant: "ghost",
|
||||
children: "Ghost Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const Link: Story = {
|
||||
args: {
|
||||
variant: "link",
|
||||
children: "Link Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const Small: Story = {
|
||||
args: {
|
||||
size: "sm",
|
||||
children: "Small Button",
|
||||
},
|
||||
};
|
||||
|
||||
export const Base: Story = {
|
||||
args: {
|
||||
size: "base",
|
||||
children: "Base 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-16 font-semibold">Primary Variants</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Button variant="primary">Primary</Button>
|
||||
<Button variant="error-fill">Error Fill</Button>
|
||||
<Button variant="error-outline">Error Outline</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="tertiary">Tertiary</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
<Button variant="link">Link</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="base">Base</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-16 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>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { cn } from "../utils";
|
||||
import type { ButtonProps } from "./helper";
|
||||
import { getIconStyling, buttonVariants } from "./helper";
|
||||
|
||||
const Button = React.forwardRef(function Button(props: ButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) {
|
||||
const {
|
||||
variant = "primary",
|
||||
size = "base",
|
||||
className = "",
|
||||
type = "button",
|
||||
loading = false,
|
||||
disabled = false,
|
||||
prependIcon = null,
|
||||
appendIcon = null,
|
||||
children,
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const buttonIconStyle = getIconStyling(size ?? "base");
|
||||
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type={type}
|
||||
className={cn(buttonVariants({ variant, size }), className)}
|
||||
disabled={disabled || loading}
|
||||
{...rest}
|
||||
>
|
||||
{prependIcon && React.cloneElement(prependIcon, { className: cn("shrink-0", buttonIconStyle), strokeWidth: 2 })}
|
||||
{children}
|
||||
{appendIcon && React.cloneElement(appendIcon, { className: cn("shrink-0", buttonIconStyle), strokeWidth: 2 })}
|
||||
</button>
|
||||
);
|
||||
});
|
||||
|
||||
Button.displayName = "plane-ui-button";
|
||||
|
||||
export { Button };
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
import type { VariantProps } from "class-variance-authority";
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
export const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-1 whitespace-nowrap transition-colors focus-visible:outline-none disabled:pointer-events-none",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
primary:
|
||||
"bg-accent-primary text-on-color hover:bg-accent-primary-hover active:bg-accent-primary-active disabled:bg-layer-disabled disabled:text-on-color-disabled",
|
||||
"error-fill":
|
||||
"bg-danger-primary text-on-color hover:bg-danger-primary-hover active:bg-danger-primary-active disabled:bg-layer-disabled disabled:text-disabled",
|
||||
"error-outline":
|
||||
"border border-danger-strong bg-layer-2 text-danger-secondary hover:bg-danger-subtle active:bg-danger-subtle-hover disabled:border-subtle-1 disabled:bg-layer-2 disabled:text-disabled",
|
||||
secondary:
|
||||
"border border-strong bg-layer-2 text-secondary shadow-raised-100 hover:bg-layer-2-hover active:bg-layer-2-active disabled:border-subtle-1 disabled:bg-layer-transparent disabled:text-disabled",
|
||||
tertiary:
|
||||
"bg-layer-3 text-secondary hover:bg-layer-3-hover active:bg-layer-3-active disabled:bg-layer-transparent disabled:text-disabled",
|
||||
ghost:
|
||||
"bg-layer-transparent text-secondary hover:bg-layer-transparent-hover focus:bg-layer-transparent-active active:bg-layer-transparent-active disabled:bg-layer-transparent disabled:text-disabled",
|
||||
link: "px-0 text-link-primary underline hover:text-link-primary-hover focus:text-link-primary-hover active:text-link-primary-hover disabled:text-disabled",
|
||||
},
|
||||
size: {
|
||||
sm: "h-5 rounded-sm px-1.5 text-caption-md-medium",
|
||||
base: "h-6 rounded-md px-2 text-body-xs-medium",
|
||||
lg: "h-7 rounded-md px-2 text-body-xs-medium",
|
||||
xl: "h-8 rounded-md px-2 text-body-sm-medium",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "primary",
|
||||
size: "base",
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
appendIcon?: React.ReactElement;
|
||||
loading?: boolean;
|
||||
prependIcon?: React.ReactElement;
|
||||
};
|
||||
|
||||
export type TButtonVariant = NonNullable<ButtonProps["variant"]>;
|
||||
export type TButtonSize = NonNullable<ButtonProps["size"]>;
|
||||
|
||||
const buttonIconStyling: Record<TButtonSize, string> = {
|
||||
sm: "size-3.5",
|
||||
base: "size-3.5",
|
||||
lg: "size-4",
|
||||
xl: "size-4 ",
|
||||
};
|
||||
|
||||
export function getIconStyling(size: TButtonSize): string {
|
||||
return buttonIconStyling[size];
|
||||
}
|
||||
|
||||
export function getButtonStyling(variant: TButtonVariant, size: TButtonSize): string {
|
||||
return buttonVariants({ variant, size });
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
export { Button } from "./button";
|
||||
export { getButtonStyling } from "./helper";
|
||||
export type { ButtonProps, TButtonVariant, TButtonSize } from "./helper";
|
||||
Reference in New Issue
Block a user