common components

This commit is contained in:
shafi54 2026-05-23 14:47:12 +05:30
parent 11eca48354
commit c0fd8671e4
11 changed files with 203 additions and 78 deletions

View file

@ -22,6 +22,7 @@
"@tanstack/react-router-devtools": "latest", "@tanstack/react-router-devtools": "latest",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
"@tanstack/router-plugin": "^1.132.0", "@tanstack/router-plugin": "^1.132.0",
"class-variance-authority": "^0.7.1",
"lucide-react": "^0.545.0", "lucide-react": "^0.545.0",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",

View file

@ -0,0 +1,53 @@
import { forwardRef, type ComponentPropsWithoutRef } from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "#/lib/cn";
export const buttonVariants = cva(
"inline-flex items-center justify-center gap-1.5 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none",
{
variants: {
variant: {
primary: "bg-blue-600 text-white hover:bg-blue-700",
danger: "bg-red-600 text-white hover:bg-red-700",
outline:
"border border-slate-200 bg-white text-slate-700 hover:bg-slate-50",
ghost: "text-slate-500 hover:bg-slate-100 hover:text-slate-900",
"ghost-blue":
"text-slate-500 hover:text-blue-600 hover:bg-blue-50",
"ghost-red":
"text-slate-500 hover:text-red-600 hover:bg-red-50",
"ghost-dark":
"text-slate-400 hover:bg-slate-800 hover:text-white",
},
size: {
default: "px-4 py-2",
sm: "px-3 py-1.5 text-xs",
lg: "px-6 py-2.5",
icon: "w-8 h-8 p-0",
"icon-sm": "w-4 h-4 p-0 rounded-full",
},
},
defaultVariants: {
variant: "primary",
size: "default",
},
},
);
export interface ButtonProps
extends ComponentPropsWithoutRef<"button">,
VariantProps<typeof buttonVariants> {}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<button
ref={ref}
className={cn(buttonVariants({ variant, size }), className)}
{...props}
/>
);
},
);
Button.displayName = "Button";

View file

@ -0,0 +1,39 @@
import { forwardRef, type ComponentPropsWithoutRef } from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "#/lib/cn";
export const inputVariants = cva(
"px-3.5 py-2.5 border rounded-md text-sm text-slate-900 bg-white transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100 w-full",
{
variants: {
variant: {
default:
"border-slate-200 focus:border-blue-600",
error: "border-red-600",
transparent: "border-none bg-transparent",
},
},
defaultVariants: {
variant: "default",
},
},
);
export interface InputProps
extends ComponentPropsWithoutRef<"input">,
VariantProps<typeof inputVariants> {}
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ className, variant, type, ...props }, ref) => {
return (
<input
ref={ref}
type={type}
className={cn(inputVariants({ variant }), className)}
{...props}
/>
);
},
);
Input.displayName = "Input";

View file

@ -0,0 +1,36 @@
import { forwardRef, type ComponentPropsWithoutRef } from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "#/lib/cn";
export const textareaVariants = cva(
"w-full px-3.5 py-2.5 border rounded-md text-sm text-slate-900 bg-white resize-y min-h-[80px] transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100",
{
variants: {
variant: {
default: "border-slate-200 focus:border-blue-600",
error: "border-red-600",
},
},
defaultVariants: {
variant: "default",
},
},
);
export interface TextareaProps
extends ComponentPropsWithoutRef<"textarea">,
VariantProps<typeof textareaVariants> {}
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, variant, ...props }, ref) => {
return (
<textarea
ref={ref}
className={cn(textareaVariants({ variant }), className)}
{...props}
/>
);
},
);
Textarea.displayName = "Textarea";

View file

@ -0,0 +1,3 @@
export { Button, type ButtonProps, buttonVariants } from "./Button";
export { Input, type InputProps, inputVariants } from "./Input";
export { Textarea, type TextareaProps, textareaVariants } from "./Textarea";

View file

@ -1,5 +1,6 @@
import { createFileRoute, Link } from "@tanstack/react-router"; import { createFileRoute, Link } from "@tanstack/react-router";
import { ArrowLeft, Pencil, Trash2, Truck } from "lucide-react"; import { ArrowLeft, Pencil, Trash2, Truck } from "lucide-react";
import { Button, buttonVariants } from "#/components/ui";
import { import {
useGetDistributorById, useGetDistributorById,
useRemoveDistributor, useRemoveDistributor,
@ -56,7 +57,7 @@ function DistributorDetailsPage() {
</p> </p>
<Link <Link
to="/distributors" to="/distributors"
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors" className={buttonVariants({ variant: "primary" })}
> >
Back to Distributors Back to Distributors
</Link> </Link>
@ -124,21 +125,14 @@ function DistributorDetailsPage() {
</div> </div>
<div className="flex items-center gap-2.5 mt-6 pt-5 border-t border-slate-200"> <div className="flex items-center gap-2.5 mt-6 pt-5 border-t border-slate-200">
<button <Button variant="primary">
type="button"
className="inline-flex items-center gap-1.5 px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors"
>
<Pencil className="w-[15px] h-[15px]" /> <Pencil className="w-[15px] h-[15px]" />
Edit Distributor Edit Distributor
</button> </Button>
<button <Button variant="danger" onClick={handleDelete}>
type="button"
onClick={handleDelete}
className="inline-flex items-center gap-1.5 px-4 py-2 bg-red-600 text-white rounded-md text-sm font-medium hover:bg-red-700 transition-colors"
>
<Trash2 className="w-[15px] h-[15px]" /> <Trash2 className="w-[15px] h-[15px]" />
Delete Distributor Delete Distributor
</button> </Button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -4,6 +4,7 @@ import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { useCreateDistributor, trpc } from "shared-react"; import { useCreateDistributor, trpc } from "shared-react";
import { Button, Input, Textarea, buttonVariants } from "#/components/ui";
const formSchema = z.object({ const formSchema = z.object({
agency: z.string().min(1, "Agency name is required"), agency: z.string().min(1, "Agency name is required"),
@ -81,11 +82,11 @@ function AddDistributorPage() {
Agency Name{" "} Agency Name{" "}
<span className="text-red-600 ml-0.5">*</span> <span className="text-red-600 ml-0.5">*</span>
</label> </label>
<input <Input
type="text" type="text"
variant={errors.agency ? "error" : "default"}
{...register("agency")} {...register("agency")}
placeholder="e.g. MediDistributors" placeholder="e.g. MediDistributors"
className={`w-full px-3.5 py-2.5 border rounded-md text-sm text-slate-900 bg-white transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100 ${errors.agency ? "border-red-600" : "border-slate-200 focus:border-blue-600"}`}
/> />
{errors.agency && ( {errors.agency && (
<p className="text-sm text-red-600 mt-1"> <p className="text-sm text-red-600 mt-1">
@ -99,11 +100,11 @@ function AddDistributorPage() {
Contact Person{" "} Contact Person{" "}
<span className="text-red-600 ml-0.5">*</span> <span className="text-red-600 ml-0.5">*</span>
</label> </label>
<input <Input
type="text" type="text"
variant={errors.contact ? "error" : "default"}
{...register("contact")} {...register("contact")}
placeholder="e.g. Rahul Mehta" placeholder="e.g. Rahul Mehta"
className={`w-full px-3.5 py-2.5 border rounded-md text-sm text-slate-900 bg-white transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100 ${errors.contact ? "border-red-600" : "border-slate-200 focus:border-blue-600"}`}
/> />
{errors.contact && ( {errors.contact && (
<p className="text-sm text-red-600 mt-1"> <p className="text-sm text-red-600 mt-1">
@ -117,8 +118,9 @@ function AddDistributorPage() {
Contact Mobile{" "} Contact Mobile{" "}
<span className="text-red-600 ml-0.5">*</span> <span className="text-red-600 ml-0.5">*</span>
</label> </label>
<input <Input
type="tel" type="tel"
variant={errors.mobile ? "error" : "default"}
{...register("mobile", { {...register("mobile", {
onChange: (e) => { onChange: (e) => {
e.target.value = e.target.value e.target.value = e.target.value
@ -127,7 +129,6 @@ function AddDistributorPage() {
}, },
})} })}
placeholder="e.g. 9876500001" placeholder="e.g. 9876500001"
className={`w-full px-3.5 py-2.5 border rounded-md text-sm text-slate-900 bg-white transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100 ${errors.mobile ? "border-red-600" : "border-slate-200 focus:border-blue-600"}`}
/> />
{errors.mobile && ( {errors.mobile && (
<p className="text-sm text-red-600 mt-1"> <p className="text-sm text-red-600 mt-1">
@ -140,13 +141,12 @@ function AddDistributorPage() {
<label className="block text-sm font-medium text-slate-900 mb-1.5"> <label className="block text-sm font-medium text-slate-900 mb-1.5">
Address Address
</label> </label>
<textarea <Textarea
{...register("address", { {...register("address", {
setValueAs: (v: string) => v || null, setValueAs: (v: string) => v || null,
})} })}
rows={3} rows={3}
placeholder="Full address of the distributor" placeholder="Full address of the distributor"
className="w-full px-3.5 py-2.5 border border-slate-200 rounded-md text-sm text-slate-900 bg-white resize-y min-h-[80px] transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100 focus:border-blue-600"
/> />
</div> </div>
</div> </div>
@ -154,20 +154,19 @@ function AddDistributorPage() {
<div className="flex justify-end gap-3 pt-5 mt-2 border-t border-slate-200"> <div className="flex justify-end gap-3 pt-5 mt-2 border-t border-slate-200">
<Link <Link
to="/distributors" to="/distributors"
className="inline-flex items-center px-5 py-2.5 border border-slate-200 rounded-md text-sm font-medium text-slate-700 bg-white hover:bg-slate-50 transition-colors" className={buttonVariants({ variant: "outline" })}
> >
Cancel Cancel
</Link> </Link>
<button <Button
type="submit" type="submit"
disabled={createMutation.isPending} disabled={createMutation.isPending}
className="inline-flex items-center gap-1.5 px-6 py-2.5 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 disabled:opacity-50 transition-colors"
> >
<Plus className="w-[15px] h-[15px]" /> <Plus className="w-[15px] h-[15px]" />
{createMutation.isPending {createMutation.isPending
? "Saving..." ? "Saving..."
: "Save Distributor"} : "Save Distributor"}
</button> </Button>
</div> </div>
{createMutation.error && ( {createMutation.error && (

View file

@ -3,6 +3,7 @@ import { createFileRoute, Link } from "@tanstack/react-router";
import { Search, Plus, Pencil, Trash2, Truck } from "lucide-react"; import { Search, Plus, Pencil, Trash2, Truck } from "lucide-react";
import { GridTable } from "#/components/GridTable"; import { GridTable } from "#/components/GridTable";
import type { GridTableColumn } from "#/components/GridTable"; import type { GridTableColumn } from "#/components/GridTable";
import { Button, buttonVariants } from "#/components/ui";
import { import {
useListDistributors, useListDistributors,
useRemoveDistributor, useRemoveDistributor,
@ -68,21 +69,21 @@ function makeColumns(
size: 90, size: 90,
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex items-center justify-center gap-1"> <div className="flex items-center justify-center gap-1">
<button <Button
type="button" variant="ghost-blue"
className="w-8 h-8 flex items-center justify-center rounded-md text-slate-500 hover:text-blue-600 hover:bg-blue-50 transition-colors" size="icon"
aria-label={`Edit ${row.agency}`} aria-label={`Edit ${row.agency}`}
> >
<Pencil className="w-[15px] h-[15px]" /> <Pencil className="w-[15px] h-[15px]" />
</button> </Button>
<button <Button
type="button" variant="ghost-red"
className="w-8 h-8 flex items-center justify-center rounded-md text-slate-500 hover:text-red-600 hover:bg-red-50 transition-colors" size="icon"
aria-label={`Delete ${row.agency}`} aria-label={`Delete ${row.agency}`}
onClick={() => onDelete(row)} onClick={() => onDelete(row)}
> >
<Trash2 className="w-[15px] h-[15px]" /> <Trash2 className="w-[15px] h-[15px]" />
</button> </Button>
</div> </div>
), ),
}, },
@ -161,7 +162,7 @@ function DistributorsIndexPage() {
</div> </div>
<Link <Link
to="/distributors/add" to="/distributors/add"
className="inline-flex items-center gap-1.5 px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors" className={buttonVariants({ variant: "primary" })}
> >
<Plus className="w-[15px] h-[15px]" /> <Plus className="w-[15px] h-[15px]" />
Add Distributor Add Distributor

View file

@ -7,6 +7,7 @@ import {
Layers, Layers,
ImageIcon, ImageIcon,
} from "lucide-react"; } from "lucide-react";
import { Button, buttonVariants } from "#/components/ui";
import { useGetStorageById, useRemoveStorage, trpc } from "shared-react"; import { useGetStorageById, useRemoveStorage, trpc } from "shared-react";
export const Route = createFileRoute("/storage/$id")({ export const Route = createFileRoute("/storage/$id")({
@ -58,7 +59,7 @@ function StorageDetailsPage() {
</p> </p>
<Link <Link
to="/storage" to="/storage"
className="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors" className={buttonVariants({ variant: "primary" })}
> >
Back to Storage Back to Storage
</Link> </Link>
@ -91,21 +92,14 @@ function StorageDetailsPage() {
)} )}
</div> </div>
<div className="flex gap-2"> <div className="flex gap-2">
<button <Button variant="primary">
type="button"
className="inline-flex items-center gap-1.5 px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors"
>
<Pencil className="w-[15px] h-[15px]" /> <Pencil className="w-[15px] h-[15px]" />
Edit Edit
</button> </Button>
<button <Button variant="danger" onClick={handleDelete}>
type="button"
onClick={handleDelete}
className="inline-flex items-center gap-1.5 px-4 py-2 bg-red-600 text-white rounded-md text-sm font-medium hover:bg-red-700 transition-colors"
>
<Trash2 className="w-[15px] h-[15px]" /> <Trash2 className="w-[15px] h-[15px]" />
Delete Delete
</button> </Button>
</div> </div>
</div> </div>

View file

@ -5,6 +5,7 @@ import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { useCreateStorage, trpc } from "shared-react"; import { useCreateStorage, trpc } from "shared-react";
import { Button, Input, Textarea, buttonVariants } from "#/components/ui";
const formSchema = z.object({ const formSchema = z.object({
name: z.string().min(1, "Rack name is required"), name: z.string().min(1, "Rack name is required"),
@ -132,11 +133,11 @@ function AddStoragePage() {
<label className="block text-sm font-medium text-slate-900 mb-1.5"> <label className="block text-sm font-medium text-slate-900 mb-1.5">
Rack Name <span className="text-red-600 ml-0.5">*</span> Rack Name <span className="text-red-600 ml-0.5">*</span>
</label> </label>
<input <Input
type="text" type="text"
variant={errors.name ? "error" : "default"}
{...register("name")} {...register("name")}
placeholder="e.g. Rack A-01, Cold Storage 1" placeholder="e.g. Rack A-01, Cold Storage 1"
className={`w-full px-3.5 py-2.5 border rounded-md text-sm text-slate-900 bg-white transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100 ${errors.name ? "border-red-600" : "border-slate-200 focus:border-blue-600"}`}
/> />
{errors.name && ( {errors.name && (
<p className="text-sm text-red-600 mt-1"> <p className="text-sm text-red-600 mt-1">
@ -149,13 +150,12 @@ function AddStoragePage() {
<label className="block text-sm font-medium text-slate-900 mb-1.5"> <label className="block text-sm font-medium text-slate-900 mb-1.5">
Description Description
</label> </label>
<textarea <Textarea
{...register("description", { {...register("description", {
setValueAs: (v: string) => v || null, setValueAs: (v: string) => v || null,
})} })}
placeholder="Describe what this rack stores, its location, special conditions..." placeholder="Describe what this rack stores, its location, special conditions..."
rows={3} rows={3}
className="w-full px-3.5 py-2.5 border border-slate-200 rounded-md text-sm text-slate-900 bg-white resize-y min-h-[80px] transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100 focus:border-blue-600"
/> />
</div> </div>
@ -168,21 +168,22 @@ function AddStoragePage() {
onRemove={handleRemoveAlias} onRemove={handleRemoveAlias}
/> />
<div className="flex gap-2"> <div className="flex gap-2">
<input <Input
type="text" type="text"
value={aliasInput} value={aliasInput}
onChange={(e) => setAliasInput(e.target.value)} onChange={(e) => setAliasInput(e.target.value)}
onKeyDown={handleAliasKeyDown} onKeyDown={handleAliasKeyDown}
placeholder="Type an alias and press Enter or comma" placeholder="Type an alias and press Enter or comma"
className="flex-1 px-3.5 py-2.5 border border-slate-200 rounded-md text-sm text-slate-900 bg-white transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100 focus:border-blue-600" className="flex-1 w-auto"
/> />
<button <Button
type="button" type="button"
variant="outline"
size="sm"
onClick={handleAddAlias} onClick={handleAddAlias}
className="px-3 py-2.5 border border-slate-200 rounded-md text-sm font-medium text-slate-700 bg-white hover:bg-slate-50 transition-colors"
> >
Add Add
</button> </Button>
</div> </div>
<p className="text-xs text-slate-500 mt-1"> <p className="text-xs text-slate-500 mt-1">
Common names or abbreviations for this rack Common names or abbreviations for this rack
@ -198,21 +199,22 @@ function AddStoragePage() {
onRemove={handleRemoveImage} onRemove={handleRemoveImage}
/> />
<div className="flex gap-2"> <div className="flex gap-2">
<input <Input
type="text" type="text"
value={imageInput} value={imageInput}
onChange={(e) => setImageInput(e.target.value)} onChange={(e) => setImageInput(e.target.value)}
onKeyDown={handleImageKeyDown} onKeyDown={handleImageKeyDown}
placeholder="Image filename (e.g. rack-a01-photo.jpg)" placeholder="Image filename (e.g. rack-a01-photo.jpg)"
className="flex-1 px-3.5 py-2.5 border border-slate-200 rounded-md text-sm text-slate-900 bg-white transition-colors focus:outline-none focus:ring-[3px] focus:ring-blue-100 focus:border-blue-600" className="flex-1 w-auto"
/> />
<button <Button
type="button" type="button"
variant="outline"
size="sm"
onClick={handleAddImage} onClick={handleAddImage}
className="px-3 py-2.5 border border-slate-200 rounded-md text-sm font-medium text-slate-700 bg-white hover:bg-slate-50 transition-colors"
> >
Add Add
</button> </Button>
</div> </div>
<p className="text-xs text-slate-500 mt-1"> <p className="text-xs text-slate-500 mt-1">
Add filenames or URLs for rack photos Add filenames or URLs for rack photos
@ -220,17 +222,16 @@ function AddStoragePage() {
</div> </div>
<div className="flex gap-3 pt-5 border-t border-slate-200"> <div className="flex gap-3 pt-5 border-t border-slate-200">
<button <Button
type="submit" type="submit"
disabled={createMutation.isPending} disabled={createMutation.isPending}
className="inline-flex items-center gap-1.5 px-5 py-2.5 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 disabled:opacity-50 transition-colors"
> >
<Plus className="w-[15px] h-[15px]" /> <Plus className="w-[15px] h-[15px]" />
{createMutation.isPending ? "Saving..." : "Save Rack"} {createMutation.isPending ? "Saving..." : "Save Rack"}
</button> </Button>
<Link <Link
to="/storage" to="/storage"
className="inline-flex items-center px-5 py-2.5 border border-slate-200 rounded-md text-sm font-medium text-slate-700 bg-white hover:bg-slate-50 transition-colors" className={buttonVariants({ variant: "outline" })}
> >
Cancel Cancel
</Link> </Link>
@ -262,13 +263,14 @@ function AliasList({
className="inline-flex items-center gap-1 px-2.5 py-1 bg-slate-100 rounded-full text-sm" className="inline-flex items-center gap-1 px-2.5 py-1 bg-slate-100 rounded-full text-sm"
> >
{alias} {alias}
<button <Button
type="button" type="button"
variant="ghost-red"
size="icon-sm"
onClick={() => onRemove(i)} onClick={() => onRemove(i)}
className="w-4 h-4 flex items-center justify-center rounded-full hover:bg-red-600 hover:text-white transition-colors text-slate-500"
> >
<X className="w-3 h-3" /> <X className="w-3 h-3" />
</button> </Button>
</span> </span>
))} ))}
</div> </div>
@ -291,13 +293,15 @@ function ImageList({
className="relative w-20 h-20 rounded-md border border-slate-200 bg-slate-50 flex items-center justify-center" className="relative w-20 h-20 rounded-md border border-slate-200 bg-slate-50 flex items-center justify-center"
> >
<ImageIcon className="w-8 h-8 text-slate-300" /> <ImageIcon className="w-8 h-8 text-slate-300" />
<button <Button
type="button" type="button"
variant="danger"
size="icon-sm"
className="absolute -top-1.5 -right-1.5 w-5 h-5 rounded-full text-xs"
onClick={() => onRemove(i)} onClick={() => onRemove(i)}
className="absolute -top-1.5 -right-1.5 w-5 h-5 rounded-full bg-red-600 text-white flex items-center justify-center text-xs hover:bg-red-700 transition-colors"
> >
<X className="w-3 h-3" /> <X className="w-3 h-3" />
</button> </Button>
</div> </div>
))} ))}
</div> </div>

View file

@ -3,6 +3,7 @@ import { createFileRoute, Link } from "@tanstack/react-router";
import { Search, Plus, Pencil, Trash2, Warehouse } from "lucide-react"; import { Search, Plus, Pencil, Trash2, Warehouse } from "lucide-react";
import { GridTable } from "#/components/GridTable"; import { GridTable } from "#/components/GridTable";
import type { GridTableColumn } from "#/components/GridTable"; import type { GridTableColumn } from "#/components/GridTable";
import { Button, buttonVariants } from "#/components/ui";
import { useListStorage, useRemoveStorage, trpc } from "shared-react"; import { useListStorage, useRemoveStorage, trpc } from "shared-react";
interface Rack { interface Rack {
@ -61,21 +62,21 @@ function makeColumns(
size: 80, size: 80,
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex items-center justify-center gap-1"> <div className="flex items-center justify-center gap-1">
<button <Button
type="button" variant="ghost-blue"
className="w-8 h-8 flex items-center justify-center rounded-md text-slate-500 hover:text-blue-600 hover:bg-blue-50 transition-colors" size="icon"
aria-label={`Edit ${row.name}`} aria-label={`Edit ${row.name}`}
> >
<Pencil className="w-[15px] h-[15px]" /> <Pencil className="w-[15px] h-[15px]" />
</button> </Button>
<button <Button
type="button" variant="ghost-red"
className="w-8 h-8 flex items-center justify-center rounded-md text-slate-500 hover:text-red-600 hover:bg-red-50 transition-colors" size="icon"
aria-label={`Delete ${row.name}`} aria-label={`Delete ${row.name}`}
onClick={() => onDelete(row)} onClick={() => onDelete(row)}
> >
<Trash2 className="w-[15px] h-[15px]" /> <Trash2 className="w-[15px] h-[15px]" />
</button> </Button>
</div> </div>
), ),
}, },
@ -153,7 +154,7 @@ function StorageIndexPage() {
</div> </div>
<Link <Link
to="/storage/add" to="/storage/add"
className="inline-flex items-center gap-1.5 px-4 py-2 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition-colors" className={buttonVariants({ variant: "primary" })}
> >
<Plus className="w-[15px] h-[15px]" /> <Plus className="w-[15px] h-[15px]" />
Add Rack Add Rack