From 7bfc01820b4b1e4cd06ad8c8bb1c357ff90e5c4a Mon Sep 17 00:00:00 2001 From: Damir Vazgird Date: Sun, 3 Mar 2024 19:48:52 -0800 Subject: [PATCH] refactor library to use tailwind-variants --- .vscode /settings.json | 20 +- README.md | 8 +- package.json | 60 +- src/components/Accordion/index.tsx | 84 +- src/components/Avatar/index.tsx | 31 +- src/components/Button/index.tsx | 54 +- src/components/Card/index.tsx | 83 +- src/components/Carousel/index.tsx | 141 +- src/components/Checkbox/index.tsx | 36 +- src/components/Dialog/index.tsx | 102 +- src/components/DropdownMenu/index.tsx | 259 ++-- src/components/Form/index.tsx | 47 +- src/components/Input/index.tsx | 15 +- src/components/Label/index.tsx | 21 +- src/components/Menubar/index.tsx | 269 ++-- .../NavigationMenu/NavigationMenu.stories.tsx | 21 +- src/components/NavigationMenu/index.tsx | 119 +- src/components/Pagination/index.tsx | 109 +- src/components/Popover/index.tsx | 14 +- src/components/Progress/index.tsx | 32 +- src/components/RadioGroup/index.tsx | 44 +- src/components/Select/index.tsx | 217 +-- src/components/Separator/index.tsx | 15 +- src/components/Switch/index.tsx | 36 +- src/components/Tabs/index.tsx | 50 +- src/components/Textarea/index.tsx | 14 +- src/components/Tooltip/index.tsx | 17 +- src/index.ts | 1 - src/utils/index.ts | 4 - yarn.lock | 1207 +++++++++-------- 30 files changed, 1809 insertions(+), 1321 deletions(-) delete mode 100644 src/utils/index.ts diff --git a/.vscode /settings.json b/.vscode /settings.json index 7f82d74..9123d1b 100644 --- a/.vscode /settings.json +++ b/.vscode /settings.json @@ -2,19 +2,21 @@ "editor.tabSize": 2, "editor.detectIndentation": false, "search.exclude": { - "package-lock.json": true + "yarn-lock.json": true }, "editor.defaultFormatter": "dbaeumer.vscode-eslint", - "editor.formatOnSave": false, - "editor.codeActionsOnSave": { - "source.addMissingImports": true, - "source.fixAll.eslint": true - }, - // Multiple language settings for json and jsonc files - "[json][jsonc]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": [ + "source.addMissingImports", + "source.fixAll.eslint" + ], + "[json][jsonc][yaml]": { "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]], + "tailwindCSS.experimental.classRegex": [ + ["tv\\((([^()]*|\\([^()]*\\))*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] + ], + "prettier.ignorePath": ".gitignore", "typescript.tsdk": "./node_modules/typescript/lib" } \ No newline at end of file diff --git a/README.md b/README.md index a148b2d..9f20e7e 100644 --- a/README.md +++ b/README.md @@ -16,16 +16,16 @@ The reason these are required is because most of these components are created us Here's a list of all the peer dependencies this package relies on: -- `class-variance-authority@^0.7.0` -- `clsx@^2.1.0` - `lucide-react@0.312.0` - `react@^18.2.0` - `react-dom@^18.2.0"` - `react-hook-form@^7.49.3` -- `tailwind-merge@^2.2.0` +- `tailwind-variants@^0.2.0` - `tailwindcss-animate@^1.0.7` - `tailwindcss@^3.4.1` +**Note: As of v1.1.0, `clsx`, `cva`, and `twMerge` are replaced by `tv`.** + ### Installing Dependencies You can use whatever package management library, but I'll be using yarn for this example. @@ -39,7 +39,7 @@ yarn add chad-ui #### 2. Install other necessary dependencies: ``` -yarn add class-variance-authority clsx lucide-react react react-dom react-hook-form tailwind-merge +yarn add lucide-react react react-dom react-hook-form tailwind-variants ``` These can be added as dev dependencies: diff --git a/package.json b/package.json index a818851..5f8315e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "chad-ui", - "version": "1.0.5", + "version": "1.1.0", "description": "A React UI component library bundled with Rollup.js to commonJs, ES6 Modules, Storybook, Tailwind CSS, and Shadcn UI.", "author": "Damir Vazgird", "license": "MIT", @@ -39,13 +39,11 @@ "ui:diff": "npx shadcn-ui diff" }, "peerDependencies": { - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", "lucide-react": "^0.321.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.49.3", - "tailwind-merge": "^2.2.0", + "tailwind-variants": "^0.2.0", "tailwindcss-animate": "^1.0.7" }, "devDependencies": { @@ -72,31 +70,29 @@ "@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-typescript": "^11.1.6", "@rollup/plugin-url": "^8.0.2", - "@storybook/addon-docs": "^7.6.12", - "@storybook/addon-essentials": "^7.6.12", - "@storybook/addon-interactions": "^7.6.12", - "@storybook/addon-links": "^7.6.12", + "@storybook/addon-docs": "^7.6.17", + "@storybook/addon-essentials": "^7.6.17", + "@storybook/addon-interactions": "^7.6.17", + "@storybook/addon-links": "^7.6.17", "@storybook/addon-onboarding": "^1.0.11", "@storybook/addon-styling": "^1.3.7", - "@storybook/addon-themes": "^7.6.12", - "@storybook/blocks": "^7.6.12", - "@storybook/react": "^7.6.12", - "@storybook/react-vite": "^7.6.12", + "@storybook/addon-themes": "^7.6.17", + "@storybook/blocks": "^7.6.17", + "@storybook/react": "^7.6.17", + "@storybook/react-vite": "^7.6.17", "@storybook/testing-library": "^0.2.2", "@svgr/rollup": "^8.1.0", - "@testing-library/jest-dom": "^6.4.1", + "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^14.2.1", "@testing-library/user-event": "^14.5.2", - "@types/node": "^20.11.16", - "@types/react": "^18.2.52", - "@types/react-dom": "^18.2.18", - "@typescript-eslint/eslint-plugin": "^6.20.0", - "@typescript-eslint/parser": "^6.20.0", - "autoprefixer": "^10.4.17", - "class-variance-authority": "^0.7.0", - "clsx": "^2.1.0", + "@types/node": "^20.11.24", + "@types/react": "^18.2.61", + "@types/react-dom": "^18.2.19", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "autoprefixer": "^10.4.18", "embla-carousel-react": "^8.0.0-rc21", - "eslint": "^8.56.0", + "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.6.1", "eslint-plugin-import": "^2.29.1", @@ -105,32 +101,32 @@ "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", - "eslint-plugin-storybook": "^0.6.15", + "eslint-plugin-storybook": "^0.8.0", "gh-pages": "^6.1.1", "jsdom": "^24.0.0", - "lucide-react": "^0.321.0", - "postcss": "^8.4.33", - "prettier": "3.2.4", + "lucide-react": "^0.344.0", + "postcss": "^8.4.35", + "prettier": "3.2.5", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-hook-form": "^7.50.0", + "react-hook-form": "^7.51.0", "regenerator-runtime": "^0.14.1", "rimraf": "^5.0.5", - "rollup": "^4.9.6", + "rollup": "^4.12.0", "rollup-plugin-copy": "^3.5.0", "rollup-plugin-dts": "^6.1.0", "rollup-plugin-peer-deps-external": "^2.2.4", "rollup-plugin-postcss": "^4.0.2", "rollup-plugin-terser": "^7.0.2", - "storybook": "^7.6.12", - "tailwind-merge": "^2.2.1", + "storybook": "^7.6.17", + "tailwind-variants": "^0.2.0", "tailwindcss": "^3.4.1", "tailwindcss-animate": "^1.0.7", "tslib": "^2.6.2", "typescript": "^5.3.3", - "vite": "^5.0.12", + "vite": "^5.1.4", "vite-plugin-svgr": "^4.2.0", - "vitest": "^1.2.2", + "vitest": "^1.3.1", "zod": "^3.22.4" }, "packageManager": "yarn@3.6.3" diff --git a/src/components/Accordion/index.tsx b/src/components/Accordion/index.tsx index 90187a8..df03e94 100644 --- a/src/components/Accordion/index.tsx +++ b/src/components/Accordion/index.tsx @@ -3,52 +3,82 @@ import * as React from 'react'; import * as AccordionPrimitive from '@radix-ui/react-accordion'; import { ChevronDown } from 'lucide-react'; +import { tv } from 'tailwind-variants'; -import { cn } from '@/utils'; +// Accordion const Accordion = AccordionPrimitive.Root; +// AccordionItem + +const accordionItem = tv({ base: 'border-b' }); + const AccordionItem = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); AccordionItem.displayName = 'AccordionItem'; +// AccordionTigger + +const accordionTrigger = tv({ + slots: { + base: 'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180', + wrapper: 'flex', + icon: 'h-4 w-4 shrink-0 transition-transform duration-200', + }, +}); + const AccordionTrigger = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - - svg]:rotate-180', - className, - )} - {...props} - > - {children} - - - -)); +>(({ className, children, ...props }, ref) => { + const { base, wrapper, icon } = accordionTrigger(); + + return ( + + + {children} + + + + ); +}); AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; +// AccordionContent + +const accordionContent = tv({ + slots: { + base: 'pb-4 pt-0', + wrapper: + 'overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down', + }, +}); + const AccordionContent = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => ( - -
{children}
-
-)); +>(({ className, children, ...props }, ref) => { + const { base, wrapper } = accordionContent(); + + return ( + +
{children}
+
+ ); +}); AccordionContent.displayName = AccordionPrimitive.Content.displayName; -export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; +export { + Accordion, + AccordionItem, + AccordionTrigger, + AccordionContent, + accordionItem, + accordionTrigger, + accordionContent, +}; diff --git a/src/components/Avatar/index.tsx b/src/components/Avatar/index.tsx index 47d6243..2dc5817 100644 --- a/src/components/Avatar/index.tsx +++ b/src/components/Avatar/index.tsx @@ -2,38 +2,41 @@ import * as React from 'react'; import * as AvatarPrimitive from '@radix-ui/react-avatar'; +import { tv } from 'tailwind-variants'; -import { cn } from '@/utils'; +// Avatar + +const avatar = tv({ + base: 'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full', +}); const Avatar = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( - -)); +>(({ className, ...props }, ref) => ); Avatar.displayName = AvatarPrimitive.Root.displayName; +// AvatarImage + +const avatarImage = tv({ base: 'aspect-square h-full w-full' }); + const AvatarImage = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); AvatarImage.displayName = AvatarPrimitive.Image.displayName; +// AvatarFallback + +const avatarFallback = tv({ base: 'flex h-full w-full items-center justify-center rounded-full bg-muted' }); + const AvatarFallback = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )); AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index c42e83b..19fa8e1 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -1,38 +1,32 @@ import { Slot } from '@radix-ui/react-slot'; -import { cva, type VariantProps } from 'class-variance-authority'; +import { tv, type VariantProps } from 'tailwind-variants'; import * as React from 'react'; -import { cn } from '@/utils'; - -const buttonVariants = cva( - 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', - { - variants: { - variant: { - default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', - destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', - ghost: 'hover:bg-accent hover:text-accent-foreground', - link: 'text-primary underline-offset-4 hover:underline', - outline: 'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground', - secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', - }, - size: { - default: 'h-9 px-4 py-2', - sm: 'h-8 rounded-md px-3 text-xs', - lg: 'h-10 rounded-md px-8', - icon: 'h-9 w-9', - }, +const button = tv({ + base: 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50', + variants: { + variant: { + default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90', + destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + outline: 'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', }, - defaultVariants: { - variant: 'default', - size: 'default', + size: { + default: 'h-9 px-4 py-2', + sm: 'h-8 rounded-md px-3 text-xs', + lg: 'h-10 rounded-md px-8', + icon: 'h-9 w-9', }, }, -); + defaultVariants: { + variant: 'default', + size: 'default', + }, +}); -export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { +export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps { asChild?: boolean; } @@ -40,9 +34,9 @@ const Button = React.forwardRef( ({ className, variant, size, asChild = false, ...props }, ref) => { const Component = asChild ? Slot : 'button'; - return ; + return ; }, ); Button.displayName = 'Button'; -export { Button, buttonVariants }; +export { Button, button }; diff --git a/src/components/Card/index.tsx b/src/components/Card/index.tsx index 8dd210b..655e888 100644 --- a/src/components/Card/index.tsx +++ b/src/components/Card/index.tsx @@ -1,9 +1,10 @@ -import { cva, type VariantProps } from 'class-variance-authority'; +import { tv, type VariantProps } from 'tailwind-variants'; import * as React from 'react'; -import { cn } from '@/utils'; +// Card -const cardVariants = cva('border', { +const card = tv({ + base: 'border', variants: { variant: { default: 'bg-card text-card-foreground', @@ -23,42 +24,70 @@ const cardVariants = cva('border', { }, }); -export interface CardProps extends React.HTMLAttributes, VariantProps {} +export interface CardProps extends React.HTMLAttributes, VariantProps {} const Card = React.forwardRef(({ className, variant, size, ...props }, ref) => ( -
+
)); Card.displayName = 'Card'; -const CardHeader = React.forwardRef>( - ({ className, ...props }, ref) => ( -
- ), -); +// CardHeader + +const cardHeader = tv({ base: 'flex flex-col gap-2 p-6' }); + +export interface CardHeaderProps extends React.HTMLAttributes, VariantProps {} + +const CardHeader = React.forwardRef(({ className, ...props }, ref) => ( +
+)); CardHeader.displayName = 'CardHeader'; -const CardTitle = React.forwardRef>( - ({ children, className, ...props }, ref) => ( -

- {children} -

- ), -); +// CardTitle + +const cardTitle = tv({ base: 'text-2xl font-semibold leading-none tracking-tight' }); + +export interface CardTitleProps extends React.HTMLAttributes, VariantProps {} + +const CardTitle = React.forwardRef(({ children, className, ...props }, ref) => ( +

+ {children} +

+)); CardTitle.displayName = 'CardTitle'; -const CardDescription = React.forwardRef>( - ({ className, ...props }, ref) =>

, -); +// Card Description + +const cardDescription = tv({ base: 'text-sm' }); + +export interface CardDescriptionProps + extends React.HTMLAttributes, + VariantProps {} + +const CardDescription = React.forwardRef(({ className, ...props }, ref) => ( +

+)); CardDescription.displayName = 'CardDescription'; -const CardContent = React.forwardRef>( - ({ className, ...props }, ref) =>

, -); +// CardContent + +const cardContent = tv({ base: 'px-6 py-0' }); + +export interface CardContentProps extends React.HTMLAttributes, VariantProps {} + +const CardContent = React.forwardRef(({ className, ...props }, ref) => ( +
+)); CardContent.displayName = 'CardContent'; -const CardFooter = React.forwardRef>( - ({ className, ...props }, ref) =>