feat: introduce trigger functionality (#27644)
Signed-off-by: lyzno1 <yuanyouhuilyz@gmail.com> Co-authored-by: Stream <Stream_2@qq.com> Co-authored-by: lyzno1 <92089059+lyzno1@users.noreply.github.com> Co-authored-by: zhsama <torvalds@linux.do> Co-authored-by: Harry <xh001x@hotmail.com> Co-authored-by: lyzno1 <yuanyouhuilyz@gmail.com> Co-authored-by: yessenia <yessenia.contact@gmail.com> Co-authored-by: hjlarry <hjlarry@163.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: WTW0313 <twwu@dify.ai> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -10,26 +10,25 @@ const Footer: FC<TimePickerFooterProps> = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className='flex items-center justify-end border-t-[0.5px] border-divider-regular p-2'>
|
||||
<div className='flex items-center gap-x-1'>
|
||||
{/* Now */}
|
||||
<button
|
||||
type='button'
|
||||
className='system-xs-medium flex items-center justify-center px-1.5 py-1 text-components-button-secondary-accent-text'
|
||||
onClick={handleSelectCurrentTime}
|
||||
>
|
||||
<span className='px-[3px]'>{t('time.operation.now')}</span>
|
||||
</button>
|
||||
{/* Confirm Button */}
|
||||
<Button
|
||||
variant='primary'
|
||||
size='small'
|
||||
className='w-16 px-1.5 py-1'
|
||||
onClick={handleConfirm.bind(null)}
|
||||
>
|
||||
{t('time.operation.ok')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className='flex items-center justify-between border-t-[0.5px] border-divider-regular p-2'>
|
||||
{/* Now Button */}
|
||||
<Button
|
||||
variant='secondary-accent'
|
||||
size='small'
|
||||
className='mr-1 flex-1'
|
||||
onClick={handleSelectCurrentTime}
|
||||
>
|
||||
{t('time.operation.now')}
|
||||
</Button>
|
||||
{/* Confirm Button */}
|
||||
<Button
|
||||
variant='primary'
|
||||
size='small'
|
||||
className='ml-1 flex-1'
|
||||
onClick={handleConfirm.bind(null)}
|
||||
>
|
||||
{t('time.operation.ok')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,15 @@ jest.mock('@/app/components/base/portal-to-follow-elem', () => ({
|
||||
|
||||
jest.mock('./options', () => () => <div data-testid="time-options" />)
|
||||
jest.mock('./header', () => () => <div data-testid="time-header" />)
|
||||
jest.mock('@/app/components/base/timezone-label', () => {
|
||||
return function MockTimezoneLabel({ timezone, inline, className }: { timezone: string, inline?: boolean, className?: string }) {
|
||||
return (
|
||||
<span data-testid="timezone-label" data-timezone={timezone} data-inline={inline} className={className}>
|
||||
UTC+8
|
||||
</span>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
describe('TimePicker', () => {
|
||||
const baseProps: Pick<TimePickerProps, 'onChange' | 'onClear' | 'value'> = {
|
||||
@@ -94,4 +103,86 @@ describe('TimePicker', () => {
|
||||
expect(isDayjsObject(emitted)).toBe(true)
|
||||
expect(emitted?.utcOffset()).toBe(dayjs().tz('America/New_York').utcOffset())
|
||||
})
|
||||
|
||||
describe('Timezone Label Integration', () => {
|
||||
test('should not display timezone label by default', () => {
|
||||
render(
|
||||
<TimePicker
|
||||
{...baseProps}
|
||||
value="12:00 AM"
|
||||
timezone="Asia/Shanghai"
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByTestId('timezone-label')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('should not display timezone label when showTimezone is false', () => {
|
||||
render(
|
||||
<TimePicker
|
||||
{...baseProps}
|
||||
value="12:00 AM"
|
||||
timezone="Asia/Shanghai"
|
||||
showTimezone={false}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByTestId('timezone-label')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('should display timezone label when showTimezone is true', () => {
|
||||
render(
|
||||
<TimePicker
|
||||
{...baseProps}
|
||||
value="12:00 AM"
|
||||
timezone="Asia/Shanghai"
|
||||
showTimezone={true}
|
||||
/>,
|
||||
)
|
||||
|
||||
const timezoneLabel = screen.getByTestId('timezone-label')
|
||||
expect(timezoneLabel).toBeInTheDocument()
|
||||
expect(timezoneLabel).toHaveAttribute('data-timezone', 'Asia/Shanghai')
|
||||
})
|
||||
|
||||
test('should pass inline prop to timezone label', () => {
|
||||
render(
|
||||
<TimePicker
|
||||
{...baseProps}
|
||||
value="12:00 AM"
|
||||
timezone="America/New_York"
|
||||
showTimezone={true}
|
||||
/>,
|
||||
)
|
||||
|
||||
const timezoneLabel = screen.getByTestId('timezone-label')
|
||||
expect(timezoneLabel).toHaveAttribute('data-inline', 'true')
|
||||
})
|
||||
|
||||
test('should not display timezone label when showTimezone is true but timezone is not provided', () => {
|
||||
render(
|
||||
<TimePicker
|
||||
{...baseProps}
|
||||
value="12:00 AM"
|
||||
showTimezone={true}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByTestId('timezone-label')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('should apply shrink-0 and text-xs classes to timezone label', () => {
|
||||
render(
|
||||
<TimePicker
|
||||
{...baseProps}
|
||||
value="12:00 AM"
|
||||
timezone="Europe/London"
|
||||
showTimezone={true}
|
||||
/>,
|
||||
)
|
||||
|
||||
const timezoneLabel = screen.getByTestId('timezone-label')
|
||||
expect(timezoneLabel).toHaveClass('shrink-0', 'text-xs')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -19,6 +19,7 @@ import Header from './header'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseCircleFill, RiTimeLine } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
import TimezoneLabel from '@/app/components/base/timezone-label'
|
||||
|
||||
const to24Hour = (hour12: string, period: Period) => {
|
||||
const normalized = Number.parseInt(hour12, 10) % 12
|
||||
@@ -35,6 +36,10 @@ const TimePicker = ({
|
||||
title,
|
||||
minuteFilter,
|
||||
popupClassName,
|
||||
notClearable = false,
|
||||
triggerFullWidth = false,
|
||||
showTimezone = false,
|
||||
placement = 'bottom-start',
|
||||
}: TimePickerProps) => {
|
||||
const { t } = useTranslation()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
@@ -189,7 +194,7 @@ const TimePicker = ({
|
||||
|
||||
const inputElem = (
|
||||
<input
|
||||
className='system-xs-regular flex-1 cursor-pointer appearance-none truncate bg-transparent p-1
|
||||
className='system-xs-regular flex-1 cursor-pointer select-none appearance-none truncate bg-transparent p-1
|
||||
text-components-input-text-filled outline-none placeholder:text-components-input-text-placeholder'
|
||||
readOnly
|
||||
value={isOpen ? '' : displayValue}
|
||||
@@ -200,28 +205,34 @@ const TimePicker = ({
|
||||
<PortalToFollowElem
|
||||
open={isOpen}
|
||||
onOpenChange={setIsOpen}
|
||||
placement='bottom-end'
|
||||
placement={placement}
|
||||
>
|
||||
<PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemTrigger className={triggerFullWidth ? '!block w-full' : undefined}>
|
||||
{renderTrigger ? (renderTrigger({
|
||||
inputElem,
|
||||
onClick: handleClickTrigger,
|
||||
isOpen,
|
||||
})) : (
|
||||
<div
|
||||
className='group flex w-[252px] cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal px-2 py-1 hover:bg-state-base-hover-alt'
|
||||
className={cn(
|
||||
'group flex cursor-pointer items-center gap-x-0.5 rounded-lg bg-components-input-bg-normal px-2 py-1 hover:bg-state-base-hover-alt',
|
||||
triggerFullWidth ? 'w-full min-w-0' : 'w-[252px]',
|
||||
)}
|
||||
onClick={handleClickTrigger}
|
||||
>
|
||||
{inputElem}
|
||||
{showTimezone && timezone && (
|
||||
<TimezoneLabel timezone={timezone} inline className='shrink-0 select-none text-xs' />
|
||||
)}
|
||||
<RiTimeLine className={cn(
|
||||
'h-4 w-4 shrink-0 text-text-quaternary',
|
||||
isOpen ? 'text-text-secondary' : 'group-hover:text-text-secondary',
|
||||
(displayValue || (isOpen && selectedTime)) && 'group-hover:hidden',
|
||||
(displayValue || (isOpen && selectedTime)) && !notClearable && 'group-hover:hidden',
|
||||
)} />
|
||||
<RiCloseCircleFill
|
||||
className={cn(
|
||||
'hidden h-4 w-4 shrink-0 text-text-quaternary',
|
||||
(displayValue || (isOpen && selectedTime)) && 'hover:text-text-secondary group-hover:inline-block',
|
||||
(displayValue || (isOpen && selectedTime)) && !notClearable && 'hover:text-text-secondary group-hover:inline-block',
|
||||
)}
|
||||
role='button'
|
||||
aria-label={t('common.operation.clear')}
|
||||
|
||||
Reference in New Issue
Block a user