From 68f1887d665958a7cf6ca3c70b831359ba0e85aa Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Thu, 19 Jan 2023 19:55:52 +0530 Subject: [PATCH 01/14] 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 44d791bb0..cff1b80fb 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 8c30f749e..60b8eda85 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 0e78b9649..51e58d0da 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 a1edb26bf..f00d1d0a3 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 fe9c53c51..9cad84da1 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 000000000..8b166a86e --- /dev/null +++ b/frontend/src/components/v2/index.tsx @@ -0,0 +1 @@ +export * from './Button'; From b80504ae0026adc64416f55843d466ceebfb0e02 Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Thu, 19 Jan 2023 21:07:44 +0530 Subject: [PATCH 02/14] feat(ui): implemented new input component --- .../src/components/v2/Input/Input.stories.tsx | 66 ++++++++++++ frontend/src/components/v2/Input/Input.tsx | 102 ++++++++++++++++++ frontend/src/components/v2/Input/index.tsx | 2 + 3 files changed, 170 insertions(+) create mode 100644 frontend/src/components/v2/Input/Input.stories.tsx create mode 100644 frontend/src/components/v2/Input/Input.tsx create mode 100644 frontend/src/components/v2/Input/index.tsx diff --git a/frontend/src/components/v2/Input/Input.stories.tsx b/frontend/src/components/v2/Input/Input.stories.tsx new file mode 100644 index 000000000..288805b21 --- /dev/null +++ b/frontend/src/components/v2/Input/Input.stories.tsx @@ -0,0 +1,66 @@ +import { faEye, faMailBulk } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import type { Meta, StoryObj } from '@storybook/react'; + +import { Input } from './Input'; + +const meta: Meta = { + title: 'Components/Input', + component: Input, + tags: ['v2'], + argTypes: { + isRounded: { + defaultValue: true, + type: 'boolean' + }, + placeholder: { + defaultValue: 'Type anything', + type: 'string' + } + } +}; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args +export const Filled: Story = { + args: {} +}; + +export const Outline: Story = { + args: { + variant: 'outline' + } +}; + +export const Plain: Story = { + args: { + variant: 'plain' + } +}; + +export const Error: Story = { + args: { + isError: true + } +}; + +export const AutoWidth: Story = { + args: { + isFullWidth: false, + className: 'w-auto' + } +}; + +export const RightIcon: Story = { + args: { + rightIcon: + } +}; + +export const LeftIcon: Story = { + args: { + leftIcon: + } +}; diff --git a/frontend/src/components/v2/Input/Input.tsx b/frontend/src/components/v2/Input/Input.tsx new file mode 100644 index 000000000..e7b63e3ad --- /dev/null +++ b/frontend/src/components/v2/Input/Input.tsx @@ -0,0 +1,102 @@ +import { forwardRef, InputHTMLAttributes, ReactNode } from 'react'; +import { cva, VariantProps } from 'cva'; +import { twMerge } from 'tailwind-merge'; + +type Props = { + placeholder?: string; + isFullWidth?: boolean; + isRequired?: boolean; + leftIcon?: ReactNode; + rightIcon?: ReactNode; +}; + +const inputVariants = cva( + 'input w-full py-2 text-gray-400 placeholder-gray-500 placeholder-opacity-50', + { + variants: { + size: { + xs: ['text-xs'], + sm: ['text-sm'], + md: ['text-md'], + lg: ['text-lg'] + }, + isRounded: { + true: ['rounded-md'], + false: '' + }, + variant: { + filled: ['bg-bunker-800', 'text-gray-400'], + outline: ['bg-transparent'], + plain: 'bg-transparent outline-none' + }, + isError: { + true: 'focus:ring-red/50 placeholder-red-300', + false: 'focus:ring-primary/50' + } + }, + compoundVariants: [] + } +); + +const inputParentContainerVariants = cva('inline-flex items-center border relative', { + variants: { + isRounded: { + true: ['rounded-md'], + false: '' + }, + isError: { + true: 'border-red', + false: 'border-mineshaft-400' + }, + isFullWidth: { + true: 'w-full', + false: '' + }, + variant: { + filled: ['bg-bunker-800', 'text-gray-400'], + outline: ['bg-transparent'], + plain: 'border-none' + } + } +}); + +export type InputProps = Omit, 'size'> & + VariantProps & + Props; + +export const Input = forwardRef( + ( + { + className, + isRounded = true, + isFullWidth = true, + isError = false, + isRequired, + leftIcon, + rightIcon, + variant = 'filled', + size = 'md', + ...props + }, + ref + ): JSX.Element => { + return ( +
+ {leftIcon && {leftIcon}} + + {rightIcon && {rightIcon}} +
+ ); + } +); + +Input.displayName = 'Input'; diff --git a/frontend/src/components/v2/Input/index.tsx b/frontend/src/components/v2/Input/index.tsx new file mode 100644 index 000000000..803d2fbab --- /dev/null +++ b/frontend/src/components/v2/Input/index.tsx @@ -0,0 +1,2 @@ +export type { InputProps } from './Input'; +export { Input } from './Input'; From e7ac74c5a0b73bf63ba6703e4d4f68da4ec549b5 Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Thu, 19 Jan 2023 23:16:59 +0530 Subject: [PATCH 03/14] feat(ui): implemented form control components --- .../v2/FormControl/FormControl.stories.tsx | 40 +++++++++++ .../components/v2/FormControl/FormControl.tsx | 72 +++++++++++++++++++ .../src/components/v2/FormControl/index.tsx | 2 + frontend/src/components/v2/index.tsx | 2 + 4 files changed, 116 insertions(+) create mode 100644 frontend/src/components/v2/FormControl/FormControl.stories.tsx create mode 100644 frontend/src/components/v2/FormControl/FormControl.tsx create mode 100644 frontend/src/components/v2/FormControl/index.tsx diff --git a/frontend/src/components/v2/FormControl/FormControl.stories.tsx b/frontend/src/components/v2/FormControl/FormControl.stories.tsx new file mode 100644 index 000000000..2e3692228 --- /dev/null +++ b/frontend/src/components/v2/FormControl/FormControl.stories.tsx @@ -0,0 +1,40 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +// Be careful on dep cycle +import { Input } from '../Input/Input'; +import { FormControl } from './FormControl'; + +const meta: Meta = { + title: 'Components/FormControl', + component: FormControl, + tags: ['v2'], + argTypes: {} +}; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args +export const Basic: Story = { + args: { + children: , + label: 'Email', + id: 'email', + helperText: 'Type something..' + } +}; + +export const RequiredInput: Story = { + args: { + ...Basic.args, + isRequired: true + } +}; + +export const ErrorInput: Story = { + args: { + ...Basic.args, + errorText: 'Some random error', + isError: true + } +}; diff --git a/frontend/src/components/v2/FormControl/FormControl.tsx b/frontend/src/components/v2/FormControl/FormControl.tsx new file mode 100644 index 000000000..d609b24f4 --- /dev/null +++ b/frontend/src/components/v2/FormControl/FormControl.tsx @@ -0,0 +1,72 @@ +import { cloneElement, ReactNode } from 'react'; +import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import * as Label from '@radix-ui/react-label'; +import { twMerge } from 'tailwind-merge'; + +export type FormLabelProps = { + id?: string; + isRequired?: boolean; + label?: ReactNode; +}; + +export const FormLabel = ({ id, label, isRequired }: FormLabelProps) => ( + + {label} + {isRequired && *} + +); + +export type FormHelperTextProps = { + isError?: boolean; + text?: ReactNode; +}; + +export const FormHelperText = ({ isError, text }: FormHelperTextProps) => ( +
+ {isError && ( + + + + )} + {text} +
+); + +export type FormControlProps = { + id?: string; + isRequired?: boolean; + isError?: boolean; + label?: ReactNode; + helperText?: ReactNode; + errorText?: ReactNode; + children: JSX.Element; +}; + +export const FormControl = ({ + children, + isRequired, + label, + helperText, + errorText, + id, + isError +}: FormControlProps): JSX.Element => { + return ( +
+ {typeof label === 'string' ? ( + + ) : ( + label + )} + {cloneElement(children, { isRequired, 'data-required': isRequired, isError })} + {!isError && helperText && } + {isError && errorText && } +
+ ); +}; diff --git a/frontend/src/components/v2/FormControl/index.tsx b/frontend/src/components/v2/FormControl/index.tsx new file mode 100644 index 000000000..806103759 --- /dev/null +++ b/frontend/src/components/v2/FormControl/index.tsx @@ -0,0 +1,2 @@ +export type { FormControlProps, FormHelperTextProps, FormLabelProps } from './FormControl'; +export { FormControl, FormHelperText, FormLabel } from './FormControl'; diff --git a/frontend/src/components/v2/index.tsx b/frontend/src/components/v2/index.tsx index 8b166a86e..71d91fc1e 100644 --- a/frontend/src/components/v2/index.tsx +++ b/frontend/src/components/v2/index.tsx @@ -1 +1,3 @@ export * from './Button'; +export * from './FormControl'; +export * from './Input'; From ad504fa84eb40080862b35e527e1485f5fde3b59 Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Fri, 20 Jan 2023 23:12:12 +0530 Subject: [PATCH 04/14] feat(ui) added select, spinner components --- frontend/.storybook/preview.js | 4 + frontend/package-lock.json | 116 ++++++++++-------- frontend/package.json | 2 +- .../components/v2/Select/Select.stories.tsx | 113 +++++++++++++++++ frontend/src/components/v2/Select/Select.tsx | 96 +++++++++++++++ frontend/src/components/v2/Select/index.tsx | 1 + .../src/components/v2/Spinner/Spinner.tsx | 42 +++++++ frontend/src/components/v2/Spinner/index.tsx | 1 + frontend/src/styles/globals.css | 6 + 9 files changed, 330 insertions(+), 51 deletions(-) create mode 100644 frontend/src/components/v2/Select/Select.stories.tsx create mode 100644 frontend/src/components/v2/Select/Select.tsx create mode 100644 frontend/src/components/v2/Select/index.tsx create mode 100644 frontend/src/components/v2/Spinner/Spinner.tsx create mode 100644 frontend/src/components/v2/Spinner/index.tsx diff --git a/frontend/.storybook/preview.js b/frontend/.storybook/preview.js index 4a19aa4c9..94133eee3 100644 --- a/frontend/.storybook/preview.js +++ b/frontend/.storybook/preview.js @@ -1,3 +1,4 @@ +import { themes } from '@storybook/theming'; import '../src/styles/globals.css'; export const parameters = { @@ -7,5 +8,8 @@ export const parameters = { color: /(background|color)$/i, date: /Date$/ } + }, + darkMode: { + dark: { ...themes.dark, appContentBg: '#0e1014', appBg: '#0e1014' } } }; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index cff1b80fb..042c69576 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -28,7 +28,6 @@ "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", @@ -96,7 +95,7 @@ "prettier": "^2.8.3", "storybook": "^7.0.0-beta.30", "storybook-dark-mode": "^2.0.5", - "tailwindcss": "^3.1.4", + "tailwindcss": "3.2", "typescript": "^4.9.3" } }, @@ -9301,22 +9300,6 @@ "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", @@ -12028,9 +12011,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -17203,12 +17186,12 @@ } }, "node_modules/postcss-nested": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", - "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", + "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.6" + "postcss-selector-parser": "^6.0.10" }, "engines": { "node": ">=12.0" @@ -20420,9 +20403,9 @@ "integrity": "sha512-+fflfPxvHFr81hTJpQ3MIwtqgvefHZFUHFiIHpVIRXvG/nX9+gu2P7JNlFu2bfDMJ+uHhi/pUgzaYacMoXv+Ww==" }, "node_modules/tailwindcss": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.1.7.tgz", - "integrity": "sha512-r7mgumZ3k0InfVPpGWcX8X/Ut4xBfv+1O/+C73ar/m01LxGVzWvPxF/w6xIUPEztrCoz7axfx0SMdh8FH8ZvRQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz", + "integrity": "sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==", "dev": true, "dependencies": { "arg": "^5.0.2", @@ -20431,18 +20414,19 @@ "detective": "^5.2.1", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.11", + "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "lilconfig": "^2.0.6", + "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", - "postcss": "^8.4.14", + "postcss": "^8.4.18", "postcss-import": "^14.1.0", "postcss-js": "^4.0.0", "postcss-load-config": "^3.1.4", - "postcss-nested": "5.0.6", + "postcss-nested": "6.0.0", "postcss-selector-parser": "^6.0.10", "postcss-value-parser": "^4.2.0", "quick-lru": "^5.1.1", @@ -20459,6 +20443,30 @@ "postcss": "^8.0.9" } }, + "node_modules/tailwindcss/node_modules/postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -28822,12 +28830,6 @@ } } }, - "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", @@ -30927,9 +30929,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -34637,12 +34639,12 @@ } }, "postcss-nested": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz", - "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz", + "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==", "dev": true, "requires": { - "postcss-selector-parser": "^6.0.6" + "postcss-selector-parser": "^6.0.10" } }, "postcss-selector-parser": { @@ -37007,9 +37009,9 @@ "integrity": "sha512-+fflfPxvHFr81hTJpQ3MIwtqgvefHZFUHFiIHpVIRXvG/nX9+gu2P7JNlFu2bfDMJ+uHhi/pUgzaYacMoXv+Ww==" }, "tailwindcss": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.1.7.tgz", - "integrity": "sha512-r7mgumZ3k0InfVPpGWcX8X/Ut4xBfv+1O/+C73ar/m01LxGVzWvPxF/w6xIUPEztrCoz7axfx0SMdh8FH8ZvRQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz", + "integrity": "sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ==", "dev": true, "requires": { "arg": "^5.0.2", @@ -37018,22 +37020,36 @@ "detective": "^5.2.1", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.2.11", + "fast-glob": "^3.2.12", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "lilconfig": "^2.0.6", + "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.0.0", - "postcss": "^8.4.14", + "postcss": "^8.4.18", "postcss-import": "^14.1.0", "postcss-js": "^4.0.0", "postcss-load-config": "^3.1.4", - "postcss-nested": "5.0.6", + "postcss-nested": "6.0.0", "postcss-selector-parser": "^6.0.10", "postcss-value-parser": "^4.2.0", "quick-lru": "^5.1.1", "resolve": "^1.22.1" + }, + "dependencies": { + "postcss": { + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + } } }, "tapable": { diff --git a/frontend/package.json b/frontend/package.json index 60b8eda85..43fa01a8b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -102,7 +102,7 @@ "prettier": "^2.8.3", "storybook": "^7.0.0-beta.30", "storybook-dark-mode": "^2.0.5", - "tailwindcss": "^3.1.4", + "tailwindcss": "3.2", "typescript": "^4.9.3" } } diff --git a/frontend/src/components/v2/Select/Select.stories.tsx b/frontend/src/components/v2/Select/Select.stories.tsx new file mode 100644 index 000000000..51f75ae53 --- /dev/null +++ b/frontend/src/components/v2/Select/Select.stories.tsx @@ -0,0 +1,113 @@ +import { useEffect, useState } from 'react'; +import { SelectProps } from '@radix-ui/react-select'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { Meta, StoryObj } from '@storybook/react'; + +import { Select, SelectItem } from './Select'; + +const meta: Meta = { + title: 'Components/Select', + component: Select, + tags: ['v2'], + argTypes: { + placeholder: { + defaultValue: 'Type something...' + } + } +}; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = { + render: (args) => ( +
+ +
+ ) +}; + +const Controlled = (args: SelectProps) => { + const [isOpen, setIsOpen] = useState(false); + const [selected, setSelected] = useState(''); + + return ( +
+ +
+ ); +}; + +export const Control: Story = { + render: (args) => +}; + +export const Disabled: Story = { + render: (args) => ( +
+ +
+ ) +}; + +export const Loading: Story = { + render: (args) => ( +
+ +
+ ) +}; + +const AsyncSelectOptions = () => { + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + // eslint-disable-next-line no-new + new Promise((resolve): void => { + setTimeout(() => { + setIsLoading(false); + resolve(); + }, 1000); + }); + }, []); + + return ( +
+ +
+ ); +}; + +export const Async: Story = { + render: (args) => +}; diff --git a/frontend/src/components/v2/Select/Select.tsx b/frontend/src/components/v2/Select/Select.tsx new file mode 100644 index 000000000..7b45a1848 --- /dev/null +++ b/frontend/src/components/v2/Select/Select.tsx @@ -0,0 +1,96 @@ +import { forwardRef, ReactNode } from 'react'; +import { faCheck, faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import * as SelectPrimitive from '@radix-ui/react-select'; +import { twMerge } from 'tailwind-merge'; + +import { Spinner } from '../Spinner'; + +type Props = { + children: ReactNode; + placeholder?: string; + className?: string; + isLoading?: boolean; +}; + +type SelectProps = SelectPrimitive.SelectProps & Props; + +export const Select = forwardRef( + ({ children, placeholder, className, isLoading, ...props }, ref): JSX.Element => { + return ( + + + + + + + + + + + + + + {isLoading ? ( +
+ + Loading... +
+ ) : ( + children + )} +
+ + + +
+
+
+ ); + } +); + +Select.displayName = 'Select'; + +type SelectItemProps = Omit & { + isDisabled?: boolean; + isSelected?: boolean; +}; + +export const SelectItem = forwardRef( + ({ children, className, isSelected, isDisabled, ...props }, forwardedRef) => { + return ( + + + + + {children} + + ); + } +); + +SelectItem.displayName = 'SelectItem'; diff --git a/frontend/src/components/v2/Select/index.tsx b/frontend/src/components/v2/Select/index.tsx new file mode 100644 index 000000000..d42fd8d59 --- /dev/null +++ b/frontend/src/components/v2/Select/index.tsx @@ -0,0 +1 @@ +export {Select} from './Select' \ No newline at end of file diff --git a/frontend/src/components/v2/Spinner/Spinner.tsx b/frontend/src/components/v2/Spinner/Spinner.tsx new file mode 100644 index 000000000..0fcbf1ce0 --- /dev/null +++ b/frontend/src/components/v2/Spinner/Spinner.tsx @@ -0,0 +1,42 @@ +import { twMerge } from 'tailwind-merge'; + +type SizeOptions = 'xs' | 'sm' | 'md' | 'lg'; + +type Props = { + className?: string; + size?: SizeOptions; +}; + +const sizeChart: Record = { + xs: 'w-4 h-4', + sm: 'w-6 h-6', + md: 'w-8 h-8', + lg: 'w-12 h-12' +}; + +export const Spinner = ({ className, size = 'md' }: Props): JSX.Element => { + return ( +
+ +
+ ); +}; diff --git a/frontend/src/components/v2/Spinner/index.tsx b/frontend/src/components/v2/Spinner/index.tsx new file mode 100644 index 000000000..1ce6cab2e --- /dev/null +++ b/frontend/src/components/v2/Spinner/index.tsx @@ -0,0 +1 @@ +export { Spinner } from './Spinner'; diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css index b5c61c956..18814e224 100644 --- a/frontend/src/styles/globals.css +++ b/frontend/src/styles/globals.css @@ -1,3 +1,9 @@ @tailwind base; @tailwind components; @tailwind utilities; + +@layer components { + .select[data-placeholder] { + color: theme("colors.gray.500") + } +} From f859bf528ef959d76cc2b6f6b59a4cfd6cf35403 Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Sat, 21 Jan 2023 12:55:43 +0530 Subject: [PATCH 05/14] feat(ui): added icon button component, updated secondary button style and added select to barrel export --- frontend/src/components/v2/Button/Button.tsx | 12 +- .../v2/IconButton/IconButton.stories.tsx | 60 ++++++++ .../components/v2/IconButton/IconButton.tsx | 131 ++++++++++++++++++ .../src/components/v2/IconButton/index.tsx | 2 + frontend/src/components/v2/Select/Select.tsx | 4 +- frontend/src/components/v2/Select/index.tsx | 3 +- frontend/src/components/v2/index.tsx | 2 + 7 files changed, 205 insertions(+), 9 deletions(-) create mode 100644 frontend/src/components/v2/IconButton/IconButton.stories.tsx create mode 100644 frontend/src/components/v2/IconButton/IconButton.tsx create mode 100644 frontend/src/components/v2/IconButton/index.tsx diff --git a/frontend/src/components/v2/Button/Button.tsx b/frontend/src/components/v2/Button/Button.tsx index f00d1d0a3..c0894f261 100644 --- a/frontend/src/components/v2/Button/Button.tsx +++ b/frontend/src/components/v2/Button/Button.tsx @@ -24,16 +24,16 @@ const buttonVariants = cva( variants: { colorSchema: { primary: ['bg-primary', 'text-black', 'border-primary hover:bg-opacity-80'], - secondary: ['bg-mineshaft-200', 'text-white', 'border-mineshaft-200'], + secondary: ['bg-mineshaft', 'text-gray-300', 'border-mineshaft hover:bg-opacity-80'], danger: ['bg-red', 'text-white', 'border-red'] }, variant: { solid: '', - outline: ['bg-transparent', 'border', 'border-solid'], + outline: ['bg-transparent', 'border-2', 'border-solid'], plain: '' }, isDisabled: { - true: 'bg-opacity-70', + true: 'bg-opacity-70 cursor-not-allowed', false: '' }, isFullWidth: { @@ -60,7 +60,7 @@ const buttonVariants = cva( { colorSchema: 'secondary', variant: 'outline', - className: 'text-mineshaft-200 hover:bg-mineshaft-400 hover:text-white' + className: 'hover:bg-mineshaft' }, { colorSchema: 'danger', @@ -75,7 +75,7 @@ const buttonVariants = cva( { colorSchema: 'secondary', variant: 'plain', - className: 'text-mineshaft-400' + className: 'text-mineshaft' }, { colorSchema: 'danger', @@ -139,7 +139,7 @@ export const Button = forwardRef( src="/images/loading/loadingblack.gif" width={36} alt="loading animation" - className="rounded-xl absolute" + className="absolute rounded-xl" /> )} = { + title: 'Components/IconButton', + component: IconButton, + tags: ['v2'], + argTypes: { + isRounded: { + defaultValue: true, + type: 'boolean' + }, + ariaLabel: { + defaultValue: 'Some buttons...' + } + } +}; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args +export const Primary: Story = { + args: { + children: + } +}; + +export const Secondary: Story = { + args: { + children: , + colorSchema: 'secondary', + variant: 'outline' + } +}; + +export const Danger: Story = { + args: { + children: , + colorSchema: 'danger', + variant: 'solid' + } +}; + +export const Plain: Story = { + args: { + children: , + variant: 'plain' + } +}; + +export const Disabled: Story = { + args: { + children: , + disabled: true + } +}; diff --git a/frontend/src/components/v2/IconButton/IconButton.tsx b/frontend/src/components/v2/IconButton/IconButton.tsx new file mode 100644 index 000000000..9790d9d7b --- /dev/null +++ b/frontend/src/components/v2/IconButton/IconButton.tsx @@ -0,0 +1,131 @@ +import { ButtonHTMLAttributes, forwardRef, ReactNode } from 'react'; +import { cva, VariantProps } from 'cva'; +import { twMerge } from 'tailwind-merge'; + +type Props = { + children: ReactNode; + // This is kept as required because by accessibility convention and eslint + // when button doesn't have text an aria-label needs to be passed + ariaLabel: string; + isDisabled?: boolean; +}; + +const iconButtonVariants = 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', 'text-gray-300', 'border-mineshaft hover:bg-opacity-80'], + danger: ['bg-red', 'text-white', 'border-red'] + }, + variant: { + solid: '', + outline: ['bg-transparent', 'border-2', 'border-solid'], + plain: '' + }, + isDisabled: { + true: 'bg-opacity-70 cursor-not-allowed', + false: '' + }, + isRounded: { + true: 'rounded-md', + false: '' + }, + size: { + xs: ['text-xs', 'py-1', 'px-2'], + sm: ['text-sm', 'py-2', 'px-3'], + md: ['text-md', 'py-3', 'px-4'], + lg: ['text-lg', 'py-3', 'px-6'] + } + }, + compoundVariants: [ + { + colorSchema: 'primary', + variant: 'outline', + className: 'text-primary hover:bg-primary hover:text-black' + }, + { + colorSchema: 'secondary', + variant: 'outline', + className: 'hover:bg-mineshaft' + }, + { + 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' + }, + { + colorSchema: 'danger', + variant: 'plain', + className: 'text-red' + }, + { + colorSchema: ['danger', 'primary', 'secondary'], + variant: ['plain'], + className: 'bg-transparent py-1 px-1' + } + ] + } +); + +export type IconButtonProps = Omit, 'aria-label'> & + VariantProps & + Props; + +export const IconButton = forwardRef( + ( + { + children, + ariaLabel, + isDisabled = false, + className, + size = 'md', + variant = 'solid', + isRounded = true, + colorSchema = 'primary', + ...props + }, + ref + ): JSX.Element => ( + + ) +); + +IconButton.displayName = 'IconButton'; diff --git a/frontend/src/components/v2/IconButton/index.tsx b/frontend/src/components/v2/IconButton/index.tsx new file mode 100644 index 000000000..e920c3d6a --- /dev/null +++ b/frontend/src/components/v2/IconButton/index.tsx @@ -0,0 +1,2 @@ +export type { IconButtonProps } from './IconButton'; +export { IconButton } from './IconButton'; diff --git a/frontend/src/components/v2/Select/Select.tsx b/frontend/src/components/v2/Select/Select.tsx index 7b45a1848..3fee8b7c7 100644 --- a/frontend/src/components/v2/Select/Select.tsx +++ b/frontend/src/components/v2/Select/Select.tsx @@ -13,7 +13,7 @@ type Props = { isLoading?: boolean; }; -type SelectProps = SelectPrimitive.SelectProps & Props; +export type SelectProps = SelectPrimitive.SelectProps & Props; export const Select = forwardRef( ({ children, placeholder, className, isLoading, ...props }, ref): JSX.Element => { @@ -64,7 +64,7 @@ export const Select = forwardRef( Select.displayName = 'Select'; -type SelectItemProps = Omit & { +export type SelectItemProps = Omit & { isDisabled?: boolean; isSelected?: boolean; }; diff --git a/frontend/src/components/v2/Select/index.tsx b/frontend/src/components/v2/Select/index.tsx index d42fd8d59..269099af6 100644 --- a/frontend/src/components/v2/Select/index.tsx +++ b/frontend/src/components/v2/Select/index.tsx @@ -1 +1,2 @@ -export {Select} from './Select' \ No newline at end of file +export type { SelectItemProps, SelectProps } from './Select'; +export { Select, SelectItem } from './Select'; diff --git a/frontend/src/components/v2/index.tsx b/frontend/src/components/v2/index.tsx index 71d91fc1e..0d6c14ea2 100644 --- a/frontend/src/components/v2/index.tsx +++ b/frontend/src/components/v2/index.tsx @@ -1,3 +1,5 @@ export * from './Button'; export * from './FormControl'; +export * from './IconButton'; export * from './Input'; +export * from './Select'; From 606a5e5317385ac5edc7a533fec2c7b98ab4237c Mon Sep 17 00:00:00 2001 From: akhilmhdh Date: Sat, 21 Jan 2023 20:49:46 +0530 Subject: [PATCH 06/14] feat(ui): added card component --- .../src/components/v2/Card/Card.stories.tsx | 40 ++++++++++++ frontend/src/components/v2/Card/Card.tsx | 62 +++++++++++++++++++ frontend/src/components/v2/Card/index.tsx | 2 + frontend/src/components/v2/index.tsx | 1 + 4 files changed, 105 insertions(+) create mode 100644 frontend/src/components/v2/Card/Card.stories.tsx create mode 100644 frontend/src/components/v2/Card/Card.tsx create mode 100644 frontend/src/components/v2/Card/index.tsx diff --git a/frontend/src/components/v2/Card/Card.stories.tsx b/frontend/src/components/v2/Card/Card.stories.tsx new file mode 100644 index 000000000..e21625cb4 --- /dev/null +++ b/frontend/src/components/v2/Card/Card.stories.tsx @@ -0,0 +1,40 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Card, CardBody, CardFooter, CardProps, CardTitle } from './Card'; + +const meta: Meta = { + title: 'Components/Card', + component: Card, + tags: ['v2'], + argTypes: { + isRounded: { + type: 'boolean', + defaultValue: true + } + } +}; + +export default meta; +type Story = StoryObj; + +// More on writing stories with args: https://storybook.js.org/docs/7.0/react/writing-stories/args +const Template = (args: CardProps) => ( +
+ + Title + Content + Footer + +
+); + +export const Basic: Story = { + render: (args) =>