Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: timerpicker can't find second #2398 #2417

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
496 changes: 493 additions & 3 deletions py/h2o_lightwave/h2o_lightwave/types.py

Large diffs are not rendered by default.

180 changes: 177 additions & 3 deletions py/h2o_lightwave/h2o_lightwave/ui.py

Large diffs are not rendered by default.

496 changes: 493 additions & 3 deletions py/h2o_wave/h2o_wave/types.py

Large diffs are not rendered by default.

180 changes: 177 additions & 3 deletions py/h2o_wave/h2o_wave/ui.py

Large diffs are not rendered by default.

296 changes: 261 additions & 35 deletions r/R/ui.R

Large diffs are not rendered by default.

Large diffs are not rendered by default.

102 changes: 51 additions & 51 deletions tools/vscode-extension/component-snippets.json

Large diffs are not rendered by default.

56 changes: 28 additions & 28 deletions ui/src/time_picker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,95 +48,95 @@ describe('time_picker.tsx', () => {
})

it('Sets args - init - value specified', async () => {
render(<XTimePicker model={{ ...timepickerProps, value: '10:30' }} />)
render(<XTimePicker model={{ ...timepickerProps, value: '10:30:00' }} />)
await waitForIdleEventLoop()
expect(wave.args[name]).toBe('10:30')
expect(wave.args[name]).toBe('10:30:00')
})

it('Set args when value is updated to different value', async () => {
const { rerender } = render(<XTimePicker model={{ ...timepickerProps, value: '15:00' }} />)
const { rerender } = render(<XTimePicker model={{ ...timepickerProps, value: '15:00:00' }} />)
await waitForIdleEventLoop()
expect(wave.args[name]).toBe('15:00')
rerender(<XTimePicker model={{ ...timepickerProps, value: '15:30' }} />)
expect(wave.args[name]).toBe('15:30')
expect(wave.args[name]).toBe('15:00:00')
rerender(<XTimePicker model={{ ...timepickerProps, value: '15:30:00' }} />)
expect(wave.args[name]).toBe('15:30:00')
})

it('Set args when value is updated to initial value', async () => {
const { container, rerender } = render(<XTimePicker model={{ ...timepickerProps, value: '04:00' }} />)
const { container, rerender } = render(<XTimePicker model={{ ...timepickerProps, value: '04:00:00' }} />)
await waitForIdleEventLoop()
expect(wave.args[name]).toBe('04:00')
expect(wave.args[name]).toBe('04:00:00')

await waitFor(() => fireEvent.click(container.querySelector("input")!))
fireEvent.keyDown(screen.getByRole('listbox'), { key: 'ArrowUp' })

await waitForIdleEventLoop()
expect(wave.args[name]).toBe('05:00')
expect(wave.args[name]).toBe('05:00:00')

rerender(<XTimePicker model={{ ...timepickerProps, value: '04:00' }} />)
expect(wave.args[name]).toBe('04:00')
rerender(<XTimePicker model={{ ...timepickerProps, value: '04:00:00' }} />)
expect(wave.args[name]).toBe('04:00:00')
})

it('Show correct input value in 12 hour time format', async () => {
const { getByDisplayValue } = render(<XTimePicker model={{ ...timepickerProps, value: '14:30' }} />)
const { getByDisplayValue } = render(<XTimePicker model={{ ...timepickerProps, value: '14:30:00' }} />)
await waitForIdleEventLoop()
expect(getByDisplayValue('02:30 PM')).toBeInTheDocument()
expect(wave.args[name]).toBe('14:30')
expect(getByDisplayValue('02:30:00 PM')).toBeInTheDocument()
expect(wave.args[name]).toBe('14:30:00')
})

it('Shows midnight correctly in 12 hour time format', async () => {
const { getByDisplayValue } = render(<XTimePicker model={{ ...timepickerProps, value: '00:00' }} />)
await waitForIdleEventLoop()
expect(getByDisplayValue('12:00 AM')).toBeInTheDocument()
expect(wave.args[name]).toBe('00:00')
expect(getByDisplayValue('12:00:00 AM')).toBeInTheDocument()
expect(wave.args[name]).toBe('00:00:00')
})

it('Shows noon correctly in 12 hour time format', async () => {
const { getByDisplayValue } = render(<XTimePicker model={{ ...timepickerProps, value: '12:00' }} />)
await waitForIdleEventLoop()
expect(getByDisplayValue('12:00 PM')).toBeInTheDocument()
expect(wave.args[name]).toBe('12:00')
expect(getByDisplayValue('12:00:00 PM')).toBeInTheDocument()
expect(wave.args[name]).toBe('12:00:00')
})

it('Show correct input value in 24 hour time format', async () => {
const { getByDisplayValue } = render(<XTimePicker model={{ ...timepickerProps, hour_format: '24', value: '23:30' }} />)
await waitForIdleEventLoop()
expect(getByDisplayValue('23:30')).toBeInTheDocument()
expect(wave.args[name]).toBe('23:30')
expect(getByDisplayValue('23:30:00')).toBeInTheDocument()
expect(wave.args[name]).toBe('23:30:00')
})

it('Value cannot be updated to be greater than max', async () => {
const { rerender } = render(<XTimePicker model={{ ...timepickerProps, min: '00:00', max: '10:00', value: '04:00' }} />)
await waitForIdleEventLoop()
expect(wave.args[name]).toBe('04:00')
expect(wave.args[name]).toBe('04:00:00')
rerender(<XTimePicker model={{ ...timepickerProps, min: '00:00', max: '10:00', value: '14:00' }} />)
await waitForIdleEventLoop()
expect(wave.args[name]).toBe('10:00')
expect(wave.args[name]).toBe('10:00:00')
})

it('Value cannot be updated to be lower than min', async () => {
const { rerender } = render(<XTimePicker model={{ ...timepickerProps, min: '02:00', max: '10:00', value: '04:00' }} />)
await waitForIdleEventLoop()
expect(wave.args[name]).toBe('04:00')
expect(wave.args[name]).toBe('04:00:00')
rerender(<XTimePicker model={{ ...timepickerProps, min: '02:00', max: '10:00', value: '01:00' }} />)
await waitForIdleEventLoop()
expect(wave.args[name]).toBe('02:00')
expect(wave.args[name]).toBe('02:00:00')
})

it('Changes out of bounds value when min is updated', async () => {
const { rerender } = render(<XTimePicker model={{ ...timepickerProps, min: '00:00', max: '10:00', value: '04:00' }} />)
await waitForIdleEventLoop()
expect(wave.args[name]).toBe('04:00')
expect(wave.args[name]).toBe('04:00:00')
rerender(<XTimePicker model={{ ...timepickerProps, min: '05:00', max: '10:00', value: '04:00' }} />)
await waitForIdleEventLoop()
expect(wave.args[name]).toBe('05:00')
expect(wave.args[name]).toBe('05:00:00')
})

it('Changes out of bounds value when max is updated', async () => {
const { rerender } = render(<XTimePicker model={{ ...timepickerProps, min: '00:00', max: '10:00', value: '04:00' }} />)
await waitForIdleEventLoop()
expect(wave.args[name]).toBe('04:00')
expect(wave.args[name]).toBe('04:00:00')
rerender(<XTimePicker model={{ ...timepickerProps, min: '00:00', max: '03:00', value: '04:00' }} />)
await waitForIdleEventLoop()
expect(wave.args[name]).toBe('03:00')
expect(wave.args[name]).toBe('03:00:00')
})
})
19 changes: 10 additions & 9 deletions ui/src/time_picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export interface TimePicker {
label?: S
/** A string that provides a brief hint to the user as to what kind of information is expected in the field. */
placeholder?: S
/** The time value in hh:mm format. E.g. '10:30', '14:25', '23:59', '00:00' */
/** The time value in hh:mm:ss format. E.g. '10:30:45', '14:25:30', '23:59:59', '00:00:00' */
value?: S
/** True if this field is disabled. */
disabled?: B
Expand All @@ -52,9 +52,9 @@ export interface TimePicker {
required?: B
/** Specifies 12-hour or 24-hour time format. One of `12` or `24`. Defaults to `12`. */
hour_format?: S
/** The minimum allowed time value in hh:mm format. E.g.: '08:00', '13:30' */
/** The minimum allowed time value in hh:mm:ss format. E.g.: '08:00:00', '13:30:00' */
min?: S
/** The maximum allowed time value in hh:mm format. E.g.: '15:30', '00:00' */
/** The maximum allowed time value in hh:mm:ss format. E.g.: '15:30:00', '00:00:00' */
max?: S
/** Limits the available minutes to select from. One of `1`, `5`, `10`, `15`, `20`, `30` or `60`. Defaults to `1`. */
minutes_step?: U
Expand Down Expand Up @@ -104,7 +104,7 @@ const
LocalizationProvider = React.lazy(() => import('@mui/x-date-pickers/LocalizationProvider').then(({ LocalizationProvider }) => ({ default: LocalizationProvider }))),
TimePicker = React.lazy(() => import('@mui/x-date-pickers/TimePicker').then(({ TimePicker }) => ({ default: TimePicker }))),
allowedMinutesSteps: { [key: U]: U } = { 1: 1, 5: 5, 10: 10, 15: 15, 20: 20, 30: 30, 60: 60 },
normalize = (time: S) => `2000-01-01T${time.slice(0, 5)}:00`,
normalize = (time: S) => `2000-01-01T${time.slice(0, 8)}`,
isBelowMin = (time: Date, minTime: Date) => time < minTime,
isOverMax = (time: Date, maxTime: Date) => time > maxTime,
isOutOfBounds = (time: Date, minTime: Date, maxTime: Date) => isBelowMin(time, minTime) || isOverMax(time, maxTime),
Expand Down Expand Up @@ -135,8 +135,9 @@ const
{label && <Fluent.Label className={css.toolbarLabel}>{label}</Fluent.Label>}
<Fluent.Text className={css.toolbarText}>
<Fluent.Text className={css.toolbarTime} onClick={() => setOpenView('hours')}>{time?.substring(0, 2) || '--'}</Fluent.Text>:
<Fluent.Text className={css.toolbarTime} onClick={() => setOpenView('minutes')}>{time?.substring(3, 5) || '--'}</Fluent.Text>{' '}
<Fluent.Text className={css.toolbarAmPm}>{time?.substring(6, 8) || ''}</Fluent.Text>
<Fluent.Text className={css.toolbarTime} onClick={() => setOpenView('minutes')}>{time?.substring(3, 5) || '--'}</Fluent.Text>:
<Fluent.Text className={css.toolbarTime} onClick={() => setOpenView('seconds')}>{time?.substring(6, 8) || '--'}</Fluent.Text>{' '}
<Fluent.Text className={css.toolbarAmPm}>{time?.substring(9, 11) || ''}</Fluent.Text>
</Fluent.Text>
</div>

Expand All @@ -149,8 +150,8 @@ export const
[isDialogOpen, setIsDialogOpen] = React.useState(false),
textInputRef = React.useRef<HTMLDivElement | null>(null),
popperRef = React.useRef<HTMLDivElement | null>(null),
minTime = React.useMemo(() => parseTimeStringToDate(min || '00:00'), [min]),
maxTime = React.useMemo(() => parseTimeStringToDate(max || '24:00'), [max]),
minTime = React.useMemo(() => parseTimeStringToDate(min || '00:00:00'), [min]),
maxTime = React.useMemo(() => parseTimeStringToDate(max || '24:00:00'), [max]),
onChangeTime = (time: unknown) => {
if (!(time instanceof Date)) return
const newValue = formatDateToTimeString(time, '24')
Expand Down Expand Up @@ -192,7 +193,7 @@ export const
},
},
{ format, AdapterDateFns, theme } = useTime(themeObj),
formatDateToTimeString = React.useCallback((date: D, hour_format: S) => format ? format(date, hour_format === '12' ? 'hh:mm aa' : 'HH:mm') : '', [format])
formatDateToTimeString = React.useCallback((date: D, hour_format: S) => format ? format(date, hour_format === '12' ? 'hh:mm:ss aa' : 'HH:mm:ss') : '', [format])

React.useEffect(() => {
const time = m.value ? parseTimeStringToDate(m.value) : null
Expand Down