From 68f1887d665958a7cf6ca3c70b831359ba0e85aa Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Thu, 19 Jan 2023 19:55:52 +0530 Subject: [PATCH] feat(ui): implemented button component --- frontend/package-lock.json | 51 +++++- frontend/package.json | 3 +- .../components/v2/Button/Button.stories.tsx | 71 ++++++++- frontend/src/components/v2/Button/Button.tsx | 148 +++++++++++++++++- frontend/src/components/v2/Button/index.tsx | 1 + frontend/src/components/v2/index.tsx | 1 + 6 files changed, 264 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/v2/index.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 44d791bb0c..cff1b80fb0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -28,8 +28,10 @@ "add": "^2.0.6", "axios": "^0.27.2", "axios-auth-refresh": "^3.3.3", + "class-variance-authority": "^0.4.0", "classnames": "^2.3.1", "cookies": "^0.8.0", + "cva": "npm:class-variance-authority@^0.4.0", "fs": "^0.0.1-security", "gray-matter": "^4.0.3", "http-proxy": "^1.18.1", @@ -9299,6 +9301,22 @@ "node": ">=0.10.0" } }, + "node_modules/class-variance-authority": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.4.0.tgz", + "integrity": "sha512-74enNN8O9ZNieycac/y8FxqgyzZhZbxmCitAtAeUrLPlxjSd5zA7LfpprmxEcOmQBnaGs5hYhiSGnJ0mqrtBLQ==", + "funding": { + "url": "https://joebell.co.uk" + }, + "peerDependencies": { + "typescript": ">= 4.5.5 < 5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/classnames": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", @@ -9938,6 +9956,23 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" }, + "node_modules/cva": { + "name": "class-variance-authority", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.4.0.tgz", + "integrity": "sha512-74enNN8O9ZNieycac/y8FxqgyzZhZbxmCitAtAeUrLPlxjSd5zA7LfpprmxEcOmQBnaGs5hYhiSGnJ0mqrtBLQ==", + "funding": { + "url": "https://joebell.co.uk" + }, + "peerDependencies": { + "typescript": ">= 4.5.5 < 5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -21135,7 +21170,7 @@ "version": "4.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -28787,6 +28822,12 @@ } } }, + "class-variance-authority": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.4.0.tgz", + "integrity": "sha512-74enNN8O9ZNieycac/y8FxqgyzZhZbxmCitAtAeUrLPlxjSd5zA7LfpprmxEcOmQBnaGs5hYhiSGnJ0mqrtBLQ==", + "requires": {} + }, "classnames": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", @@ -29292,6 +29333,12 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" }, + "cva": { + "version": "npm:class-variance-authority@0.4.0", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.4.0.tgz", + "integrity": "sha512-74enNN8O9ZNieycac/y8FxqgyzZhZbxmCitAtAeUrLPlxjSd5zA7LfpprmxEcOmQBnaGs5hYhiSGnJ0mqrtBLQ==", + "requires": {} + }, "damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -37538,7 +37585,7 @@ "version": "4.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", - "dev": true + "devOptional": true }, "uc.micro": { "version": "1.0.6", diff --git a/frontend/package.json b/frontend/package.json index 8c30f749ec..60b8eda85e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,7 +8,7 @@ "start:docker": "next build && next start", "lint": "eslint --fix --ext js,ts,tsx ./src", "type-check": "tsc --project tsconfig.json", - "storybook": "storybook dev -p 6006", + "storybook": "storybook dev -p 6006 -s ./public", "build-storybook": "storybook build" }, "dependencies": { @@ -37,6 +37,7 @@ "axios-auth-refresh": "^3.3.3", "classnames": "^2.3.1", "cookies": "^0.8.0", + "cva": "npm:class-variance-authority@^0.4.0", "fs": "^0.0.1-security", "gray-matter": "^4.0.3", "http-proxy": "^1.18.1", diff --git a/frontend/src/components/v2/Button/Button.stories.tsx b/frontend/src/components/v2/Button/Button.stories.tsx index 0e78b96491..51e58d0da1 100644 --- a/frontend/src/components/v2/Button/Button.stories.tsx +++ b/frontend/src/components/v2/Button/Button.stories.tsx @@ -1,3 +1,5 @@ +import { faPlus } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import type { Meta, StoryObj } from '@storybook/react'; import { Button } from './Button'; @@ -6,7 +8,16 @@ const meta: Meta = { title: 'Components/Button', component: Button, tags: ['v2'], - argTypes: {} + argTypes: { + isRounded: { + defaultValue: true, + type: 'boolean' + }, + isFullWidth: { + defaultValue: false, + type: 'boolean' + } + } }; export default meta; @@ -18,3 +29,61 @@ export const Primary: Story = { children: 'Hello Infisical' } }; + +export const Secondary: Story = { + args: { + children: 'Hello Infisical', + colorSchema: 'secondary', + variant: 'outline' + } +}; + +export const Danger: Story = { + args: { + children: 'Hello Infisical', + colorSchema: 'danger', + variant: 'solid' + } +}; + +export const Plain: Story = { + args: { + children: 'Hello Infisical', + variant: 'plain' + } +}; + +export const Disabled: Story = { + args: { + children: 'Hello Infisical', + disabled: true + } +}; + +export const FullWidth: Story = { + args: { + children: 'Hello Infisical', + isFullWidth: true + } +}; + +export const Loading: Story = { + args: { + children: 'Hello Infisical', + isLoading: true + } +}; + +export const LeftIcon: Story = { + args: { + children: 'Hello Infisical', + leftIcon: + } +}; + +export const RightIcon: Story = { + args: { + children: 'Hello Infisical', + rightIcon: + } +}; diff --git a/frontend/src/components/v2/Button/Button.tsx b/frontend/src/components/v2/Button/Button.tsx index a1edb26bf7..f00d1d0a36 100644 --- a/frontend/src/components/v2/Button/Button.tsx +++ b/frontend/src/components/v2/Button/Button.tsx @@ -1,32 +1,166 @@ import { ButtonHTMLAttributes, forwardRef, ReactNode } from 'react'; +import { cva, VariantProps } from 'cva'; import { twMerge } from 'tailwind-merge'; type Props = { children: ReactNode; isDisabled?: boolean; + leftIcon?: ReactNode; + rightIcon?: ReactNode; // loading state isLoading?: boolean; - // various button sizes - size: 'sm' | 'md' | 'lg'; }; -export type ButtonProps = ButtonHTMLAttributes & Props; +const buttonVariants = cva( + [ + 'button', + 'transition-all', + 'font-medium', + 'cursor-pointer', + 'inline-flex items-center justify-center', + 'relative' + ], + { + variants: { + colorSchema: { + primary: ['bg-primary', 'text-black', 'border-primary hover:bg-opacity-80'], + secondary: ['bg-mineshaft-200', 'text-white', 'border-mineshaft-200'], + danger: ['bg-red', 'text-white', 'border-red'] + }, + variant: { + solid: '', + outline: ['bg-transparent', 'border', 'border-solid'], + plain: '' + }, + isDisabled: { + true: 'bg-opacity-70', + false: '' + }, + isFullWidth: { + true: 'w-full', + false: '' + }, + isRounded: { + true: 'rounded-md', + false: '' + }, + size: { + xs: ['text-xs', 'py-1', 'px-2'], + sm: ['text-sm', 'py-2', 'px-4'], + md: ['text-md', 'py-2', 'px-6'], + lg: ['text-lg', 'py-2', 'px-8'] + } + }, + compoundVariants: [ + { + colorSchema: 'primary', + variant: 'outline', + className: 'text-primary hover:bg-primary hover:text-black' + }, + { + colorSchema: 'secondary', + variant: 'outline', + className: 'text-mineshaft-200 hover:bg-mineshaft-400 hover:text-white' + }, + { + colorSchema: 'danger', + variant: 'outline', + className: 'text-red hover:bg-red hover:text-black' + }, + { + colorSchema: 'primary', + variant: 'plain', + className: 'text-primary' + }, + { + colorSchema: 'secondary', + variant: 'plain', + className: 'text-mineshaft-400' + }, + { + colorSchema: 'danger', + variant: 'plain', + className: 'text-red' + }, + { + colorSchema: ['danger', 'primary', 'secondary'], + variant: ['plain'], + className: 'bg-transparent py-1 px-1' + } + ] + } +); + +export type ButtonProps = ButtonHTMLAttributes & + VariantProps & + Props; export const Button = forwardRef( - ({ children, isDisabled = false, className, ...props }, ref): JSX.Element => { + ( + { + children, + isDisabled = false, + className = '', + size = 'md', + variant = 'solid', + isFullWidth, + isRounded = true, + leftIcon, + rightIcon, + isLoading, + colorSchema = 'primary', + ...props + }, + ref + ): JSX.Element => { + const loadingToggleClass = isLoading ? 'opacity-0' : 'opacity-100'; + return ( ); } diff --git a/frontend/src/components/v2/Button/index.tsx b/frontend/src/components/v2/Button/index.tsx index fe9c53c511..9cad84da16 100644 --- a/frontend/src/components/v2/Button/index.tsx +++ b/frontend/src/components/v2/Button/index.tsx @@ -1 +1,2 @@ +export type { ButtonProps } from './Button'; export { Button } from './Button'; diff --git a/frontend/src/components/v2/index.tsx b/frontend/src/components/v2/index.tsx new file mode 100644 index 0000000000..8b166a86e4 --- /dev/null +++ b/frontend/src/components/v2/index.tsx @@ -0,0 +1 @@ +export * from './Button';