TimePeriodSelect.tsx 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import { CalendarIcon } from 'lucide-react';
  2. import type { FunnelPeriod } from '../../../api/types';
  3. import { toSnapshotParam, yesterday } from '../period';
  4. import { Calendar } from '@/components/ui/calendar';
  5. import {
  6. Popover,
  7. PopoverContent,
  8. PopoverTrigger,
  9. } from '@/components/ui/popover';
  10. import { cn } from '@/lib/utils';
  11. interface Props {
  12. period: FunnelPeriod;
  13. /** Selected day for `period: 'day'`. */
  14. snapshotDt: Date;
  15. /** Pick a historical day → switch to single-day mode. */
  16. onPickDate: (d: Date) => void;
  17. /** Pick a rolling window. */
  18. onPickRolling: (p: 'last_7d' | 'last_30d') => void;
  19. }
  20. // 统一 segmented:选中态由 React 状态直接驱动 mint 实色,不依赖 shadcn 变体。
  21. const SEG =
  22. 'h-8 px-3 inline-flex items-center gap-1.5 rounded-md text-sm font-medium transition-colors cursor-pointer';
  23. const ON = 'bg-primary text-primary-foreground shadow-sm';
  24. const OFF = 'text-muted-foreground hover:text-foreground hover:bg-background';
  25. /**
  26. * 时间选择 — 单日 / 近 7 天 / 近 30 天 三选一的 segmented 控件(docs/04)。
  27. * 「单日」段点开日历可选历史日(上限昨日,T+1);任一选中 = mint 高亮,
  28. * 给明确的"当前在此周期"反馈。
  29. */
  30. export function TimePeriodSelect({
  31. period,
  32. snapshotDt,
  33. onPickDate,
  34. onPickRolling,
  35. }: Props) {
  36. const max = yesterday();
  37. return (
  38. <div className="inline-flex items-center gap-1 rounded-lg border border-border bg-muted/60 p-1">
  39. {/* 单日 —— 日历 popover */}
  40. <Popover>
  41. <PopoverTrigger asChild>
  42. <button
  43. type="button"
  44. aria-label="选择历史日期"
  45. aria-pressed={period === 'day'}
  46. className={cn(SEG, period === 'day' ? ON : OFF)}
  47. >
  48. <CalendarIcon className="size-4" />
  49. <span>单日</span>
  50. <span className="tabular-nums opacity-90">
  51. {toSnapshotParam(snapshotDt)}
  52. </span>
  53. </button>
  54. </PopoverTrigger>
  55. <PopoverContent className="w-auto p-0" align="start">
  56. <Calendar
  57. mode="single"
  58. selected={snapshotDt}
  59. defaultMonth={snapshotDt}
  60. disabled={{ after: max }}
  61. required
  62. onSelect={(d) => {
  63. if (d) onPickDate(d);
  64. }}
  65. />
  66. </PopoverContent>
  67. </Popover>
  68. {/* 近 7 天 / 近 30 天 */}
  69. <button
  70. type="button"
  71. aria-pressed={period === 'last_7d'}
  72. onClick={() => onPickRolling('last_7d')}
  73. className={cn(SEG, period === 'last_7d' ? ON : OFF)}
  74. >
  75. 近 7 天
  76. </button>
  77. <button
  78. type="button"
  79. aria-pressed={period === 'last_30d'}
  80. onClick={() => onPickRolling('last_30d')}
  81. className={cn(SEG, period === 'last_30d' ? ON : OFF)}
  82. >
  83. 近 30 天
  84. </button>
  85. </div>
  86. );
  87. }
  88. export default TimePeriodSelect;