From 5ca4caa0bab8bd4c55b5fe0b7344488b77c82cce Mon Sep 17 00:00:00 2001 From: arraysid Date: Tue, 27 May 2025 03:37:02 +0700 Subject: [PATCH] feat: update dependencies and enhance button and form components with new variants and context --- libs/shadcn-ui/package-lock.json | 123 +++++++++++++++++++- libs/shadcn-ui/package.json | 8 +- libs/shadcn-ui/src/atoms/button.tsx | 20 ++-- libs/shadcn-ui/src/atoms/form.tsx | 167 ++++++++++++++++++++++++++++ libs/shadcn-ui/src/atoms/index.ts | 3 + libs/shadcn-ui/src/atoms/input.tsx | 23 ++++ libs/shadcn-ui/src/atoms/label.tsx | 23 ++++ 7 files changed, 349 insertions(+), 18 deletions(-) create mode 100644 libs/shadcn-ui/src/atoms/form.tsx create mode 100644 libs/shadcn-ui/src/atoms/input.tsx create mode 100644 libs/shadcn-ui/src/atoms/label.tsx diff --git a/libs/shadcn-ui/package-lock.json b/libs/shadcn-ui/package-lock.json index 652dc97..50115cc 100644 --- a/libs/shadcn-ui/package-lock.json +++ b/libs/shadcn-ui/package-lock.json @@ -8,14 +8,18 @@ "name": "@imphnen-frontend-service/shadcn-ui", "version": "0.0.1", "dependencies": { - "@radix-ui/react-slot": "^1.2.0", + "@hookform/resolvers": "^5.0.1", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-slot": "^1.2.3", "@tailwindcss/vite": "^4.1.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.503.0", + "react-hook-form": "^7.56.4", "react-icons": "^5.5.0", "tailwind-merge": "^3.2.0", - "tailwindcss": "^4.1.4" + "tailwindcss": "^4.1.4", + "zod": "^3.25.28" }, "devDependencies": { "tw-animate-css": "^1.2.8" @@ -446,6 +450,18 @@ "node": ">=18" } }, + "node_modules/@hookform/resolvers": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-5.0.1.tgz", + "integrity": "sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==", + "license": "MIT", + "dependencies": { + "@standard-schema/utils": "^0.3.0" + }, + "peerDependencies": { + "react-hook-form": "^7.55.0" + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", @@ -461,10 +477,56 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", - "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -759,6 +821,12 @@ ], "peer": true }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@tailwindcss/node": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.4.tgz", @@ -1464,6 +1532,35 @@ "node": ">=0.10.0" } }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-hook-form": { + "version": "7.56.4", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.56.4.tgz", + "integrity": "sha512-Rob7Ftz2vyZ/ZGsQZPaRdIefkgOSrQSPXfqBdvOPwJfoGnjwRJUs7EM7Kc1mcoDv3NOtqBzPGbcMB8CGn9CKgw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", @@ -1513,6 +1610,13 @@ "fsevents": "~2.3.2" } }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT", + "peer": true + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1649,6 +1753,15 @@ "optional": true } } + }, + "node_modules/zod": { + "version": "3.25.28", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.28.tgz", + "integrity": "sha512-/nt/67WYKnr5by3YS7LroZJbtcCBurDKKPBPWWzaxvVCGuG/NOsiKkrjoOhI8mJ+SQUXEbUzeB3S+6XDUEEj7Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/libs/shadcn-ui/package.json b/libs/shadcn-ui/package.json index 374ee3c..7616d4a 100644 --- a/libs/shadcn-ui/package.json +++ b/libs/shadcn-ui/package.json @@ -10,14 +10,18 @@ } }, "dependencies": { - "@radix-ui/react-slot": "^1.2.0", + "@hookform/resolvers": "^5.0.1", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-slot": "^1.2.3", "@tailwindcss/vite": "^4.1.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "lucide-react": "^0.503.0", + "react-hook-form": "^7.56.4", "react-icons": "^5.5.0", "tailwind-merge": "^3.2.0", - "tailwindcss": "^4.1.4" + "tailwindcss": "^4.1.4", + "zod": "^3.25.28" }, "devDependencies": { "tw-animate-css": "^1.2.8" diff --git a/libs/shadcn-ui/src/atoms/button.tsx b/libs/shadcn-ui/src/atoms/button.tsx index 80237ef..6da2a3c 100644 --- a/libs/shadcn-ui/src/atoms/button.tsx +++ b/libs/shadcn-ui/src/atoms/button.tsx @@ -6,20 +6,18 @@ import * as React from 'react'; import { cn } from '../lib'; const buttonVariants = cva( - 'inline-flex items-center justify-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', + 'inline-flex items-center justify-center font-[600] rounded-md px-[16px] py-[10px] transition-colors duration-200 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed', { 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', - outline: - 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', + primary: 'bg-primary-500 hover:bg-primary-600 text-white shadow-md', secondary: - 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', - ghost: 'hover:bg-accent hover:text-accent-foreground', - link: 'text-primary underline-offset-4 hover:underline', + 'bg-white hover:text-primary-600 hover:bg-gray-50 text-primary-500 shadow-md', + text: 'bg-transparent hover:text-primary-600 hover:bg-gray-50 text-primary-500', + bordered: + 'border border-primary-500 hover:border-primary-600 bg-transparent hover:text-primary-600 hover:bg-gray-50 text-primary-500', + success: 'bg-success-500 hover:bg-success-600 text-white shadow-md', + danger: 'bg-danger-100 hover:bg-danger-200 text-danger-500 shadow-md', }, size: { default: 'h-9 px-4 py-2', @@ -29,7 +27,7 @@ const buttonVariants = cva( }, }, defaultVariants: { - variant: 'default', + variant: 'primary', size: 'default', }, } diff --git a/libs/shadcn-ui/src/atoms/form.tsx b/libs/shadcn-ui/src/atoms/form.tsx new file mode 100644 index 0000000..cca75e1 --- /dev/null +++ b/libs/shadcn-ui/src/atoms/form.tsx @@ -0,0 +1,167 @@ +'use client'; + +import * as LabelPrimitive from '@radix-ui/react-label'; +import { Slot } from '@radix-ui/react-slot'; +import * as React from 'react'; +import { + Controller, + FormProvider, + useFormContext, + useFormState, + type ControllerProps, + type FieldPath, + type FieldValues, +} from 'react-hook-form'; +import { cn } from '../lib'; +import { Label } from './label'; + +const Form = FormProvider; + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +> = { + name: TName; +}; + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue +); + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath +>({ + ...props +}: ControllerProps) => { + return ( + + + + ); +}; + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState } = useFormContext(); + const formState = useFormState({ name: fieldContext.name }); + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error('useFormField should be used within '); + } + + const { id } = itemContext; + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + }; +}; + +type FormItemContextValue = { + id: string; +}; + +const FormItemContext = React.createContext( + {} as FormItemContextValue +); + +function FormItem({ className, ...props }: React.ComponentProps<'div'>) { + const id = React.useId(); + + return ( + +
+ + ); +} + +function FormLabel({ + className, + ...props +}: React.ComponentProps) { + const { error, formItemId } = useFormField(); + + return ( +