Compare commits
	
		
			2 Commits
		
	
	
		
			bc9cf1bf25
			...
			167a23a000
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 167a23a000 | |||
| 46c419f310 | 
							
								
								
									
										57
									
								
								app/components/ui/button.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								app/components/ui/button.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,57 @@ | |||||||
|  | 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 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", | ||||||
|  |   { | ||||||
|  |     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", | ||||||
|  |         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", | ||||||
|  |       }, | ||||||
|  |       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<HTMLButtonElement>, | ||||||
|  |     VariantProps<typeof buttonVariants> { | ||||||
|  |   asChild?: boolean | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( | ||||||
|  |   ({ className, variant, size, asChild = false, ...props }, ref) => { | ||||||
|  |     const Comp = asChild ? Slot : "button" | ||||||
|  |     return ( | ||||||
|  |       <Comp | ||||||
|  |         className={cn(buttonVariants({ variant, size, className }))} | ||||||
|  |         ref={ref} | ||||||
|  |         {...props} | ||||||
|  |       /> | ||||||
|  |     ) | ||||||
|  |   } | ||||||
|  | ) | ||||||
|  | Button.displayName = "Button" | ||||||
|  | 
 | ||||||
|  | export { Button, buttonVariants } | ||||||
							
								
								
									
										176
									
								
								app/components/ui/form.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								app/components/ui/form.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -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, | ||||||
|  |   ControllerProps, | ||||||
|  |   FieldPath, | ||||||
|  |   FieldValues, | ||||||
|  |   FormProvider, | ||||||
|  |   useFormContext, | ||||||
|  | } from "react-hook-form" | ||||||
|  | 
 | ||||||
|  | import { cn } from "~/lib/utils" | ||||||
|  | import { Label } from "~/components/ui/label" | ||||||
|  | 
 | ||||||
|  | const Form = FormProvider | ||||||
|  | 
 | ||||||
|  | type FormFieldContextValue< | ||||||
|  |   TFieldValues extends FieldValues = FieldValues, | ||||||
|  |   TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> | ||||||
|  | > = { | ||||||
|  |   name: TName | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const FormFieldContext = React.createContext<FormFieldContextValue>( | ||||||
|  |   {} as FormFieldContextValue | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const FormField = < | ||||||
|  |   TFieldValues extends FieldValues = FieldValues, | ||||||
|  |   TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues> | ||||||
|  | >({ | ||||||
|  |   ...props | ||||||
|  | }: ControllerProps<TFieldValues, TName>) => { | ||||||
|  |   return ( | ||||||
|  |     <FormFieldContext.Provider value={{ name: props.name }}> | ||||||
|  |       <Controller {...props} /> | ||||||
|  |     </FormFieldContext.Provider> | ||||||
|  |   ) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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 <FormField>") | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   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<FormItemContextValue>( | ||||||
|  |   {} as FormItemContextValue | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const FormItem = React.forwardRef< | ||||||
|  |   HTMLDivElement, | ||||||
|  |   React.HTMLAttributes<HTMLDivElement> | ||||||
|  | >(({ className, ...props }, ref) => { | ||||||
|  |   const id = React.useId() | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <FormItemContext.Provider value={{ id }}> | ||||||
|  |       <div ref={ref} className={cn("space-y-2", className)} {...props} /> | ||||||
|  |     </FormItemContext.Provider> | ||||||
|  |   ) | ||||||
|  | }) | ||||||
|  | FormItem.displayName = "FormItem" | ||||||
|  | 
 | ||||||
|  | const FormLabel = React.forwardRef< | ||||||
|  |   React.ElementRef<typeof LabelPrimitive.Root>, | ||||||
|  |   React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> | ||||||
|  | >(({ className, ...props }, ref) => { | ||||||
|  |   const { error, formItemId } = useFormField() | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <Label | ||||||
|  |       ref={ref} | ||||||
|  |       className={cn(error && "text-destructive", className)} | ||||||
|  |       htmlFor={formItemId} | ||||||
|  |       {...props} | ||||||
|  |     /> | ||||||
|  |   ) | ||||||
|  | }) | ||||||
|  | FormLabel.displayName = "FormLabel" | ||||||
|  | 
 | ||||||
|  | const FormControl = React.forwardRef< | ||||||
|  |   React.ElementRef<typeof Slot>, | ||||||
|  |   React.ComponentPropsWithoutRef<typeof Slot> | ||||||
|  | >(({ ...props }, ref) => { | ||||||
|  |   const { error, formItemId, formDescriptionId, formMessageId } = useFormField() | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <Slot | ||||||
|  |       ref={ref} | ||||||
|  |       id={formItemId} | ||||||
|  |       aria-describedby={ | ||||||
|  |         !error | ||||||
|  |           ? `${formDescriptionId}` | ||||||
|  |           : `${formDescriptionId} ${formMessageId}` | ||||||
|  |       } | ||||||
|  |       aria-invalid={!!error} | ||||||
|  |       {...props} | ||||||
|  |     /> | ||||||
|  |   ) | ||||||
|  | }) | ||||||
|  | FormControl.displayName = "FormControl" | ||||||
|  | 
 | ||||||
|  | const FormDescription = React.forwardRef< | ||||||
|  |   HTMLParagraphElement, | ||||||
|  |   React.HTMLAttributes<HTMLParagraphElement> | ||||||
|  | >(({ className, ...props }, ref) => { | ||||||
|  |   const { formDescriptionId } = useFormField() | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <p | ||||||
|  |       ref={ref} | ||||||
|  |       id={formDescriptionId} | ||||||
|  |       className={cn("text-[0.8rem] text-muted-foreground", className)} | ||||||
|  |       {...props} | ||||||
|  |     /> | ||||||
|  |   ) | ||||||
|  | }) | ||||||
|  | FormDescription.displayName = "FormDescription" | ||||||
|  | 
 | ||||||
|  | const FormMessage = React.forwardRef< | ||||||
|  |   HTMLParagraphElement, | ||||||
|  |   React.HTMLAttributes<HTMLParagraphElement> | ||||||
|  | >(({ className, children, ...props }, ref) => { | ||||||
|  |   const { error, formMessageId } = useFormField() | ||||||
|  |   const body = error ? String(error?.message) : children | ||||||
|  | 
 | ||||||
|  |   if (!body) { | ||||||
|  |     return null | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   return ( | ||||||
|  |     <p | ||||||
|  |       ref={ref} | ||||||
|  |       id={formMessageId} | ||||||
|  |       className={cn("text-[0.8rem] font-medium text-destructive", className)} | ||||||
|  |       {...props} | ||||||
|  |     > | ||||||
|  |       {body} | ||||||
|  |     </p> | ||||||
|  |   ) | ||||||
|  | }) | ||||||
|  | FormMessage.displayName = "FormMessage" | ||||||
|  | 
 | ||||||
|  | export { | ||||||
|  |   useFormField, | ||||||
|  |   Form, | ||||||
|  |   FormItem, | ||||||
|  |   FormLabel, | ||||||
|  |   FormControl, | ||||||
|  |   FormDescription, | ||||||
|  |   FormMessage, | ||||||
|  |   FormField, | ||||||
|  | } | ||||||
							
								
								
									
										22
									
								
								app/components/ui/input.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								app/components/ui/input.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | import * as React from "react" | ||||||
|  | 
 | ||||||
|  | import { cn } from "~/lib/utils" | ||||||
|  | 
 | ||||||
|  | const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>( | ||||||
|  |   ({ className, type, ...props }, ref) => { | ||||||
|  |     return ( | ||||||
|  |       <input | ||||||
|  |         type={type} | ||||||
|  |         className={cn( | ||||||
|  |           "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", | ||||||
|  |           className | ||||||
|  |         )} | ||||||
|  |         ref={ref} | ||||||
|  |         {...props} | ||||||
|  |       /> | ||||||
|  |     ) | ||||||
|  |   } | ||||||
|  | ) | ||||||
|  | Input.displayName = "Input" | ||||||
|  | 
 | ||||||
|  | export { Input } | ||||||
							
								
								
									
										24
									
								
								app/components/ui/label.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/components/ui/label.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | import * as React from "react" | ||||||
|  | import * as LabelPrimitive from "@radix-ui/react-label" | ||||||
|  | import { cva, type VariantProps } from "class-variance-authority" | ||||||
|  | 
 | ||||||
|  | import { cn } from "~/lib/utils" | ||||||
|  | 
 | ||||||
|  | const labelVariants = cva( | ||||||
|  |   "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const Label = React.forwardRef< | ||||||
|  |   React.ElementRef<typeof LabelPrimitive.Root>, | ||||||
|  |   React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & | ||||||
|  |     VariantProps<typeof labelVariants> | ||||||
|  | >(({ className, ...props }, ref) => ( | ||||||
|  |   <LabelPrimitive.Root | ||||||
|  |     ref={ref} | ||||||
|  |     className={cn(labelVariants(), className)} | ||||||
|  |     {...props} | ||||||
|  |   /> | ||||||
|  | )) | ||||||
|  | Label.displayName = LabelPrimitive.Root.displayName | ||||||
|  | 
 | ||||||
|  | export { Label } | ||||||
							
								
								
									
										42
									
								
								app/components/ui/radio-group.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								app/components/ui/radio-group.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | import * as React from "react" | ||||||
|  | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" | ||||||
|  | import { Circle } from "lucide-react" | ||||||
|  | 
 | ||||||
|  | import { cn } from "~/lib/utils" | ||||||
|  | 
 | ||||||
|  | const RadioGroup = React.forwardRef< | ||||||
|  |   React.ElementRef<typeof RadioGroupPrimitive.Root>, | ||||||
|  |   React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root> | ||||||
|  | >(({ className, ...props }, ref) => { | ||||||
|  |   return ( | ||||||
|  |     <RadioGroupPrimitive.Root | ||||||
|  |       className={cn("grid gap-2", className)} | ||||||
|  |       {...props} | ||||||
|  |       ref={ref} | ||||||
|  |     /> | ||||||
|  |   ) | ||||||
|  | }) | ||||||
|  | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName | ||||||
|  | 
 | ||||||
|  | const RadioGroupItem = React.forwardRef< | ||||||
|  |   React.ElementRef<typeof RadioGroupPrimitive.Item>, | ||||||
|  |   React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item> | ||||||
|  | >(({ className, ...props }, ref) => { | ||||||
|  |   return ( | ||||||
|  |     <RadioGroupPrimitive.Item | ||||||
|  |       ref={ref} | ||||||
|  |       className={cn( | ||||||
|  |         "aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50", | ||||||
|  |         className | ||||||
|  |       )} | ||||||
|  |       {...props} | ||||||
|  |     > | ||||||
|  |       <RadioGroupPrimitive.Indicator className="flex items-center justify-center"> | ||||||
|  |         <Circle className="h-3.5 w-3.5 fill-primary" /> | ||||||
|  |       </RadioGroupPrimitive.Indicator> | ||||||
|  |     </RadioGroupPrimitive.Item> | ||||||
|  |   ) | ||||||
|  | }) | ||||||
|  | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName | ||||||
|  | 
 | ||||||
|  | export { RadioGroup, RadioGroupItem } | ||||||
							
								
								
									
										6
									
								
								app/lib/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								app/lib/utils.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | import { clsx, type ClassValue } from "clsx" | ||||||
|  | import { twMerge } from "tailwind-merge" | ||||||
|  | 
 | ||||||
|  | export function cn(...inputs: ClassValue[]) { | ||||||
|  |   return twMerge(clsx(inputs)) | ||||||
|  | } | ||||||
| @ -1,51 +1,71 @@ | |||||||
| import React, { ChangeEvent, useEffect, useRef, useState } from "react"; | import React, { ChangeEvent, useEffect, useRef, useState } from "react"; | ||||||
| import DragableBox from "./DragableBox"; | import DragableBox from "./DragableBox"; | ||||||
| import { Coordinates } from "~/types"; | import { Coordinates } from "~/types"; | ||||||
|  | import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; | ||||||
|  | import { Label } from "@/components/ui/label"; | ||||||
|  | import { useSearchParams } from "@remix-run/react"; | ||||||
| 
 | 
 | ||||||
| type ContainerDimensions = { | type ContainerDimensions = { | ||||||
|   height: number; |   height: number; | ||||||
|   width: number; |   width: number; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type FileDimensions = { | ||||||
|  |   x: number; | ||||||
|  |   y: number; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | type UnitType = "pixels" | "percentage"; | ||||||
|  | 
 | ||||||
|  | interface ComponentProps { | ||||||
|  |   image?: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default function CropSelector() { | export default function CropSelector({ image }: ComponentProps) { | ||||||
|   const [firstDragable, setFirstDragable] = useState<Coordinates>({ |   const [firstDragable, setFirstDragable] = useState<Coordinates>({ | ||||||
|     x: 0, |     x: 0, | ||||||
|     y: 0 |     y: 0, | ||||||
|   }); |   }); | ||||||
|   const [secondDragable, setSecondDragable] = useState<Coordinates>({ |   const [secondDragable, setSecondDragable] = useState<Coordinates>({ | ||||||
|     x: 100, |     x: 100, | ||||||
|     y: 100 |     y: 100, | ||||||
|   }); |   }); | ||||||
|   const [selectorPosition, setSelectorPosition] = useState<Coordinates>({ |   const [selectorPosition, setSelectorPosition] = useState<Coordinates>({ | ||||||
|     x: 0, |     x: 0, | ||||||
|     y: 0 |     y: 0, | ||||||
|   }); |   }); | ||||||
|   const [containerSize, setContainerSize] = useState<ContainerDimensions>({ |   const [containerSize, setContainerSize] = useState<ContainerDimensions>({ | ||||||
|     height: 0, |     height: 0, | ||||||
|     width: 0 |     width: 0, | ||||||
|   }); |   }); | ||||||
|   const [userInputs, setUserInputs] = useState<ContainerDimensions>({ |   const [pixelDimensions, setPixelDimensions] = useState<ContainerDimensions>({ | ||||||
|     height: 100, |     height: 100, | ||||||
|     width: 100 |     width: 100, | ||||||
|   }); |   }); | ||||||
| 
 |   const [unit, setUnit] = useState<UnitType>("pixels"); | ||||||
|  |   const [searchParams, setSearchParams] = useSearchParams(); | ||||||
|   const containerRef = useRef<HTMLDivElement>(null); |   const containerRef = useRef<HTMLDivElement>(null); | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     setSelectorPosition({ |     setSelectorPosition({ | ||||||
|       x: Math.min(firstDragable.x, secondDragable.x), |       x: Math.min(firstDragable.x, secondDragable.x), | ||||||
|       y: Math.min(firstDragable.y, secondDragable.y) |       y: Math.min(firstDragable.y, secondDragable.y), | ||||||
|     }); |     }); | ||||||
|  |     const setUrlParams = setTimeout(() => { | ||||||
|  |       writeDimensionsToURL(); | ||||||
|  |     }, 500); | ||||||
|  | 
 | ||||||
|  |     return () => clearTimeout(setUrlParams); | ||||||
|   }, [firstDragable, secondDragable]); |   }, [firstDragable, secondDragable]); | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (!containerRef.current) return; |     if (!containerRef.current) return; | ||||||
| 
 | 
 | ||||||
|     const resizeObserver = new ResizeObserver(entries => { |     const resizeObserver = new ResizeObserver((entries) => { | ||||||
|       const entry = entries[0]; |       const entry = entries[0]; | ||||||
|       setContainerSize({ |       setContainerSize({ | ||||||
|         width: entry.contentRect.width, |         width: entry.contentRect.width, | ||||||
|         height: entry.contentRect.height |         height: entry.contentRect.height, | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
| @ -66,56 +86,83 @@ export default function CropSelector() { | |||||||
|     const newSecondX = Math.max(firstDragable.x, secondDragable.x) + deltaX; |     const newSecondX = Math.max(firstDragable.x, secondDragable.x) + deltaX; | ||||||
|     const newSecondY = Math.max(firstDragable.y, secondDragable.y) + deltaY; |     const newSecondY = Math.max(firstDragable.y, secondDragable.y) + deltaY; | ||||||
| 
 | 
 | ||||||
|     // Ensure the selector stays within container bounds
 |     if (newFirstX >= 0 && newSecondX <= 100 && newFirstY >= 0 && newSecondY <= 100) { | ||||||
|     if (newFirstX >= 0 && newSecondX <= containerSize.width && |  | ||||||
|         newFirstY >= 0 && newSecondY <= containerSize.height) { |  | ||||||
|       setFirstDragable({ |       setFirstDragable({ | ||||||
|         x: newFirstX, |         x: newFirstX, | ||||||
|         y: newFirstY |         y: newFirstY, | ||||||
|       }); |       }); | ||||||
|       setSecondDragable({ |       setSecondDragable({ | ||||||
|         x: newSecondX, |         x: newSecondX, | ||||||
|         y: newSecondY |         y: newSecondY, | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |   const pixelsToPercentage = (pixels: number, totalSize: number): number => { | ||||||
|  |     return totalSize > 0 ? (pixels / totalSize) * 100 : 0; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const percentageToPixels = (percentage: number, totalSize: number): number => { | ||||||
|  |     return (percentage * totalSize) / 100; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const getActualPixelDimensions = (): ContainerDimensions => { | ||||||
|  |     return { | ||||||
|  |       width: Math.round(percentageToPixels(selectorWidth, containerSize.width)), | ||||||
|  |       height: Math.round(percentageToPixels(selectorHeight, containerSize.height)), | ||||||
|  |     }; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const writeDimensionsToURL = (): void => { | ||||||
|  |     setSearchParams((prev) => ({ | ||||||
|  |       ...prev, | ||||||
|  |       x1: ((firstDragable.x / 100) * containerSize.width).toFixed(), | ||||||
|  |       y1: ((firstDragable.y / 100) * containerSize.height).toFixed(), | ||||||
|  |       x2: ((secondDragable.x / 100) * containerSize.width).toFixed(), | ||||||
|  |       y2: ((secondDragable.y / 100) * containerSize.height).toFixed(), | ||||||
|  |     })); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const getDisplayValue = (dimension: "width" | "height"): number => { | ||||||
|  |     const actualPixels = getActualPixelDimensions(); | ||||||
|  |     if (unit === "percentage") { | ||||||
|  |       return dimension === "width" ? selectorWidth : selectorHeight; | ||||||
|  |     } | ||||||
|  |     return dimension === "width" ? actualPixels.width : actualPixels.height; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   const handleDimensionChange = ( |   const handleDimensionChange = ( | ||||||
|     dimension: 'width' | 'height', |     dimension: "width" | "height", | ||||||
|     event: React.ChangeEvent<HTMLInputElement> |     event: React.ChangeEvent<HTMLInputElement> | ||||||
|   ) => { |   ) => { | ||||||
|     const value = Math.max(0, parseInt(event.target.value) || 0); |     const inputValue = Math.max(0, parseFloat(event.target.value) || 0); | ||||||
|     const maxValue = dimension === 'width' ? containerSize.width : containerSize.height; |     let newPercentage: number; | ||||||
|     const clampedValue = Math.min(value, maxValue); |  | ||||||
| 
 | 
 | ||||||
|     setUserInputs(prev => ({ |     if (unit === "percentage") { | ||||||
|       ...prev, |       newPercentage = Math.min(inputValue, 100); | ||||||
|       [dimension]: clampedValue |  | ||||||
|     })); |  | ||||||
| 
 |  | ||||||
|     // Update selector dimensions
 |  | ||||||
|     if (dimension === 'width') { |  | ||||||
|       const newX = Math.min(firstDragable.x, containerSize.width - clampedValue); |  | ||||||
|       setFirstDragable(prev => ({ ...prev, x: newX })); |  | ||||||
|       setSecondDragable(prev => ({ ...prev, x: newX + clampedValue })); |  | ||||||
|     } else { |     } else { | ||||||
|       const newY = Math.min(firstDragable.y, containerSize.height - clampedValue); |       // Convert pixel input to percentage
 | ||||||
|       setFirstDragable(prev => ({ ...prev, y: newY })); |       const totalSize = dimension === "width" ? containerSize.width : containerSize.height; | ||||||
|       setSecondDragable(prev => ({ ...prev, y: newY + clampedValue })); |       newPercentage = pixelsToPercentage(Math.min(inputValue, totalSize), totalSize); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (dimension === "width") { | ||||||
|  |       const newX = Math.min(firstDragable.x, 100 - newPercentage); | ||||||
|  |       setFirstDragable((prev) => ({ ...prev, x: newX })); | ||||||
|  |       setSecondDragable((prev) => ({ ...prev, x: newX + newPercentage })); | ||||||
|  |     } else { | ||||||
|  |       const newY = Math.min(firstDragable.y, 100 - newPercentage); | ||||||
|  |       setFirstDragable((prev) => ({ ...prev, y: newY })); | ||||||
|  |       setSecondDragable((prev) => ({ ...prev, y: newY + newPercentage })); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   // Update user inputs when dragging
 |  | ||||||
|   useEffect(() => { |  | ||||||
|     setUserInputs({ |  | ||||||
|       width: Math.round(selectorWidth), |  | ||||||
|       height: Math.round(selectorHeight) |  | ||||||
|     }); |  | ||||||
|   }, [selectorWidth, selectorHeight]); |  | ||||||
| 
 |  | ||||||
|   return ( |   return ( | ||||||
|     <div className="flex flex-col h-screen w-screen justify-center items-center gap-4"> |     <div className="flex flex-col w-full p-4 h-fit justify-center items-center gap-4"> | ||||||
|       <div ref={containerRef} className="relative w-1/3 h-1/3 border-black border"> |       <div | ||||||
|  |         ref={containerRef} | ||||||
|  |         className="relative w-auto h-fit min-w-40 min-h-40 border-black border" | ||||||
|  |       > | ||||||
|         <DragableBox |         <DragableBox | ||||||
|           position={selectorPosition} |           position={selectorPosition} | ||||||
|           mode="box" |           mode="box" | ||||||
| @ -128,33 +175,50 @@ export default function CropSelector() { | |||||||
|         <DragableBox position={firstDragable} coordSetter={setFirstDragable} mode="handle"> |         <DragableBox position={firstDragable} coordSetter={setFirstDragable} mode="handle"> | ||||||
|           <div className="bg-red-600 h-2 w-2 rounded-full hover:scale-125" /> |           <div className="bg-red-600 h-2 w-2 rounded-full hover:scale-125" /> | ||||||
|         </DragableBox> |         </DragableBox> | ||||||
|  |         <img src={image} /> | ||||||
|         <DragableBox position={secondDragable} coordSetter={setSecondDragable} mode="handle"> |         <DragableBox position={secondDragable} coordSetter={setSecondDragable} mode="handle"> | ||||||
|           <div className="bg-red-600 h-2 w-2 rounded-full hover:scale-125" /> |           <div className="bg-red-600 h-2 w-2 rounded-full hover:scale-125" /> | ||||||
|         </DragableBox> |         </DragableBox> | ||||||
|       </div> |       </div> | ||||||
| 
 | 
 | ||||||
|  |       <RadioGroup | ||||||
|  |         defaultValue="pixels" | ||||||
|  |         value={unit} | ||||||
|  |         onValueChange={(value) => setUnit(value as UnitType)} | ||||||
|  |         className="flex gap-4 mb-2" | ||||||
|  |       > | ||||||
|  |         <div className="flex items-center space-x-2"> | ||||||
|  |           <RadioGroupItem value="pixels" id="pixels" /> | ||||||
|  |           <Label htmlFor="pixels">Pixels</Label> | ||||||
|  |         </div> | ||||||
|  |         <div className="flex items-center space-x-2"> | ||||||
|  |           <RadioGroupItem value="percentage" id="percentage" /> | ||||||
|  |           <Label htmlFor="percentage">Percentage</Label> | ||||||
|  |         </div> | ||||||
|  |       </RadioGroup> | ||||||
|  | 
 | ||||||
|       <div className="flex gap-4"> |       <div className="flex gap-4"> | ||||||
|         <div className="flex flex-col gap-2"> |         <div className="flex flex-col gap-2"> | ||||||
|           <label htmlFor="width">Width (px)</label> |           <label htmlFor="width">Width ({unit === "pixels" ? "px" : "%"})</label> | ||||||
|           <input |           <input | ||||||
|             id="width" |             id="width" | ||||||
|             type="number" |             type="number" | ||||||
|             min={0} |             min={0} | ||||||
|             max={containerSize.width} |             max={unit === "percentage" ? 100 : containerSize.width} | ||||||
|             value={userInputs.width.toFixed()} |             value={getDisplayValue("width").toFixed(unit === "percentage" ? 1 : 0)} | ||||||
|             onChange={(e: ChangeEvent<HTMLInputElement>) => handleDimensionChange('width', e)} |             onChange={(e: ChangeEvent<HTMLInputElement>) => handleDimensionChange("width", e)} | ||||||
|             className="w-24" |             className="w-24" | ||||||
|           /> |           /> | ||||||
|         </div> |         </div> | ||||||
|         <div className="flex flex-col gap-2"> |         <div className="flex flex-col gap-2"> | ||||||
|           <label htmlFor="height">Height (px)</label> |           <label htmlFor="height">Height ({unit === "pixels" ? "px" : "%"})</label> | ||||||
|           <input |           <input | ||||||
|             id="height" |             id="height" | ||||||
|             type="number" |             type="number" | ||||||
|             min={0} |             min={0} | ||||||
|             max={containerSize.height} |             max={unit === "percentage" ? 100 : containerSize.height} | ||||||
|             value={userInputs.height.toFixed()} |             value={getDisplayValue("height").toFixed(unit === "percentage" ? 1 : 0)} | ||||||
|             onChange={(e) => handleDimensionChange('height', e)} |             onChange={(e) => handleDimensionChange("height", e)} | ||||||
|             className="w-24" |             className="w-24" | ||||||
|           /> |           /> | ||||||
|         </div> |         </div> | ||||||
|  | |||||||
| @ -1,5 +1,11 @@ | |||||||
|  | import { Input } from "~/components/ui/input"; | ||||||
| import type { MetaFunction } from "@remix-run/cloudflare"; | import type { MetaFunction } from "@remix-run/cloudflare"; | ||||||
| import CropSelector from "./CropSelector"; | import CropSelector from "./CropSelector"; | ||||||
|  | import { Button } from "~/components/ui/button"; | ||||||
|  | import { z, ZodError } from "zod"; | ||||||
|  | import { ChangeEvent, Suspense, useEffect, useRef, useState } from "react"; | ||||||
|  | import { useSubmit } from "@remix-run/react"; | ||||||
|  | import { ActionFunctionArgs, Form, json, useFetcher } from "react-router-dom"; | ||||||
| 
 | 
 | ||||||
| export const meta: MetaFunction = () => { | export const meta: MetaFunction = () => { | ||||||
|   return [ |   return [ | ||||||
| @ -8,7 +14,63 @@ export const meta: MetaFunction = () => { | |||||||
|   ]; |   ]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | const MAX_FILE_SIZE = 5 * 1024 * 1024; | ||||||
|  | 
 | ||||||
|  | const ACCEPTED_IMAGE_TYPES = [ | ||||||
|  |   "image/jpeg", | ||||||
|  |   "image/jpg", | ||||||
|  |   "image/png", | ||||||
|  |   "image/webp", | ||||||
|  |   "image/gif", | ||||||
|  | ] as const; | ||||||
|  | 
 | ||||||
|  | export const imageFileSchema = z.object({ | ||||||
|  |   name: z.string(), | ||||||
|  |   size: z.number().max(MAX_FILE_SIZE, "File size must be less than 5MB"), | ||||||
|  |   type: z.enum(ACCEPTED_IMAGE_TYPES, { | ||||||
|  |     errorMap: () => ({ message: "Only .jpg, .jpeg, .png, .webp and .gif files are accepted." }), | ||||||
|  |   }), | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| export default function Index() { | export default function Index() { | ||||||
|  |   const [image, setImage] = useState<string>(); | ||||||
|  |   const imageRef = useRef<HTMLImageElement | null>(null); | ||||||
|  | 
 | ||||||
|  |   const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => { | ||||||
|  |     console.log(e.target.files); | ||||||
|  |     if (!e.target.files) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     setImage(URL.createObjectURL(e.target.files[0])); | ||||||
|  |   }; | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (imageRef.current?.complete && image) { | ||||||
|  |       URL.revokeObjectURL(image); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|   return ( |   return ( | ||||||
|     <CropSelector />); |     <div className="w-screen h-screen flex flex-col justify-center items-center"> | ||||||
|  |       {image && <img ref={imageRef} src={image} className="h-40 w-40" />} | ||||||
|  |       <Input name="image-upload" type="file" onChange={handleFileChange} /> | ||||||
|  |       <CropSelector image={image} /> | ||||||
|  |       <Button>Submit</Button> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // export async function action({ request }: ActionFunctionArgs) {
 | ||||||
|  | //   const formData = await request.formData();
 | ||||||
|  | //   const image = formData.get("image-upload") as File;
 | ||||||
|  | //   console.log("skibidi action!");
 | ||||||
|  | //   try {
 | ||||||
|  | //     const valid_image = imageFileSchema.parse(image);
 | ||||||
|  | //     return valid_image;
 | ||||||
|  | //   } catch (e: any) {
 | ||||||
|  | //     if (e instanceof ZodError) {
 | ||||||
|  | //       const errors: Record<string, string> = {};
 | ||||||
|  | //       errors.image = e.message;
 | ||||||
|  | //       return json({ errors });
 | ||||||
|  | //     }
 | ||||||
|  | //     console.log(e);
 | ||||||
|  | //   }
 | ||||||
|  | // }
 | ||||||
|  | |||||||
| @ -10,3 +10,66 @@ | |||||||
| /*    color-scheme: dark;*/ | /*    color-scheme: dark;*/ | ||||||
| /*  }*/ | /*  }*/ | ||||||
| /*}*/ | /*}*/ | ||||||
|  | @layer base { | ||||||
|  |   :root { | ||||||
|  |     --background: 0 0% 100%; | ||||||
|  |     --foreground: 0 0% 3.9%; | ||||||
|  |     --card: 0 0% 100%; | ||||||
|  |     --card-foreground: 0 0% 3.9%; | ||||||
|  |     --popover: 0 0% 100%; | ||||||
|  |     --popover-foreground: 0 0% 3.9%; | ||||||
|  |     --primary: 0 0% 9%; | ||||||
|  |     --primary-foreground: 0 0% 98%; | ||||||
|  |     --secondary: 0 0% 96.1%; | ||||||
|  |     --secondary-foreground: 0 0% 9%; | ||||||
|  |     --muted: 0 0% 96.1%; | ||||||
|  |     --muted-foreground: 0 0% 45.1%; | ||||||
|  |     --accent: 0 0% 96.1%; | ||||||
|  |     --accent-foreground: 0 0% 9%; | ||||||
|  |     --destructive: 0 84.2% 60.2%; | ||||||
|  |     --destructive-foreground: 0 0% 98%; | ||||||
|  |     --border: 0 0% 89.8%; | ||||||
|  |     --input: 0 0% 89.8%; | ||||||
|  |     --ring: 0 0% 3.9%; | ||||||
|  |     --chart-1: 12 76% 61%; | ||||||
|  |     --chart-2: 173 58% 39%; | ||||||
|  |     --chart-3: 197 37% 24%; | ||||||
|  |     --chart-4: 43 74% 66%; | ||||||
|  |     --chart-5: 27 87% 67%; | ||||||
|  |     --radius: 0.5rem | ||||||
|  |   } | ||||||
|  |   .dark { | ||||||
|  |     --background: 0 0% 3.9%; | ||||||
|  |     --foreground: 0 0% 98%; | ||||||
|  |     --card: 0 0% 3.9%; | ||||||
|  |     --card-foreground: 0 0% 98%; | ||||||
|  |     --popover: 0 0% 3.9%; | ||||||
|  |     --popover-foreground: 0 0% 98%; | ||||||
|  |     --primary: 0 0% 98%; | ||||||
|  |     --primary-foreground: 0 0% 9%; | ||||||
|  |     --secondary: 0 0% 14.9%; | ||||||
|  |     --secondary-foreground: 0 0% 98%; | ||||||
|  |     --muted: 0 0% 14.9%; | ||||||
|  |     --muted-foreground: 0 0% 63.9%; | ||||||
|  |     --accent: 0 0% 14.9%; | ||||||
|  |     --accent-foreground: 0 0% 98%; | ||||||
|  |     --destructive: 0 62.8% 30.6%; | ||||||
|  |     --destructive-foreground: 0 0% 98%; | ||||||
|  |     --border: 0 0% 14.9%; | ||||||
|  |     --input: 0 0% 14.9%; | ||||||
|  |     --ring: 0 0% 83.1%; | ||||||
|  |     --chart-1: 220 70% 50%; | ||||||
|  |     --chart-2: 160 60% 45%; | ||||||
|  |     --chart-3: 30 80% 55%; | ||||||
|  |     --chart-4: 280 65% 60%; | ||||||
|  |     --chart-5: 340 75% 55% | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @layer base { | ||||||
|  |   * { | ||||||
|  |     @apply border-border; | ||||||
|  |   } | ||||||
|  |   body { | ||||||
|  |     @apply bg-background text-foreground; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										21
									
								
								components.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								components.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | { | ||||||
|  |   "$schema": "https://ui.shadcn.com/schema.json", | ||||||
|  |   "style": "new-york", | ||||||
|  |   "rsc": false, | ||||||
|  |   "tsx": true, | ||||||
|  |   "tailwind": { | ||||||
|  |     "config": "tailwind.config.ts", | ||||||
|  |     "css": "app/tailwind.css", | ||||||
|  |     "baseColor": "neutral", | ||||||
|  |     "cssVariables": true, | ||||||
|  |     "prefix": "" | ||||||
|  |   }, | ||||||
|  |   "aliases": { | ||||||
|  |     "components": "~/components", | ||||||
|  |     "utils": "~/lib/utils", | ||||||
|  |     "ui": "~/components/ui", | ||||||
|  |     "lib": "~/lib", | ||||||
|  |     "hooks": "~/hooks" | ||||||
|  |   }, | ||||||
|  |   "iconLibrary": "lucide" | ||||||
|  | } | ||||||
							
								
								
									
										722
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										722
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										14
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								package.json
									
									
									
									
									
								
							| @ -13,13 +13,25 @@ | |||||||
|     "typegen": "wrangler types" |     "typegen": "wrangler types" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|  |     "@hookform/resolvers": "^3.9.1", | ||||||
|  |     "@radix-ui/react-label": "^2.1.0", | ||||||
|  |     "@radix-ui/react-radio-group": "^1.2.1", | ||||||
|  |     "@radix-ui/react-slot": "^1.1.0", | ||||||
|     "@remix-run/cloudflare": "^2.13.1", |     "@remix-run/cloudflare": "^2.13.1", | ||||||
|     "@remix-run/cloudflare-pages": "^2.13.1", |     "@remix-run/cloudflare-pages": "^2.13.1", | ||||||
|     "@remix-run/cloudflare-workers": "^2.13.1", |     "@remix-run/cloudflare-workers": "^2.13.1", | ||||||
|     "@remix-run/react": "^2.13.1", |     "@remix-run/react": "^2.13.1", | ||||||
|  |     "class-variance-authority": "^0.7.0", | ||||||
|  |     "clsx": "^2.1.1", | ||||||
|     "isbot": "^4.1.0", |     "isbot": "^4.1.0", | ||||||
|  |     "lucide-react": "^0.460.0", | ||||||
|     "react": "^18.2.0", |     "react": "^18.2.0", | ||||||
|     "react-dom": "^18.2.0" |     "react-dom": "^18.2.0", | ||||||
|  |     "react-hook-form": "^7.53.2", | ||||||
|  |     "remix-utils": "^7.7.0", | ||||||
|  |     "tailwind-merge": "^2.5.4", | ||||||
|  |     "tailwindcss-animate": "^1.0.7", | ||||||
|  |     "zod": "^3.23.8" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@cloudflare/workers-types": "^4.20240512.0", |     "@cloudflare/workers-types": "^4.20240512.0", | ||||||
|  | |||||||
| @ -1,22 +1,69 @@ | |||||||
| import type { Config } from "tailwindcss"; | import type { Config } from "tailwindcss"; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|  |   darkMode: ["class"], | ||||||
|   content: ["./app/**/{**,.client,.server}/**/*.{js,jsx,ts,tsx}"], |   content: ["./app/**/{**,.client,.server}/**/*.{js,jsx,ts,tsx}"], | ||||||
|   theme: { |   theme: { | ||||||
|     extend: { |     extend: { | ||||||
|       fontFamily: { |       fontFamily: { | ||||||
|         sans: [ |         sans: [ | ||||||
|           '"Inter"', |           "Inter", | ||||||
|           "ui-sans-serif", |           "ui-sans-serif", | ||||||
|           "system-ui", |           "system-ui", | ||||||
|           "sans-serif", |           "Apple Color Emoji", | ||||||
|           '"Apple Color Emoji"', |           "Segoe UI Emoji", | ||||||
|           '"Segoe UI Emoji"', |           "Segoe UI Symbol", | ||||||
|           '"Segoe UI Symbol"', |           "Noto Color Emoji", | ||||||
|           '"Noto Color Emoji"', |  | ||||||
|         ], |         ], | ||||||
|       }, |       }, | ||||||
|  |       borderRadius: { | ||||||
|  |         lg: "var(--radius)", | ||||||
|  |         md: "calc(var(--radius) - 2px)", | ||||||
|  |         sm: "calc(var(--radius) - 4px)", | ||||||
|  |       }, | ||||||
|  |       colors: { | ||||||
|  |         background: "hsl(var(--background))", | ||||||
|  |         foreground: "hsl(var(--foreground))", | ||||||
|  |         card: { | ||||||
|  |           DEFAULT: "hsl(var(--card))", | ||||||
|  |           foreground: "hsl(var(--card-foreground))", | ||||||
|  |         }, | ||||||
|  |         popover: { | ||||||
|  |           DEFAULT: "hsl(var(--popover))", | ||||||
|  |           foreground: "hsl(var(--popover-foreground))", | ||||||
|  |         }, | ||||||
|  |         primary: { | ||||||
|  |           DEFAULT: "hsl(var(--primary))", | ||||||
|  |           foreground: "hsl(var(--primary-foreground))", | ||||||
|  |         }, | ||||||
|  |         secondary: { | ||||||
|  |           DEFAULT: "hsl(var(--secondary))", | ||||||
|  |           foreground: "hsl(var(--secondary-foreground))", | ||||||
|  |         }, | ||||||
|  |         muted: { | ||||||
|  |           DEFAULT: "hsl(var(--muted))", | ||||||
|  |           foreground: "hsl(var(--muted-foreground))", | ||||||
|  |         }, | ||||||
|  |         accent: { | ||||||
|  |           DEFAULT: "hsl(var(--accent))", | ||||||
|  |           foreground: "hsl(var(--accent-foreground))", | ||||||
|  |         }, | ||||||
|  |         destructive: { | ||||||
|  |           DEFAULT: "hsl(var(--destructive))", | ||||||
|  |           foreground: "hsl(var(--destructive-foreground))", | ||||||
|  |         }, | ||||||
|  |         border: "hsl(var(--border))", | ||||||
|  |         input: "hsl(var(--input))", | ||||||
|  |         ring: "hsl(var(--ring))", | ||||||
|  |         chart: { | ||||||
|  |           "1": "hsl(var(--chart-1))", | ||||||
|  |           "2": "hsl(var(--chart-2))", | ||||||
|  |           "3": "hsl(var(--chart-3))", | ||||||
|  |           "4": "hsl(var(--chart-4))", | ||||||
|  |           "5": "hsl(var(--chart-5))", | ||||||
|         }, |         }, | ||||||
|       }, |       }, | ||||||
|   plugins: [], |     }, | ||||||
|  |   }, | ||||||
|  |   plugins: [require("tailwindcss-animate")], | ||||||
| } satisfies Config; | } satisfies Config; | ||||||
|  | |||||||
| @ -8,8 +8,15 @@ | |||||||
|     "**/.client/**/*.tsx" |     "**/.client/**/*.tsx" | ||||||
|   ], |   ], | ||||||
|   "compilerOptions": { |   "compilerOptions": { | ||||||
|     "lib": ["DOM", "DOM.Iterable", "ES2022"], |     "lib": [ | ||||||
|     "types": ["@remix-run/cloudflare", "vite/client"], |       "DOM", | ||||||
|  |       "DOM.Iterable", | ||||||
|  |       "ES2022" | ||||||
|  |     ], | ||||||
|  |     "types": [ | ||||||
|  |       "@remix-run/cloudflare", | ||||||
|  |       "vite/client" | ||||||
|  |     ], | ||||||
|     "isolatedModules": true, |     "isolatedModules": true, | ||||||
|     "esModuleInterop": true, |     "esModuleInterop": true, | ||||||
|     "jsx": "react-jsx", |     "jsx": "react-jsx", | ||||||
| @ -23,10 +30,14 @@ | |||||||
|     "forceConsistentCasingInFileNames": true, |     "forceConsistentCasingInFileNames": true, | ||||||
|     "baseUrl": ".", |     "baseUrl": ".", | ||||||
|     "paths": { |     "paths": { | ||||||
|       "~/*": ["./app/*"] |       "~/*": [ | ||||||
|  |         "./app/*" | ||||||
|  |       ], | ||||||
|  |       "@/*": [ | ||||||
|  |         "./app/*" | ||||||
|  |       ] | ||||||
|  |     } | ||||||
|   }, |   }, | ||||||
| 
 |  | ||||||
|   // Vite takes care of building everything, not tsc. |   // Vite takes care of building everything, not tsc. | ||||||
|   "noEmit": true |   "noEmit": true | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user