diff --git a/packages/common/business_components/README.md b/packages/common/business_components/README.md new file mode 100644 index 000000000..597f49a5c --- /dev/null +++ b/packages/common/business_components/README.md @@ -0,0 +1,22 @@ +## business_components 패키지 +### 원앤올에서 제공받는 컴포넌트 외에 업무 파트에서 사용할 수 있는 별도 components 패키지지 +  1) 설정 + +    (1) tsconfig.json 'path'에 '@common/business_components' 변수를 추가 +```json +"paths": { + "@/*": ["./*"], + "@common/components/*": ["../../packages/common/components/src/*"], + "@common/components": ["../../packages/common/components/src/index"], + // 작성구문문 + "@common/business_components/*": ["../../packages/common/business_components/src/*"], + "@common/business_components": ["../../packages/common/business_components/src/index"] +} +``` +    (2) package.json 'dependencies'에 '@common/business_components' 추가 +```json +"dependencies": { + "@common/business_components": "workspace:*", + "next": "15.1.7" +}, +``` \ No newline at end of file diff --git a/packages/common/business_components/package.json b/packages/common/business_components/package.json index 39e55ebb6..ca6f67b76 100644 --- a/packages/common/business_components/package.json +++ b/packages/common/business_components/package.json @@ -26,10 +26,17 @@ }, "dependencies": { "@common/components": "workspace:*", + "@hookform/resolvers": "^4.1.2", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-slot": "^1.1.2", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "lucide-react": "^0.474.0", "path": "^0.12.7", + "react-hook-form": "^7.54.2", "recharts": "^2.15.1", - "tailwind-merge": "^3.0.1" + "tailwind-merge": "^3.0.1", + "zod": "^3.24.2" }, "devDependencies": { "@eslint/js": "^9.17.0", diff --git a/packages/common/business_components/src/chart.md b/packages/common/business_components/src/chart.md new file mode 100644 index 000000000..f2d989375 --- /dev/null +++ b/packages/common/business_components/src/chart.md @@ -0,0 +1,54 @@ +## Chart 모듈 +### 공통 모듈 중 Chart를 다양한 테마로 적용시킬 수 있도록 해주는 모듈 +  1. 차트 모듈 사용법 + +    1-1) 호출방법 + +      (1) 파일에 차트생성에 필요한 form, MainChart, MainConfig 임폴트 +```js +import {type ChartConfig, MainChart, CHART_FORM } from "@common/business_components"; +``` +      (2) ChartForm 지정 +```js +/** + * CHART_FORM.BAR : Bar차트 + * CHART_FORM.LINE : Line차트 + * DN : Daughnut차트 +**/ +const chartForm = CHART_FORM.BAR; +``` +      (3) chartData 정의 +``` js +/** + * label : 차트항목 / val : 데이터값 +**/ +const chartData = [ + { label: "January", val: 186}, + { label: "March", val: 237}, + { label: "February", val: 305}, + { label: "April", val: 73}, + { label: "May", val: 209}, + { label: "June", val: 214}, +] +``` +      (4) Chart생성에 필요한 chartConfig 정의 +``` js +/** + * 차트 Config + */ +const chartConfig = chartData.reduce((config,>>element) => { + label: element.label, + config[element.label] = { + color : "hsl(var(--chart-1))" + }; + return config; + }, {} as Record) satisfies ChartConfig; +``` +      (5) Chart 객체 생성 +```html +return ( +
+ +
+ ); +``` \ No newline at end of file diff --git a/packages/common/business_components/src/index.css b/packages/common/business_components/src/index.css new file mode 100644 index 000000000..46896e552 --- /dev/null +++ b/packages/common/business_components/src/index.css @@ -0,0 +1,11 @@ + +@layer base {} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} \ No newline at end of file diff --git a/packages/common/business_components/src/ui/form/button.tsx b/packages/common/business_components/src/ui/form/button.tsx new file mode 100644 index 000000000..36496a287 --- /dev/null +++ b/packages/common/business_components/src/ui/form/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/packages/common/business_components/src/ui/form/index.tsx b/packages/common/business_components/src/ui/form/index.tsx new file mode 100644 index 000000000..4c6d473f4 --- /dev/null +++ b/packages/common/business_components/src/ui/form/index.tsx @@ -0,0 +1,176 @@ +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + type ControllerProps, + type FieldPath, + type FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form" + +import { cn } from "@/lib/utils" +import { Label } from "@/ui/form/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, formState } = useFormContext() + + 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 +) + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId() + + return ( + +
+ + ) +}) +FormItem.displayName = "FormItem" + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField() + + return ( +