feat: range picker

This commit is contained in:
Joel 2025-11-05 16:55:39 +08:00
parent 92ab453a5a
commit 56dd17cdaa
7 changed files with 97 additions and 8 deletions

View File

@ -4,13 +4,17 @@ import type { FC } from 'react'
import React, { useCallback } from 'react'
import { SimpleSelect } from '@/app/components/base/select'
import type { Item } from '@/app/components/base/select'
import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import { HourglassShape } from '@/app/components/base/icons/src/vender/other'
import TimePicker from '@/app/components/base/date-and-time-picker/time-picker'
import { noop } from 'lodash-es'
import { useTranslation } from 'react-i18next'
import cn from '@/utils/classnames'
import { RiArrowDownSLine, RiCheckLine } from '@remixicon/react'
import { RiArrowDownSLine, RiCalendarLine, RiCheckLine } from '@remixicon/react'
import DatePicker from '@/app/components/base/date-and-time-picker/date-picker'
import 'dayjs/locale/zh-cn'
import { formatToLocalTime } from '@/utils/format'
import { useI18N } from '@/context/i18n'
const today = dayjs()
@ -68,6 +72,17 @@ const TimeRangePicker: FC<Props> = ({
}
onSelect({ query: period!, name })
}, [onSelect])
const { locale } = useI18N()
const renderDate = useCallback(({ value, handleClickTrigger, isOpen }: { value?: Dayjs, handleClickTrigger: () => void, isOpen: boolean }) => {
return (
<div className={cn('system-sm-regular flex h-7 cursor-pointer items-center rounded-lg px-1 text-components-input-text-filled hover:bg-state-base-hover', isOpen && 'bg-state-base-hover')} onClick={handleClickTrigger}>
{value ? formatToLocalTime(value, locale, 'MMM D') : ''}
</div>
)
}, [locale])
return (
<div className='flex items-center'>
<SimpleSelect
@ -83,10 +98,23 @@ const TimeRangePicker: FC<Props> = ({
renderOption={renderOption}
/>
<HourglassShape className='h-3.5 w-2 text-components-input-bg-normal' />
<TimePicker
value={today}
onChange={noop}
/>
<div className='flex h-8 items-center space-x-0.5 rounded-lg bg-components-input-bg-normal px-2'>
<RiCalendarLine className='size-3.5 text-text-tertiary' />
<DatePicker
value={today}
onChange={noop}
renderTrigger={renderDate}
needTimePicker={false}
/>
<span className='system-sm-regular text-text-tertiary'>-</span>
<DatePicker
value={today}
onChange={noop}
renderTrigger={renderDate}
needTimePicker={false}
/>
</div>
</div>
)
}

View File

@ -8,9 +8,10 @@ const Calendar: FC<CalendarProps> = ({
selectedDate,
onDateClick,
wrapperClassName,
getIsDateDisabled,
}) => {
return <div className={wrapperClassName}>
<DaysOfWeek/>
<DaysOfWeek />
<div className='grid grid-cols-7 gap-0.5 p-2'>
{
days.map(day => <CalendarItem
@ -18,6 +19,7 @@ const Calendar: FC<CalendarProps> = ({
day={day}
selectedDate={selectedDate}
onClick={onDateClick}
isDisabled={getIsDateDisabled ? getIsDateDisabled(day.date) : false}
/>)
}
</div>

View File

@ -7,6 +7,7 @@ const Item: FC<CalendarItemProps> = ({
day,
selectedDate,
onClick,
isDisabled,
}) => {
const { date, isCurrentMonth } = day
const isSelected = selectedDate?.isSame(date, 'date')
@ -14,11 +15,12 @@ const Item: FC<CalendarItemProps> = ({
return (
<button type="button"
onClick={() => onClick(date)}
onClick={() => !isDisabled && onClick(date)}
className={cn(
'system-sm-medium relative flex items-center justify-center rounded-lg px-1 py-2',
isCurrentMonth ? 'text-text-secondary' : 'text-text-quaternary hover:text-text-secondary',
isSelected ? 'system-sm-medium bg-components-button-primary-bg text-components-button-primary-text' : 'hover:bg-state-base-hover',
isDisabled && 'cursor-not-allowed text-text-quaternary hover:bg-transparent',
)}
>
{date.date()}

View File

@ -36,6 +36,7 @@ const DatePicker = ({
renderTrigger,
triggerWrapClassName,
popupZIndexClassname = 'z-[11]',
getIsDateDisabled,
}: DatePickerProps) => {
const { t } = useTranslation()
const [isOpen, setIsOpen] = useState(false)
@ -270,6 +271,7 @@ const DatePicker = ({
days={days}
selectedDate={selectedDate}
onDateClick={handleDateSelect}
getIsDateDisabled={getIsDateDisabled}
/>
) : view === ViewType.yearMonth ? (
<YearAndMonthPickerOptions

View File

@ -30,6 +30,7 @@ export type DatePickerProps = {
renderTrigger?: (props: TriggerProps) => React.ReactNode
minuteFilter?: (minutes: string[]) => string[]
popupZIndexClassname?: string
getIsDateDisabled?: (date: Dayjs) => boolean
}
export type DatePickerHeaderProps = {
@ -80,12 +81,14 @@ export type CalendarProps = {
selectedDate: Dayjs | undefined
onDateClick: (date: Dayjs) => void
wrapperClassName?: string
getIsDateDisabled?: (date: Dayjs) => boolean
}
export type CalendarItemProps = {
day: Day
selectedDate: Dayjs | undefined
onClick: (date: Dayjs) => void
isDisabled: boolean
}
export type TimeOptionsProps = {

View File

@ -67,6 +67,7 @@ const translation = {
quarterToDate: '四半期初から今日まで',
yearToDate: '年初から今日まで',
allTime: 'すべての期間',
custom: 'カスタム',
},
annotation: {
all: 'すべて',

View File

@ -1,3 +1,50 @@
import type { Locale } from '@/i18n-config'
import type { Dayjs } from 'dayjs'
import 'dayjs/locale/de'
import 'dayjs/locale/es'
import 'dayjs/locale/fa'
import 'dayjs/locale/fr'
import 'dayjs/locale/hi'
import 'dayjs/locale/id'
import 'dayjs/locale/it'
import 'dayjs/locale/ja'
import 'dayjs/locale/ko'
import 'dayjs/locale/pl'
import 'dayjs/locale/pt-br'
import 'dayjs/locale/ro'
import 'dayjs/locale/ru'
import 'dayjs/locale/sl'
import 'dayjs/locale/th'
import 'dayjs/locale/tr'
import 'dayjs/locale/uk'
import 'dayjs/locale/vi'
import 'dayjs/locale/zh-cn'
import 'dayjs/locale/zh-tw'
const localeMap: Record<Locale, string> = {
'en-US': 'en',
'zh-Hans': 'zh-cn',
'zh-Hant': 'zh-tw',
'pt-BR': 'pt-br',
'es-ES': 'es',
'fr-FR': 'fr',
'de-DE': 'de',
'ja-JP': 'ja',
'ko-KR': 'ko',
'ru-RU': 'ru',
'it-IT': 'it',
'th-TH': 'th',
'id-ID': 'id',
'uk-UA': 'uk',
'vi-VN': 'vi',
'ro-RO': 'ro',
'pl-PL': 'pl',
'hi-IN': 'hi',
'tr-TR': 'tr',
'fa-IR': 'fa',
'sl-SI': 'sl',
}
/**
* Formats a number with comma separators.
* @example formatNumber(1234567) will return '1,234,567'
@ -90,3 +137,7 @@ export const formatNumberAbbreviated = (num: number) => {
}
}
}
export const formatToLocalTime = (time: Dayjs, local: string, format: string) => {
return time.locale(localeMap[local] ?? 'en').format(format)
}