From c0fad38c35d4ecd2d481322c26014732102aefbe Mon Sep 17 00:00:00 2001 From: Radu-Cristian Popa Date: Thu, 21 Dec 2023 09:54:59 +0200 Subject: [PATCH] Add initial button component --- package.json | 5 +- pnpm-lock.yaml | 73 +++++++++++++++++++++++++++++- src/components/button.tsx | 58 ++++++++++++++++++++++++ src/components/icons.tsx | 19 ++++++++ src/components/loading-spinner.tsx | 22 +++++++++ src/utils/cn.ts | 6 +++ tsconfig.json | 5 +- 7 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 src/components/button.tsx create mode 100644 src/components/icons.tsx create mode 100644 src/components/loading-spinner.tsx create mode 100644 src/utils/cn.ts diff --git a/package.json b/package.json index 3c5a2298..40dbea08 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,12 @@ "lint:prettier": "prettier \"**/*.(md|json|yml)\" --ignore-path .gitignore --check", "lint:type": "tsc --noEmit", "local-signatures": "pnpm tsx --watch ./local-signatures/index.ts", - "test": "jest --maxWorkers=2", + "test": "jest --maxWorkers=2 --passWithNoTests", "ci:test": "run-s \" test --ci --reporters=\"default\" --reporters=\"github-actions\" \"" }, "dependencies": { "axios": "^1.5.1", + "class-variance-authority": "^0.7.0", "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^11.0.0", "core-js": "^3.32.1", @@ -36,6 +37,7 @@ "sass": "^1.66.1", "sass-loader": "^13.3.2", "style-loader": "^3.3.3", + "tailwind-merge": "^2.1.0", "terser-webpack-plugin": "^5.3.9", "uuid": "^9.0.1", "webextension-polyfill": "^0.10.0", @@ -94,6 +96,7 @@ "postcss-loader": "^7.3.3", "prettier": "^3.0.3", "tailwindcss": "^3.4.0", + "ts-jest": "^29.1.1", "ts-loader": "^9.4.4", "ts-node": "^10.9.2", "tsx": "^4.6.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 850f8293..e6e04cd0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ dependencies: axios: specifier: ^1.5.1 version: 1.6.2 + class-variance-authority: + specifier: ^0.7.0 + version: 0.7.0 clean-webpack-plugin: specifier: ^4.0.0 version: 4.0.0(webpack@5.89.0) @@ -47,6 +50,9 @@ dependencies: style-loader: specifier: ^3.3.3 version: 3.3.3(webpack@5.89.0) + tailwind-merge: + specifier: ^2.1.0 + version: 2.1.0 terser-webpack-plugin: specifier: ^5.3.9 version: 5.3.9(webpack@5.89.0) @@ -217,6 +223,9 @@ devDependencies: tailwindcss: specifier: ^3.4.0 version: 3.4.0(ts-node@10.9.2) + ts-jest: + specifier: ^29.1.1 + version: 29.1.1(@babel/core@7.23.5)(jest@29.7.0)(typescript@5.3.3) ts-loader: specifier: ^9.4.4 version: 9.5.1(typescript@5.3.3)(webpack@5.89.0) @@ -551,7 +560,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.0 - dev: true /@babel/template@7.22.15: resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} @@ -2461,6 +2469,13 @@ packages: node-releases: 2.0.14 update-browserslist-db: 1.0.13(browserslist@4.22.2) + /bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + dependencies: + fast-json-stable-stringify: 2.1.0 + dev: true + /bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} dependencies: @@ -2593,6 +2608,12 @@ packages: resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} dev: true + /class-variance-authority@0.7.0: + resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} + dependencies: + clsx: 2.0.0 + dev: false + /clean-css@5.3.3: resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} engines: {node: '>= 10.0'} @@ -2627,6 +2648,11 @@ packages: kind-of: 6.0.3 shallow-clone: 3.0.1 + /clsx@2.0.0: + resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} + engines: {node: '>=6'} + dev: false + /co-body@6.1.0: resolution: {integrity: sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==} dependencies: @@ -5558,6 +5584,10 @@ packages: p-locate: 5.0.0 dev: true + /lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + dev: true + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -6340,7 +6370,6 @@ packages: /regenerator-runtime@0.14.0: resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} - dev: true /regexp.prototype.flags@1.5.1: resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} @@ -6904,6 +6933,12 @@ packages: tslib: 2.6.2 dev: true + /tailwind-merge@2.1.0: + resolution: {integrity: sha512-l11VvI4nSwW7MtLSLYT4ldidDEUwQAMWuSHk7l4zcXZDgnCRa0V3OdCwFfM7DCzakVXMNRwAeje9maFFXT71dQ==} + dependencies: + '@babel/runtime': 7.23.5 + dev: false + /tailwindcss@3.4.0(ts-node@10.9.2): resolution: {integrity: sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==} engines: {node: '>=14.0.0'} @@ -7077,6 +7112,40 @@ packages: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} dev: true + /ts-jest@29.1.1(@babel/core@7.23.5)(jest@29.7.0)(typescript@5.3.3): + resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + dependencies: + '@babel/core': 7.23.5 + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@20.10.4)(ts-node@10.9.2) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.5.4 + typescript: 5.3.3 + yargs-parser: 21.1.1 + dev: true + /ts-loader@9.5.1(typescript@5.3.3)(webpack@5.89.0): resolution: {integrity: sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==} engines: {node: '>=12.0.0'} diff --git a/src/components/button.tsx b/src/components/button.tsx new file mode 100644 index 00000000..ba4fbf38 --- /dev/null +++ b/src/components/button.tsx @@ -0,0 +1,58 @@ +import { type VariantProps, cva } from 'class-variance-authority' +import React, { forwardRef } from 'react' + +import { LoadingSpinner } from '@/components/loading-spinner' +import { cn } from '@/utils/cn' + +const buttonVariants = cva( + [ + 'relative inline-flex items-center justify-center whitespace-nowrap rounded-xl font-semibold', + 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500', + 'disabled:pointer-events-none disabled:select-none disabled:opacity-50', + ], + + { + variants: { + variant: { + default: 'bg-button-base text-white hover:bg-button-base-hover active:bg-red-500', + }, + size: { + default: 'py-4 px-6 font-medium', + }, + fullWidth: { + true: 'w-full', + }, + loading: { + true: 'text-transparent', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + }, +) + +export interface ButtonProps + extends VariantProps, + React.ButtonHTMLAttributes { + loading?: boolean + ['aria-label']: string +} + +export const Button = forwardRef(function Button( + { variant, size, fullWidth, loading, className, type = 'button', children, ...props }, + ref, +) { + return ( + + ) +}) diff --git a/src/components/icons.tsx b/src/components/icons.tsx new file mode 100644 index 00000000..555791c8 --- /dev/null +++ b/src/components/icons.tsx @@ -0,0 +1,19 @@ +import React from 'react' + +export const Spinner = (props: React.SVGProps) => { + return ( + + ) +} diff --git a/src/components/loading-spinner.tsx b/src/components/loading-spinner.tsx new file mode 100644 index 00000000..c42a723c --- /dev/null +++ b/src/components/loading-spinner.tsx @@ -0,0 +1,22 @@ +import { type VariantProps, cva } from 'class-variance-authority' +import React from 'react' + +import { Spinner } from '@/components/icons' + +const loadingSpinnerStyles = cva('animate-spin text-white', { + variants: { + variant: { + md: 'h-4 w-4', + lg: 'h-6 w-6', + }, + }, + defaultVariants: { + variant: 'lg', + }, +}) + +export type LoadingIndicatorProps = VariantProps + +export const LoadingSpinner = ({ variant }: LoadingIndicatorProps) => { + return +} diff --git a/src/utils/cn.ts b/src/utils/cn.ts new file mode 100644 index 00000000..ef859784 --- /dev/null +++ b/src/utils/cn.ts @@ -0,0 +1,6 @@ +import { cx, CxOptions } from 'class-variance-authority' +import { twMerge } from 'tailwind-merge' + +export function cn(...inputs: CxOptions) { + return twMerge(cx(inputs)) +} diff --git a/tsconfig.json b/tsconfig.json index f8fa4ff6..a568303a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,9 +23,8 @@ "@/components/*": ["./components/*"], "@/types/*": ["./types/*"], "@/hooks/*": ["./hooks/*"] - }, - "typeRoots": ["node_modules/@types", "./src/types"] + } }, - "include": ["src/**/*", "local-signature/**/*"], + "include": ["./src/**/*", "local-signature/**/*", "./jest.config.ts", "./jest.setup.ts"], "exclude": ["dist", "dev", "temp"] }