File size: 3,424 Bytes
b64de39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c5a7b7d
b64de39
 
 
 
 
 
 
 
 
c5a7b7d
 
b64de39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/**
 * Sidebar - Desktop side navigation panel.
 *
 * Renders a collapsible sidebar with navigation links.
 * Supports expanded and collapsed (icon-only) modes.
 */

import { useState, useCallback } from "react";
import { Link, useLocation } from "react-router-dom";
import PropTypes from "prop-types";
import {
  HomeIcon,
  CameraIcon,
  CloudIcon,
  CurrencyRupeeIcon,
  DocumentTextIcon,
  ChevronLeftIcon,
  ChevronRightIcon,
} from "@heroicons/react/24/outline";
import { ROUTES, APP_NAME } from "@utils/constants";

const NAV_ITEMS = [
  { label: "Home", path: ROUTES.HOME, Icon: HomeIcon },
  { label: "Disease Detection", path: ROUTES.DISEASE_DETECTION, Icon: CameraIcon },
  { label: "Weather", path: ROUTES.WEATHER, Icon: CloudIcon },
  { label: "APMC Price", path: ROUTES.APMC, Icon: CurrencyRupeeIcon },
  { label: "Schemes", path: ROUTES.SCHEMES, Icon: DocumentTextIcon },
];

function Sidebar({
  defaultCollapsed = false,
  className = "",
}) {
  const [collapsed, setCollapsed] = useState(defaultCollapsed);
  const location = useLocation();

  const toggle = useCallback(() => {
    setCollapsed((prev) => !prev);
  }, []);

  return (
    <aside
      className={[
        "flex flex-col border-r border-neutral-200 bg-white transition-[width] duration-200",
        collapsed ? "w-16" : "w-60",
        className,
      ].join(" ")}
      aria-label="Side navigation"
    >
      {/* Header / Logo */}
      <div className="flex h-16 items-center justify-between border-b border-neutral-200 px-3">
        {!collapsed && (
          <span className="font-display font-bold text-primary-700 text-sm truncate">
            {APP_NAME}
          </span>
        )}
        <button
          type="button"
          onClick={toggle}
          className="rounded-lg p-1.5 text-neutral-400 hover:text-neutral-600 hover:bg-neutral-100 transition-colors focus:outline-none focus:ring-2 focus:ring-primary-500"
          aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
        >
          {collapsed ? (
            <ChevronRightIcon className="h-4 w-4" aria-hidden="true" />
          ) : (
            <ChevronLeftIcon className="h-4 w-4" aria-hidden="true" />
          )}
        </button>
      </div>

      {/* Navigation */}
      <nav className="flex-1 py-4 px-2 space-y-1">
        {NAV_ITEMS.map((item) => {
          const isActive = location.pathname === item.path;
          return (
            <Link
              key={item.path}
              to={item.path}
              title={collapsed ? item.label : undefined}
              className={[
                "flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors",
                "focus:outline-none focus:ring-2 focus:ring-primary-500",
                isActive
                  ? "bg-primary-50 text-primary-700"
                  : "text-neutral-600 hover:text-neutral-900 hover:bg-neutral-100",
                collapsed ? "justify-center" : "",
              ].join(" ")}
              aria-current={isActive ? "page" : undefined}
            >
              <item.Icon className="h-5 w-5 shrink-0" aria-hidden="true" />
              {!collapsed && <span className="truncate">{item.label}</span>}
            </Link>
          );
        })}
      </nav>
    </aside>
  );
}

Sidebar.propTypes = {
  defaultCollapsed: PropTypes.bool,
  className: PropTypes.string,
};

export default Sidebar;