diff --git a/app/(app)/dashboard/card/Card.tsx b/app/(app)/dashboard/card/Card.tsx index d16893c..fef9079 100644 --- a/app/(app)/dashboard/card/Card.tsx +++ b/app/(app)/dashboard/card/Card.tsx @@ -11,6 +11,8 @@ import CardActions from "./CardActions"; import CardAvatars from "./CardAvatars"; import CardDueDate from "./CardDueDate"; import CardPriority from "./CardPriority"; +import HighlightText from "@/app/components/ui/HighlightText"; +import { useSearchParams } from "next/navigation"; /** * Properties for the Card component. @@ -29,8 +31,8 @@ export interface CardProps { } /** - * Renders an interactive card container handling drag-and-drop actions, loading states, - * and assembling modular sub-components for priorities, due dates, avatars, and actions. + * Renders an interactive card container supporting search match highlighting, drag-and-drop actions, + * loading states, and modular sub-components for priorities, due dates, avatars, and actions. * * @param {CardProps} props - The component props containing the task object, updating status flag, and status change handler. * @returns {JSX.Element} The rendered card component. @@ -41,6 +43,9 @@ export default function Card({ onStatusChange, onDelete, }: CardProps) { + const searchParams = useSearchParams(); + const searchQuery = searchParams.get("search") || ""; + /** * Initiates the drag action on a task card if not currently updating, storing its ID and status payload. * @@ -87,14 +92,14 @@ export default function Card({ {/* --- Card Header --- */}

- {task.title} +

{/* Card Description */}

- {task.description} +

{/* --- Card Footer --- */} diff --git a/app/(app)/trash/TrashList.tsx b/app/(app)/trash/TrashList.tsx index 630f928..9bf00b1 100644 --- a/app/(app)/trash/TrashList.tsx +++ b/app/(app)/trash/TrashList.tsx @@ -8,16 +8,21 @@ import { Task, PRIORITY_CONFIG } from "@/types/task"; import { Calendar } from "lucide-react"; import TaskActionButton from "./components/TaskActionButton"; +import HighlightText from "@/app/components/ui/HighlightText"; +import { useSearchParams } from "next/navigation"; /** - * Renders a list of deleted tasks stored in the trash, featuring priority badges, - * deletion dates, and action controls for permanent deletion or restoration. + * Renders a list of deleted tasks stored in the trash with search term highlighting, + * featuring priority badges, deletion dates, and action controls for permanent deletion or restoration. * * @param {Object} props - The component props. * @param {Task[]} props.tasks - The array of deleted tasks to render. * @returns {JSX.Element} The rendered trash list component or an empty state placeholder. */ export default function TrashList({ tasks }: { tasks: Task[] }) { + const searchParams = useSearchParams(); + const searchQuery = searchParams.get("search") || ""; + return (
{tasks.length === 0 ? ( @@ -54,7 +59,7 @@ export default function TrashList({ tasks }: { tasks: Task[] }) {

- {task.title} +

- {task.description} +

)} diff --git a/app/components/ui/HighlightText.tsx b/app/components/ui/HighlightText.tsx new file mode 100644 index 0000000..bb96972 --- /dev/null +++ b/app/components/ui/HighlightText.tsx @@ -0,0 +1,56 @@ +/** + * @file components/ui/HighlightText.tsx + * @description Component to highlight matching search query terms within a text string. + */ + +/** + * Properties for the HighlightText component. + * + * @interface HighlightTextProps + * @property {string | null} [text] - The full text string to render and search within. + * @property {string} [query] - The search query term to highlight within the text. + * @property {string} [className] - Optional CSS classes applied to the wrapping container element. + */ +interface HighlightTextProps { + text?: string | null; + query?: string; + className?: string; +} + +/** + * Renders a text string with case-insensitive highlighted search query matches wrapped in a marked element. + * + * @param {HighlightTextProps} props - The component props. + * @returns {JSX.Element | null} The rendered text component with highlighted query terms, or null if no text is provided. + */ +export default function HighlightText({ + text, + query, + className = "", +}: HighlightTextProps) { + if (!text) return null; + if (!query || !query.trim()) { + return {text}; + } + + // Regular expression with escaping for special characters, case-insensitive ('gi') + const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const parts = text.split(new RegExp(`(${escapedQuery})`, "gi")); + + return ( + + {parts.map((part, index) => + part.toLowerCase() === query.toLowerCase() ? ( + + {part} + + ) : ( + part + ), + )} + + ); +}