Skip to content

Commit

Permalink
Feat: timerpicker can't find second h2oai#2398
Browse files Browse the repository at this point in the history
Added seconds select in time picker and updated the testing to be in format
  • Loading branch information
akwaed committed Oct 17, 2024
1 parent cc61555 commit 5fb00bd
Show file tree
Hide file tree
Showing 10 changed files with 1,903 additions and 186 deletions.
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.

153 changes: 102 additions & 51 deletions tools/intellij-plugin/src/main/resources/templates/wave-components.xml

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.

111 changes: 111 additions & 0 deletions ui/src/defs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,116 @@ export const cardDefs: CardDef[] = [
"value": {}
}
]
},
{
"view": "chat",
"icon": "OfficeChat",
"attrs": [
{
"name": "box",
"optional": false,
"t": "box",
"value": "{\"zone\":\"Body\"}"
},
{
"name": "title",
"optional": false,
"t": "textbox",
"value": "Untitled Chat"
},
{
"name": "data",
"optional": false,
"t": "record",
"value": {}
},
{
"name": "capacity",
"optional": true,
"t": "spinbox",
"value": 50,
"min": 10,
"max": 10000,
"step": 10
}
]
},
{
"view": "frame",
"icon": "PageAdd",
"attrs": [
{
"name": "box",
"optional": false,
"t": "box",
"value": "{\"zone\":\"Body\"}"
},
{
"name": "title",
"optional": false,
"t": "textbox",
"value": "Untitled Frame"
},
{
"name": "path",
"optional": true,
"t": "textbox",
"value": ""
},
{
"name": "content",
"optional": true,
"t": "textarea",
"value": ""
}
]
},
{
"view": "markdown",
"icon": "InsertTextBox",
"attrs": [
{
"name": "box",
"optional": false,
"t": "box",
"value": "{\"zone\":\"Body\"}"
},
{
"name": "title",
"optional": false,
"t": "textbox",
"value": "Untitled Content"
},
{
"name": "content",
"optional": false,
"t": "textarea",
"value": "Hello, World!"
}
]
},
{
"view": "markup",
"icon": "FileHTML",
"attrs": [
{
"name": "box",
"optional": false,
"t": "box",
"value": "{\"zone\":\"Body\"}"
},
{
"name": "title",
"optional": false,
"t": "textbox",
"value": "Untitled Content"
},
{
"name": "content",
"optional": false,
"t": "textarea",
"value": "<div/>"
}
]
}
]
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

0 comments on commit 5fb00bd

Please sign in to comment.