SegmentDropdown.jsx 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. "use client";
  2. import React from "react";
  3. import { useRouter } from "next/navigation";
  4. import { Check, ChevronDown } from "lucide-react";
  5. import {
  6. DropdownMenu,
  7. DropdownMenuContent,
  8. DropdownMenuItem,
  9. DropdownMenuLabel,
  10. DropdownMenuSeparator,
  11. DropdownMenuTrigger,
  12. } from "@/components/ui/dropdown-menu";
  13. /**
  14. * SegmentDropdown
  15. *
  16. * A small dropdown trigger used next to breadcrumb segments to switch between
  17. * existing years/months/days.
  18. *
  19. * IMPORTANT:
  20. * - This component MUST be declared at module scope (not inside another component render),
  21. * otherwise React will treat it as a new component type on each render and warn/error.
  22. *
  23. * UX rule:
  24. * - All visible text is provided by the parent in German.
  25. *
  26. * @param {{
  27. * items: Array<{ value: string, label: string, href: string }>,
  28. * currentValue: string|null,
  29. * menuLabel: string,
  30. * triggerAriaLabel: string
  31. * }} props
  32. */
  33. export default function SegmentDropdown({
  34. items,
  35. currentValue,
  36. menuLabel,
  37. triggerAriaLabel,
  38. }) {
  39. const router = useRouter();
  40. if (!Array.isArray(items) || items.length === 0) return null;
  41. return (
  42. <DropdownMenu>
  43. <DropdownMenuTrigger asChild>
  44. <button
  45. type="button"
  46. className="ml-1 inline-flex h-7 w-7 items-center justify-center rounded-md border bg-background text-muted-foreground shadow-xs hover:bg-accent hover:text-foreground focus:outline-none focus:ring-2 focus:ring-ring/50"
  47. aria-label={triggerAriaLabel}
  48. title={triggerAriaLabel}
  49. >
  50. <ChevronDown className="h-4 w-4" aria-hidden="true" />
  51. </button>
  52. </DropdownMenuTrigger>
  53. <DropdownMenuContent align="start" className="max-h-72 overflow-auto">
  54. <DropdownMenuLabel>{menuLabel}</DropdownMenuLabel>
  55. <DropdownMenuSeparator />
  56. {items.map((it) => {
  57. const isActive =
  58. currentValue && String(it.value) === String(currentValue);
  59. return (
  60. <DropdownMenuItem
  61. key={it.href}
  62. className="cursor-pointer"
  63. onSelect={(e) => {
  64. // Radix fires onSelect for click + keyboard. Prevent default for consistent behavior.
  65. e.preventDefault();
  66. router.push(it.href);
  67. }}
  68. >
  69. <span className="flex items-center gap-2">
  70. {isActive ? (
  71. <Check className="h-4 w-4" aria-hidden="true" />
  72. ) : (
  73. <span className="h-4 w-4" aria-hidden="true" />
  74. )}
  75. <span>{it.label}</span>
  76. </span>
  77. </DropdownMenuItem>
  78. );
  79. })}
  80. </DropdownMenuContent>
  81. </DropdownMenu>
  82. );
  83. }