feat(ui): add tooltips to action icons in admins table

This commit introduces the `react-tooltip` library to improve user interface clarity and provide a better user experience.

Tooltips have been added to the action icons (Edit, Delete, Change Status) in the Admins table. This provides users with clear, on-hover information about the function of each icon, enhancing usability and reducing ambiguity.

The implementation involved:
- Adding `react-tooltip` as a project dependency.
- Importing the required CSS in the root layout.
- Integrating the `Tooltip` component within the Admins table.
This commit is contained in:
AmirReza Jamali
2025-09-26 20:48:59 +03:30
parent 85d95530a8
commit da250b805d
11 changed files with 157 additions and 4 deletions
+46
View File
@@ -32,6 +32,7 @@
"react-paginate": "^8.3.0", "react-paginate": "^8.3.0",
"react-switch": "^7.1.0", "react-switch": "^7.1.0",
"react-toastify": "^11.0.5", "react-toastify": "^11.0.5",
"react-tooltip": "^5.29.1",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"zod": "^4.1.5" "zod": "^4.1.5"
}, },
@@ -233,6 +234,31 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@floating-ui/core": {
"version": "1.7.3",
"resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.3.tgz",
"integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.7.4",
"resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.4.tgz",
"integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.7.3",
"@floating-ui/utils": "^0.2.10"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.10",
"resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz",
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT"
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -2171,6 +2197,12 @@
"url": "https://polar.sh/cva" "url": "https://polar.sh/cva"
} }
}, },
"node_modules/classnames": {
"version": "2.5.1",
"resolved": "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz",
"integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==",
"license": "MIT"
},
"node_modules/client-only": { "node_modules/client-only": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@@ -5391,6 +5423,20 @@
"react-dom": "^18 || ^19" "react-dom": "^18 || ^19"
} }
}, },
"node_modules/react-tooltip": {
"version": "5.29.1",
"resolved": "https://registry.npmmirror.com/react-tooltip/-/react-tooltip-5.29.1.tgz",
"integrity": "sha512-rmJmEb/p99xWhwmVT7F7riLG08wwKykjHiMGbDPloNJk3tdI73oHsVOwzZ4SRjqMdd5/xwb/4nmz0RcoMfY7Bw==",
"license": "MIT",
"dependencies": {
"@floating-ui/dom": "^1.6.1",
"classnames": "^2.3.0"
},
"peerDependencies": {
"react": ">=16.14.0",
"react-dom": ">=16.14.0"
}
},
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+1
View File
@@ -33,6 +33,7 @@
"react-paginate": "^8.3.0", "react-paginate": "^8.3.0",
"react-switch": "^7.1.0", "react-switch": "^7.1.0",
"react-toastify": "^11.0.5", "react-toastify": "^11.0.5",
"react-tooltip": "^5.29.1",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"zod": "^4.1.5" "zod": "^4.1.5"
}, },
+1
View File
@@ -1,5 +1,6 @@
import "@/css/satoshi.css"; import "@/css/satoshi.css";
import "@/css/style.css"; import "@/css/style.css";
import "react-tooltip/dist/react-tooltip.css";
import { ConditionalLayout } from "@/components/Layouts/conditional-layout"; import { ConditionalLayout } from "@/components/Layouts/conditional-layout";
import "flatpickr/dist/flatpickr.min.css"; import "flatpickr/dist/flatpickr.min.css";
+16
View File
@@ -3,6 +3,7 @@
import { PencilSquareIcon, TrashIcon, UserSettingIcon } from "@/assets/icons"; import { PencilSquareIcon, TrashIcon, UserSettingIcon } from "@/assets/icons";
import ConfirmationModal from "@/components/ui/ConfirmationModal"; import ConfirmationModal from "@/components/ui/ConfirmationModal";
import Modal from "@/components/ui/modal"; import Modal from "@/components/ui/modal";
import { _TooltipDefaultParams } from "@/constants/tooltip";
import Status from "@/components/ui/Status"; import Status from "@/components/ui/Status";
import { import {
Table, Table,
@@ -15,6 +16,7 @@ import {
import TableSkeleton from "@/components/ui/TableSkeleton"; import TableSkeleton from "@/components/ui/TableSkeleton";
import { AdminStatus } from "@/constants/enums"; import { AdminStatus } from "@/constants/enums";
import Link from "next/link"; import Link from "next/link";
import { Tooltip } from "react-tooltip";
import ChangeStatusModalContent from "./ChangeStatusModalContent"; import ChangeStatusModalContent from "./ChangeStatusModalContent";
import { useAdminsPresenter } from "./useAdminsPresenter"; import { useAdminsPresenter } from "./useAdminsPresenter";
@@ -82,29 +84,43 @@ const AdminsTable = () => {
<TableCell className="text-start xl:pr-7.5"> <TableCell className="text-start xl:pr-7.5">
<div className="flex items-center justify-start gap-x-3.5"> <div className="flex items-center justify-start gap-x-3.5">
<button <button
data-tooltip-id="delete"
data-tooltip-content="Delete"
data-tooltip-place="top"
className="hover:text-red-500" className="hover:text-red-500"
onClick={() => { onClick={() => {
setCurrentUser(item); setCurrentUser(item);
setIsModalOpen(true); setIsModalOpen(true);
}} }}
> >
<Tooltip {..._TooltipDefaultParams({ id: "delete" })} />
<span className="sr-only">Delete Admin </span> <span className="sr-only">Delete Admin </span>
<TrashIcon /> <TrashIcon />
</button> </button>
<button <button
data-tooltip-id="change-status"
data-tooltip-content="Change status"
data-tooltip-place="top"
className="hover:text-yellow-dark" className="hover:text-yellow-dark"
onClick={() => { onClick={() => {
setCurrentUser(item); setCurrentUser(item);
setIsChangeStatusModalOpen(true); setIsChangeStatusModalOpen(true);
}} }}
> >
<Tooltip
{..._TooltipDefaultParams({ id: "change-status" })}
/>
<span className="sr-only">Change Admin Status</span> <span className="sr-only">Change Admin Status</span>
<UserSettingIcon /> <UserSettingIcon />
</button> </button>
<button <button
data-tooltip-id="edit"
data-tooltip-content="Edit"
data-tooltip-place="top"
className="hover:text-primary" className="hover:text-primary"
onClick={() => router.push(`/admins/edit/${item.id}`)} onClick={() => router.push(`/admins/edit/${item.id}`)}
> >
<Tooltip {..._TooltipDefaultParams({ id: "edit" })} />
<span className="sr-only">Edit Admin </span> <span className="sr-only">Edit Admin </span>
<PencilSquareIcon /> <PencilSquareIcon />
</button> </button>
@@ -2,6 +2,7 @@
import { PencilSquareIcon, TrashIcon } from "@/assets/icons"; import { PencilSquareIcon, TrashIcon } from "@/assets/icons";
import { Switch } from "@/components/FormElements/switch"; import { Switch } from "@/components/FormElements/switch";
import ConfirmationModal from "@/components/ui/ConfirmationModal"; import ConfirmationModal from "@/components/ui/ConfirmationModal";
import { _TooltipDefaultParams } from "@/constants/tooltip";
import IsVisible from "@/components/ui/IsVisible"; import IsVisible from "@/components/ui/IsVisible";
import { import {
Table, Table,
@@ -14,6 +15,7 @@ import {
import TableSkeleton from "@/components/ui/TableSkeleton"; import TableSkeleton from "@/components/ui/TableSkeleton";
import { unixToDate } from "@/utils/shared"; import { unixToDate } from "@/utils/shared";
import Link from "next/link"; import Link from "next/link";
import { Tooltip } from "react-tooltip";
import { Skeleton } from "../../../ui/skeleton"; import { Skeleton } from "../../../ui/skeleton";
import useCompanyCategoriesPresenter from "./useCompanyCategoriesPresenter"; import useCompanyCategoriesPresenter from "./useCompanyCategoriesPresenter";
@@ -87,21 +89,29 @@ const CompanyCategoriesTable = () => {
<TableCell className="text-start xl:pr-7.5"> <TableCell className="text-start xl:pr-7.5">
<div className="flex items-center justify-start gap-x-3.5"> <div className="flex items-center justify-start gap-x-3.5">
<button <button
data-tooltip-id="delete"
data-tooltip-content="Delete"
data-tooltip-place="top"
className="hover:text-red-500" className="hover:text-red-500"
onClick={() => { onClick={() => {
setCurrentCategory(category); setCurrentCategory(category);
setIsModalOpen(true); setIsModalOpen(true);
}} }}
> >
<Tooltip {..._TooltipDefaultParams({ id: "delete" })} />
<span className="sr-only">Delete Admin </span> <span className="sr-only">Delete Admin </span>
<TrashIcon /> <TrashIcon />
</button> </button>
<button <button
data-tooltip-id="edit"
data-tooltip-content="Edit"
data-tooltip-place="top"
className="hover:text-primary" className="hover:text-primary"
onClick={() => onClick={() =>
router.push(`${pathName}/edit/${category.id}`) router.push(`${pathName}/edit/${category.id}`)
} }
> >
<Tooltip {..._TooltipDefaultParams({ id: "edit" })} />
<span className="sr-only">Edit Category</span> <span className="sr-only">Edit Category</span>
<PencilSquareIcon /> <PencilSquareIcon />
</button> </button>
+14
View File
@@ -1,6 +1,7 @@
"use client"; "use client";
import { ExclamationIcon, PencilSquareIcon, TrashIcon } from "@/assets/icons"; import { ExclamationIcon, PencilSquareIcon, TrashIcon } from "@/assets/icons";
import ConfirmationModal from "@/components/ui/ConfirmationModal"; import ConfirmationModal from "@/components/ui/ConfirmationModal";
import { _TooltipDefaultParams } from "@/constants/tooltip";
import { import {
Table, Table,
TableBody, TableBody,
@@ -12,6 +13,7 @@ import {
import TableSkeleton from "@/components/ui/TableSkeleton"; import TableSkeleton from "@/components/ui/TableSkeleton";
import { formatPhoneNumber } from "@/utils/shared"; import { formatPhoneNumber } from "@/utils/shared";
import Link from "next/link"; import Link from "next/link";
import { Tooltip } from "react-tooltip";
import { useCompanyListPresenter } from "./useCompanyListPresenter"; import { useCompanyListPresenter } from "./useCompanyListPresenter";
interface IProps {} interface IProps {}
@@ -88,30 +90,42 @@ const CompaniesTable = ({}: IProps) => {
<TableCell className="text-start xl:pr-7.5"> <TableCell className="text-start xl:pr-7.5">
<div className="flex items-center justify-start gap-x-3.5"> <div className="flex items-center justify-start gap-x-3.5">
<button <button
data-tooltip-id="delete"
data-tooltip-content="Delete"
data-tooltip-place="top"
className="hover:text-red-500" className="hover:text-red-500"
onClick={() => { onClick={() => {
setCurrentCompany(company); setCurrentCompany(company);
setIsModalOpen(true); setIsModalOpen(true);
}} }}
> >
<Tooltip {..._TooltipDefaultParams({ id: "delete" })} />
<span className="sr-only">Delete Company </span> <span className="sr-only">Delete Company </span>
<TrashIcon /> <TrashIcon />
</button> </button>
<button <button
data-tooltip-id="edit"
data-tooltip-content="Edit"
data-tooltip-place="top"
className="hover:text-primary" className="hover:text-primary"
onClick={() => onClick={() =>
router.push(`${pathName}/edit/${company.id}`) router.push(`${pathName}/edit/${company.id}`)
} }
> >
<Tooltip {..._TooltipDefaultParams({ id: "edit" })} />
<span className="sr-only">Edit Company </span> <span className="sr-only">Edit Company </span>
<PencilSquareIcon /> <PencilSquareIcon />
</button> </button>
<button <button
data-tooltip-id="feedback"
data-tooltip-content="Feedback"
data-tooltip-place="top"
className="text-gray-40 rounded-full bg-gray-dark bg-opacity-5 hover:text-green-dark dark:bg-gray dark:bg-opacity-5" className="text-gray-40 rounded-full bg-gray-dark bg-opacity-5 hover:text-green-dark dark:bg-gray dark:bg-opacity-5"
onClick={() => { onClick={() => {
router.push(`${pathName}/feedback/${company.id}`); router.push(`${pathName}/feedback/${company.id}`);
}} }}
> >
<Tooltip {..._TooltipDefaultParams({ id: "feedback" })} />
<ExclamationIcon /> <ExclamationIcon />
</button> </button>
</div> </div>
+20
View File
@@ -1,6 +1,7 @@
"use client"; "use client";
import { ExclamationIcon, PencilSquareIcon, TrashIcon } from "@/assets/icons"; import { ExclamationIcon, PencilSquareIcon, TrashIcon } from "@/assets/icons";
import ConfirmationModal from "@/components/ui/ConfirmationModal"; import ConfirmationModal from "@/components/ui/ConfirmationModal";
import { _TooltipDefaultParams } from "@/constants/tooltip";
import { import {
Table, Table,
TableBody, TableBody,
@@ -17,6 +18,7 @@ import Modal from "@/components/ui/modal";
import Status from "@/components/ui/Status"; import Status from "@/components/ui/Status";
import TableSkeleton from "@/components/ui/TableSkeleton"; import TableSkeleton from "@/components/ui/TableSkeleton";
import { unixToDate } from "@/utils/shared"; import { unixToDate } from "@/utils/shared";
import { Tooltip } from "react-tooltip";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import AssignToCompanyModalContent from "./AssignToCompanyModalContent"; import AssignToCompanyModalContent from "./AssignToCompanyModalContent";
@@ -89,25 +91,36 @@ const CustomersTable = () => {
<TableCell className="text-start xl:pr-7.5"> <TableCell className="text-start xl:pr-7.5">
<div className="flex items-center justify-start gap-x-3.5"> <div className="flex items-center justify-start gap-x-3.5">
<button <button
data-tooltip-id="delete"
data-tooltip-content="Delete"
data-tooltip-place="top"
className="hover:text-red-500" className="hover:text-red-500"
onClick={() => { onClick={() => {
setCurrentCustomer(customer); setCurrentCustomer(customer);
setIsModalOpen(true); setIsModalOpen(true);
}} }}
> >
<Tooltip {..._TooltipDefaultParams({ id: "delete" })} />
<span className="sr-only">Delete Company </span> <span className="sr-only">Delete Company </span>
<TrashIcon /> <TrashIcon />
</button> </button>
<button <button
data-tooltip-id="edit"
data-tooltip-content="Edit"
data-tooltip-place="top"
className="hover:text-primary" className="hover:text-primary"
onClick={() => onClick={() =>
router.push(`${pathName}/edit/${customer.id}`) router.push(`${pathName}/edit/${customer.id}`)
} }
> >
<Tooltip {..._TooltipDefaultParams({ id: "edit" })} />
<span className="sr-only">Edit Company </span> <span className="sr-only">Edit Company </span>
<PencilSquareIcon /> <PencilSquareIcon />
</button> </button>
<button <button
data-tooltip-id="assign-company"
data-tooltip-content="Assign to company"
data-tooltip-place="top"
className="hover:text-green-dark" className="hover:text-green-dark"
onClick={() => { onClick={() => {
setCurrentCustomer(customer); setCurrentCustomer(customer);
@@ -117,15 +130,22 @@ const CustomersTable = () => {
}); });
}} }}
> >
<Tooltip
{..._TooltipDefaultParams({ id: "assign-company" })}
/>
<span className="sr-only">Assign To Company </span> <span className="sr-only">Assign To Company </span>
<Building /> <Building />
</button> </button>
<button <button
data-tooltip-id="feedback"
data-tooltip-content="Feedback"
data-tooltip-place="top"
className="text-gray-40 rounded-full bg-gray-dark bg-opacity-5 hover:text-green-dark dark:bg-gray dark:bg-opacity-5" className="text-gray-40 rounded-full bg-gray-dark bg-opacity-5 hover:text-green-dark dark:bg-gray dark:bg-opacity-5"
onClick={() => { onClick={() => {
router.push(`${pathName}/feedback/${customer.id}`); router.push(`${pathName}/feedback/${customer.id}`);
}} }}
> >
<Tooltip {..._TooltipDefaultParams({ id: "feedback" })} />
<ExclamationIcon /> <ExclamationIcon />
</button> </button>
</div> </div>
@@ -2,6 +2,8 @@
import { EyeIcon } from "@/assets/icons"; import { EyeIcon } from "@/assets/icons";
import { ShowcaseSection } from "@/components/Layouts/showcase-section"; import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import Pagination from "@/components/ui/pagination"; import Pagination from "@/components/ui/pagination";
import { _TooltipDefaultParams } from "@/constants/tooltip";
import { Tooltip } from "react-tooltip";
import Status from "@/components/ui/Status"; import Status from "@/components/ui/Status";
import { import {
Table, Table,
@@ -73,10 +75,18 @@ const NotificationHistoryTable = () => {
</TableCell> </TableCell>
<TableCell colSpan={100}> <TableCell colSpan={100}>
<button <button
data-tooltip-id="details"
data-tooltip-content="Details"
data-tooltip-place="top"
onClick={() => { onClick={() => {
router.push(`${pathName}/${item.id}`); router.push(`${pathName}/${item.id}`);
}} }}
> >
<Tooltip
{..._TooltipDefaultParams({
id: "details",
})}
/>
<EyeIcon /> <EyeIcon />
</button> </button>
</TableCell> </TableCell>
+14
View File
@@ -9,11 +9,13 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { Tooltip } from "react-tooltip";
import useTenderListPresenter from "./useTenderListPresenter"; import useTenderListPresenter from "./useTenderListPresenter";
import { ExclamationIcon, EyeIcon } from "@/assets/icons"; import { ExclamationIcon, EyeIcon } from "@/assets/icons";
import Pagination from "@/components/ui/pagination"; import Pagination from "@/components/ui/pagination";
import TableSkeleton from "@/components/ui/TableSkeleton"; import TableSkeleton from "@/components/ui/TableSkeleton";
import { _TooltipDefaultParams } from "@/constants/tooltip";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import Link from "next/link"; import Link from "next/link";
const TendersTable = () => { const TendersTable = () => {
@@ -70,18 +72,30 @@ const TendersTable = () => {
colSpan={100} colSpan={100}
> >
<button <button
data-tooltip-id="details"
data-tooltip-content="Details"
data-tooltip-place="top"
onClick={() => { onClick={() => {
router.push(`${pathName}/${item.id}`); router.push(`${pathName}/${item.id}`);
}} }}
> >
<Tooltip
{..._TooltipDefaultParams({
id: "details",
})}
/>
<EyeIcon /> <EyeIcon />
</button> </button>
<button <button
data-tooltip-id="feedback"
data-tooltip-content="Feedback"
data-tooltip-place="top"
className="text-gray-40 rounded-full bg-gray-dark bg-opacity-5 hover:text-green-dark dark:bg-gray dark:bg-opacity-5" className="text-gray-40 rounded-full bg-gray-dark bg-opacity-5 hover:text-green-dark dark:bg-gray dark:bg-opacity-5"
onClick={() => { onClick={() => {
router.push(`${pathName}/feedback/${item.id}`); router.push(`${pathName}/feedback/${item.id}`);
}} }}
> >
<Tooltip {..._TooltipDefaultParams({ id: "feedback" })} />
<ExclamationIcon /> <ExclamationIcon />
</button> </button>
</TableCell> </TableCell>
+18
View File
@@ -0,0 +1,18 @@
import { CSSProperties } from "react";
import { VariantType } from "react-tooltip";
export const _TooltipDefaultParams = ({
id,
variant = id === "delete" ? "error" : "info",
styles,
}: {
id: string;
variant?: VariantType;
styles?: CSSProperties;
}) => ({
id,
delayShow: 500,
delayHide: 500,
variant,
style: { backgroundColor: variant === "error" ? "#F23030" : "", ...styles },
});
+7 -4
View File
@@ -49,6 +49,9 @@
.tableCheckbox:checked ~ div span { .tableCheckbox:checked ~ div span {
@apply opacity-100; @apply opacity-100;
} }
.react-tooltip {
@apply !rounded-full !transition-opacity !duration-500;
}
.tableCheckbox:checked ~ div { .tableCheckbox:checked ~ div {
@apply border-primary bg-primary; @apply border-primary bg-primary;
@@ -502,17 +505,17 @@ input[type="search"]::-webkit-search-cancel-button {
} }
.Toastify__toast--success { .Toastify__toast--success {
background: linear-gradient(to bottom right, #82E6AC, #1A8245) !important; background: linear-gradient(to bottom right, #82e6ac, #1a8245) !important;
} }
.Toastify__toast--error { .Toastify__toast--error {
background: linear-gradient(to bottom right, #F89090, #E10E0E) !important; background: linear-gradient(to bottom right, #f89090, #e10e0e) !important;
} }
.Toastify__toast--warning { .Toastify__toast--warning {
background: linear-gradient(to bottom right, #F59E0B, #D97706) !important; background: linear-gradient(to bottom right, #f59e0b, #d97706) !important;
} }
.Toastify__toast--info { .Toastify__toast--info {
background: linear-gradient(to bottom right, #8099EC, #1C3FB7) !important; background: linear-gradient(to bottom right, #8099ec, #1c3fb7) !important;
} }