UNPKG

phx-react

Version:

PHX REACT

165 lines 9.59 kB
import React, { useEffect, useRef, useState } from 'react'; import { PHXButton } from '../Button'; import { classNames } from '../types'; const defaultDaysOfWeek = ['Chủ nhật', 'Thứ 2', 'Thứ 3', 'Thứ 4', 'Thứ 5', 'Thứ 6', 'Thứ 7']; const PHXTabDate = ({ daysOfWeek = defaultDaysOfWeek, hideTodayButton = false, onChangeTime, timeline: timelineProp, windowSize = 8, showRightArrowButton = false, selectedDate, }) => { var _a; const containerRef = useRef(null); const [isScrollX, setIsScrollX] = useState(false); // Dùng ref để tránh onChangeTime gây infinite loop khi cha khai báo inline const onChangeTimeRef = useRef(onChangeTime); useEffect(() => { onChangeTimeRef.current = onChangeTime; }, [onChangeTime]); // Ref để track lần đầu mount, tránh gọi onChangeTime khi init const isFirstMount = useRef(true); const buildDefaultTimeline = () => { function getPreviousDays(baseDate, n) { const result = []; for (let i = n; i >= 1; i--) { const d = new Date(baseDate); d.setDate(baseDate.getDate() - i); const dayOfWeek = d.getDay(); result.push({ title: daysOfWeek[dayOfWeek], date: `${d.getDate()}/${d.getMonth() + 1}`, time: d, }); } return result; } const today = new Date(); const past = getPreviousDays(today, 30); const todayDayOfWeek = today.getDay(); past.push({ title: daysOfWeek[todayDayOfWeek], date: `${today.getDate()}/${today.getMonth() + 1}`, time: today, }); return past; }; const timeline = timelineProp || buildDefaultTimeline(); const isHaveOnlyOneOption = timeline.length === 1; const [todayIndex, setTodayIndex] = useState(-1); const initialStartIndex = Math.max(todayIndex - (windowSize - 1), 0); const [startIndex, setStartIndex] = useState(initialStartIndex); const [activeIndex, setActiveIndex] = useState(todayIndex); const visibleDays = timeline.slice(startIndex, startIndex + windowSize); function handleCheckActive(newActiveIndex) { setActiveIndex(newActiveIndex); } const handlePrevClick = () => { if (startIndex - 1 >= 0) { setStartIndex((pre) => pre - 1); } handleCheckActive(activeIndex - 1); }; const handleNextClick = () => { if (startIndex + windowSize < timeline.length) { setStartIndex((prev) => prev + 1); } handleCheckActive(activeIndex + 1); }; const handleClickToday = () => { var _a; setActiveIndex(todayIndex); const newStartIndex = Math.max(todayIndex - (windowSize - 1), 0); setStartIndex(newStartIndex); (_a = onChangeTimeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeTimeRef, new Date()); }; // Sync activeIndex khi selectedDate hoặc timeline thay đổi từ bên ngoài useEffect(() => { if (selectedDate) { const selectedIndex = timeline.findIndex((t) => t.date === selectedDate); if (selectedIndex !== -1) { setActiveIndex(selectedIndex); const isVisible = selectedIndex >= startIndex && selectedIndex < startIndex + windowSize; if (!isVisible) { const newStart = Math.max(selectedIndex - Math.floor(windowSize / 2), 0); const safeStart = Math.min(newStart, timeline.length - windowSize); setStartIndex(Math.max(0, safeStart)); } } return; } const newTodayIndex = timeline.findIndex((t) => t.time.toDateString() === new Date().toDateString()); setActiveIndex(newTodayIndex); setTodayIndex(newTodayIndex); setStartIndex(Math.max(newTodayIndex - (windowSize - 1), 0)); }, [selectedDate, timeline.length]); // FIX: Bỏ onChangeTime khỏi dependency array, dùng ref thay thế useEffect(() => { const activeItem = timeline[activeIndex]; if (!activeItem || !onChangeTimeRef.current) return; if (isFirstMount.current) { isFirstMount.current = false; return; } onChangeTimeRef.current(activeItem.time); }, [(_a = timeline[activeIndex]) === null || _a === void 0 ? void 0 : _a.date]); useEffect(() => { const el = containerRef.current; if (!el) return; const checkScrollX = () => { const hasScrollX = el.scrollWidth > el.clientWidth; setIsScrollX(hasScrollX); }; checkScrollX(); window.addEventListener('resize', checkScrollX); return () => { window.removeEventListener('resize', checkScrollX); }; }, []); return (React.createElement("div", null, !hideTodayButton && (React.createElement(PHXButton, { className: 'mb-3', iconPosition: 'before', onClick: handleClickToday, secondary: true, size: 'extra-micro', typeIcon: 'calendar' }, "H\u00F4m nay")), React.createElement("div", { className: 'flex w-full items-start' }, !isHaveOnlyOneOption && (React.createElement("button", { style: { height: isScrollX ? '58px' : '50px', }, className: '\n mr-1 flex w-[1.875rem] flex-shrink-0 items-center justify-center \n rounded-lg border border-gray-300 bg-white hover:bg-gray-200\n disabled:cursor-not-allowed disabled:bg-gray-100 disabled:text-gray-400\n ', disabled: activeIndex === 0, onClick: handlePrevClick }, React.createElement("svg", { fill: 'none', height: '24', stroke: 'currentColor', viewBox: '0 0 24 24', width: '24', xmlns: 'http://www.w3.org/2000/svg', className: 'w-4 h-4' }, React.createElement("path", { d: 'M15 18L9 12L15 6', strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: '2' })))), React.createElement("style", null, ` .scrollbar-thin::-webkit-scrollbar { width: 8px; height: 8px; } .scrollbar-thin::-webkit-scrollbar-thumb { background: rgb(199 199 199); border-radius: 16px; border: 2px solid transparent; background-clip: content-box; } .shadow-container { box-shadow: 0rem 0.3125rem 0.3125rem -0.15625rem rgba(0, 0, 0, 0.03), 0rem 0.1875rem 0.1875rem -0.09375rem rgba(0, 0, 0, 0.02), 0rem 0.125rem 0.125rem -0.0625rem rgba(0, 0, 0, 0.02), 0rem 0.0625rem 0.0625rem -0.03125rem rgba(0, 0, 0, 0.03), 0rem 0.03125rem 0.03125rem 0rem rgba(0, 0, 0, 0.04), 0rem 0rem 0rem 0.0625rem rgba(0, 0, 0, 0.06); } `), React.createElement("div", { ref: containerRef, className: classNames('overflow-x-auto scrollbar-thin rounded-lg shadow-container', isHaveOnlyOneOption ? 'w-[7.5rem]' : ' flex-1') }, React.createElement("div", { className: classNames('flex h-[3.125rem] overflow-y-hidden', isHaveOnlyOneOption ? 'w-[7.5rem]' : 'min-w-max') }, visibleDays.map((item, index) => { const globalIndex = startIndex + index; const isActive = activeIndex === globalIndex; return (React.createElement("div", { key: globalIndex, className: 'w-full h-[3.125rem] min-w-[7.5rem]' }, React.createElement("button", { className: `${index === 0 ? '' : 'border-l'} h-full w-full border-gray-300 bg-white px-1.5 py-1 hover:bg-gray-100`, onClick: () => { setActiveIndex(globalIndex); } }, isActive ? (React.createElement("div", { className: 'flex h-full w-full flex-col items-center justify-center rounded-lg bg-gray-900 text-white' }, React.createElement("span", { className: 'text-xxs' }, item.title), React.createElement("p", { className: 'text-xs font-medium' }, item.date))) : (React.createElement("div", { className: 'flex h-full w-full flex-col items-center justify-center' }, React.createElement("span", { className: 'text-xxs text-gray-600' }, item.title), React.createElement("p", { className: 'text-xs font-medium text-gray-900' }, item.date)))))); }))), (startIndex + windowSize < timeline.length || showRightArrowButton) && !isHaveOnlyOneOption && (React.createElement("button", { style: { height: isScrollX ? '58px' : '50px', }, className: 'ml-1 flex w-[1.875rem] flex-shrink-0 items-center justify-center \n rounded-lg border border-gray-300 bg-white hover:bg-gray-200 disabled:cursor-not-allowed disabled:bg-gray-100 disabled:text-gray-400', onClick: handleNextClick, disabled: activeIndex >= timeline.length - 1 }, React.createElement("svg", { fill: 'none', height: '24', viewBox: '0 0 24 24', width: '24', xmlns: 'http://www.w3.org/2000/svg', className: 'w-4 h-4' }, React.createElement("path", { d: 'M9 18L15 12L9 6', stroke: 'currentColor', strokeLinecap: 'round', strokeLinejoin: 'round', strokeWidth: '2' }))))))); }; export default PHXTabDate; //# sourceMappingURL=PHXTabDate.js.map