From e4da88b98b15d41fa080e887cc4b983ae5f2d4f8 Mon Sep 17 00:00:00 2001 From: shafi54 <108669266+shafi-aviz@users.noreply.github.com> Date: Sat, 23 May 2026 16:30:47 +0530 Subject: [PATCH] common components extraction --- apps/pharmanager/package.json | 1 + .../src/components/ui/Checkbox.tsx | 44 +++++++++ .../src/components/ui/Combobox.tsx | 99 +++++++++++++++++++ apps/pharmanager/src/components/ui/index.ts | 4 + apps/pharmanager/src/routes/products/add.tsx | 28 ++---- apps/pharmanager/src/routes/stock/add.tsx | 41 ++++---- 6 files changed, 177 insertions(+), 40 deletions(-) create mode 100644 apps/pharmanager/src/components/ui/Checkbox.tsx create mode 100644 apps/pharmanager/src/components/ui/Combobox.tsx diff --git a/apps/pharmanager/package.json b/apps/pharmanager/package.json index fb2cd02..21d9387 100644 --- a/apps/pharmanager/package.json +++ b/apps/pharmanager/package.json @@ -23,6 +23,7 @@ "@tanstack/react-table": "^8.21.3", "@tanstack/router-plugin": "^1.132.0", "class-variance-authority": "^0.7.1", + "cmdk": "^1.1.1", "lucide-react": "^0.545.0", "react": "19.1.0", "react-dom": "19.1.0", diff --git a/apps/pharmanager/src/components/ui/Checkbox.tsx b/apps/pharmanager/src/components/ui/Checkbox.tsx new file mode 100644 index 0000000..9ed4a30 --- /dev/null +++ b/apps/pharmanager/src/components/ui/Checkbox.tsx @@ -0,0 +1,44 @@ +import { forwardRef, type ComponentPropsWithoutRef } from "react"; +import { cn } from "#/lib/cn"; + +export interface CheckboxProps extends ComponentPropsWithoutRef<"input"> { + label?: string; +} + +export const Checkbox = forwardRef( + ({ className, label, id, disabled, ...props }, ref) => { + return ( + + ); + }, +); + +Checkbox.displayName = "Checkbox"; diff --git a/apps/pharmanager/src/components/ui/Combobox.tsx b/apps/pharmanager/src/components/ui/Combobox.tsx new file mode 100644 index 0000000..1a57a9b --- /dev/null +++ b/apps/pharmanager/src/components/ui/Combobox.tsx @@ -0,0 +1,99 @@ +import { useState, useRef, useEffect } from "react"; +import { Command } from "cmdk"; +import { Check, ChevronsUpDown } from "lucide-react"; +import { cn } from "#/lib/cn"; + +export interface ComboboxOption { + value: string; + label: string; +} + +interface ComboboxProps { + options: ComboboxOption[]; + value: string; + onChange: (value: string) => void; + placeholder?: string; + emptyMessage?: string; + className?: string; +} + +export function Combobox({ + options, + value, + onChange, + placeholder = "Search...", + emptyMessage = "No results found.", + className, +}: ComboboxProps) { + const [open, setOpen] = useState(false); + const ref = useRef(null); + + const selectedLabel = options.find((o) => o.value === value)?.label ?? ""; + + useEffect(() => { + function handleClickOutside(e: MouseEvent) { + if (ref.current && !ref.current.contains(e.target as Node)) { + setOpen(false); + } + } + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, []); + + return ( +
+ + + {open && ( +
+ + + + + {emptyMessage} + + {options.map((option) => ( + { + onChange(option.value); + setOpen(false); + }} + className={cn( + "flex items-center gap-2 px-3.5 py-2.5 text-sm cursor-pointer", + "data-[selected=true]:bg-blue-50 data-[selected=true]:text-blue-600", + )} + > + + {option.label} + + ))} + + +
+ )} +
+ ); +} diff --git a/apps/pharmanager/src/components/ui/index.ts b/apps/pharmanager/src/components/ui/index.ts index 956dc57..3ce292d 100644 --- a/apps/pharmanager/src/components/ui/index.ts +++ b/apps/pharmanager/src/components/ui/index.ts @@ -1,3 +1,7 @@ export { Button, type ButtonProps, buttonVariants } from "./Button"; export { Input, type InputProps, inputVariants } from "./Input"; export { Textarea, type TextareaProps, textareaVariants } from "./Textarea"; +export { Combobox } from "./Combobox"; +export type { ComboboxOption } from "./Combobox"; +export { Checkbox } from "./Checkbox"; +export type { CheckboxProps } from "./Checkbox"; diff --git a/apps/pharmanager/src/routes/products/add.tsx b/apps/pharmanager/src/routes/products/add.tsx index 9bb70b3..18e0475 100644 --- a/apps/pharmanager/src/routes/products/add.tsx +++ b/apps/pharmanager/src/routes/products/add.tsx @@ -13,7 +13,7 @@ import { useListDistributors, trpc, } from "shared-react"; -import { Button, Input, buttonVariants } from "#/components/ui"; +import { Button, Input, Checkbox, buttonVariants } from "#/components/ui"; import { CreateProductInput } from "@repo/shared"; const formSchema = CreateProductInput.extend({ @@ -361,23 +361,15 @@ function AddProductPage() {
- - + +
diff --git a/apps/pharmanager/src/routes/stock/add.tsx b/apps/pharmanager/src/routes/stock/add.tsx index 620d951..81a48bb 100644 --- a/apps/pharmanager/src/routes/stock/add.tsx +++ b/apps/pharmanager/src/routes/stock/add.tsx @@ -13,7 +13,7 @@ import { useListDistributors, trpc, } from "shared-react"; -import { Button, Input, buttonVariants } from "#/components/ui"; +import { Button, Input, Checkbox, buttonVariants, Combobox } from "#/components/ui"; import { CreateStockBatchInput } from "@repo/shared"; const formSchema = CreateStockBatchInput.extend({ @@ -53,6 +53,8 @@ function AddStockPage() { register, handleSubmit, reset, + watch, + setValue, formState: { errors }, } = useForm({ resolver: zodResolver(formSchema), @@ -69,6 +71,8 @@ function AddStockPage() { }, }); + const selectedProductId = watch("product_id"); + useEffect(() => { if (isEditing && existingBatch) { reset({ @@ -135,17 +139,16 @@ function AddStockPage() { - + ({ + value: String(p.id), + label: `${p.name} — ${p.brand}`, + }))} + value={selectedProductId ? String(selectedProductId) : ""} + onChange={(val) => setValue("product_id", Number(val))} + placeholder="Search and select a product..." + emptyMessage="No products found." + /> {errors.product_id &&

{errors.product_id.message}

}
@@ -215,16 +218,10 @@ function AddStockPage() {
- +