Skip to content

Commit

Permalink
fix: make value behavior consistent #1154
Browse files Browse the repository at this point in the history
  • Loading branch information
aalencar committed Mar 25, 2022
1 parent bdc99f9 commit 656c47c
Show file tree
Hide file tree
Showing 17 changed files with 346 additions and 178 deletions.
135 changes: 135 additions & 0 deletions py/examples/debug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Nav
# Use nav cards to display #sidebar #navigation.
# ---
from h2o_wave import main, app, Q, ui


persona = 'https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&h=750&w=1260'

choices = [
ui.choice('A', 'Option A'),
ui.choice('B', 'Option B'),
ui.choice('C', 'Option C', disabled=True),
ui.choice('D', 'Option D'),
]

combobox_choices = ['Cyan', 'Magenta', 'Yellow', 'Black']

tabs = [
ui.tab(name='email', label='Mail', icon='Mail'),
ui.tab(name='events', label='Events', icon='Calendar'),
ui.tab(name='spam', label='Spam'),
]


@app('/demo')
async def serve(q: Q):

content = 'Welcome to our store!'
location = q.args['#']
if location:
if location == 'menu/spam':
content = "Sorry, we're out of spam!"
elif location == 'menu/ham':
content = "Sorry, we're out of ham!"
elif location == 'menu/eggs':
content = "Sorry, we're out of eggs!"
elif location == 'about':
content = 'Everything here is gluten-free!'

if not q.client.initialized:
q.page['example'] = ui.form_card(box='3 4 4 10', items=[
ui.checkbox(name='checkbox', label='Checkbox', value=False),
ui.dropdown(name='dropdown', label='Choices', value='',
choices=[ui.choice(name=x, label=x) for x in ['Egg', 'Bacon', 'Spam']]),
ui.dropdown(name='dropdown2', label='Choices', values=[],
choices=[ui.choice(name=x, label=x) for x in ['Egg', 'Bacon', 'Spam']]),
ui.dropdown(name='dropdown3', label='Choices', popup='always', value='',
choices=[ui.choice(name=x, label=x) for x in ['Egg', 'Bacon', 'Spam']]),
ui.dropdown(name='dropdown4', label='Choices', popup='always', values=[],
choices=[ui.choice(name=x, label=x) for x in ['Egg', 'Bacon', 'Spam']]),
ui.checklist(name='checklist', values=['Egg', 'Spam'], label='Choices',
choices=[ui.choice(name=x, label=x) for x in ['Egg', 'Bacon', 'Spam']]),
ui.choice_group(name='choice_group', label='Pick one', value='B', required=True, choices=choices),
ui.color_picker(name='color', label='Pick a color', value='#F25F5C'),
ui.combobox(name='combobox', label='Enter or choose a color', placeholder='Color...', value='Blue',
choices=combobox_choices),
ui.copyable_text(label='Copyable text', value='foo'),
ui.date_picker(name='date', label='Standard date picker', value='2017-10-19'),
ui.picker(name='picker', label='Place an order (try Spam, Eggs or Ham):', choices=[
ui.choice(name='spam', label='Spam'),
ui.choice(name='eggs', label='Eggs'),
ui.choice(name='ham', label='Ham'),
ui.choice(name='cheese', label='Cheese'),
ui.choice(name='beans', label='Beans'),
ui.choice(name='toast', label='Toast'),
], values=['eggs']),
ui.progress(label='Indeterminate Progress', caption='Goes on forever', value=0.5),
ui.slider(name='slider', label='Standard slider', min=0, max=100, step=10, value=30),
ui.spinbox(name='spinbox', label='Standard spinbox', min=0, max=100, step=10, value=30),
ui.tabs(name='menu', value='spam', items=tabs),
ui.toggle(name='toggle', label='Not checked'),
ui.button(name='change', label='Change', primary=True),
])

q.page['tab'] = ui.tab_card(
box='3 1 4 1',
items=[
ui.tab(name='#menu/spam', label='Spam'),
ui.tab(name='#menu/ham', label='Ham'),
ui.tab(name='#menu/eggs', label='Eggs'),
ui.tab(name='#about', label='About'),
],
value='#menu/ham',
)

q.page['nav'] = ui.nav_card(
box='1 1 2 -1',
value='#menu/spam',
title='H2O Wave',
subtitle='And now for something completely different!',
items=[
ui.nav_group('Menu', items=[
ui.nav_item(name='#menu/spam', label='Spam'),
ui.nav_item(name='#menu/ham', label='Ham'),
ui.nav_item(name='#menu/eggs', label='Eggs', tooltip='Make me a scrambled egg.'),
ui.nav_item(name='#menu/toast', label='Toast'),
])
],
)

q.page['blurb'] = ui.markdown_card(
box='3 2 4 2',
title='Store',
content=content,
)

q.client.initialized = True

if q.args.change:
q.page['example'].items[0].checkbox.value = True
q.page['example'].items[0].checkbox.value = True
q.page['example'].items[1].dropdown.value = 'Bacon'
q.page['example'].items[2].dropdown.values = ['Spam', 'Bacon']
q.page['example'].items[3].dropdown.value = 'Bacon'
q.page['example'].items[4].dropdown.values = ['Spam', 'Bacon']
q.page['example'].items[5].checklist.values = ['Bacon']
q.page['example'].items[6].choice_group.value = 'A'
q.page['example'].items[7].color_picker.value = '#FFFFF8'
q.page['example'].items[8].combobox.value = 'Yellow'
q.page['example'].items[9].copyable_text.value = 'bar'
q.page['example'].items[10].date_picker.value = '2022-03-22'
q.page['example'].items[11].picker.values = ['ham', 'toast']
q.page['example'].items[12].progress.value = 0.75
q.page['example'].items[13].slider.value = 50
q.page['example'].items[14].spinbox.value = 50
q.page['example'].items[15].tabs.value = 'events'
q.page['example'].items[16].toggle.value = True

q.page['tab'].value = '#menu/spam'
q.page['nav'].value = '#menu/spam'
if location:
blurb = q.page['blurb']
blurb.content = content

await q.page.save()
29 changes: 17 additions & 12 deletions ui/src/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import * as Fluent from '@fluentui/react'
import { B, Id, S } from 'h2o-wave'
import React from 'react'
import { useControlledComponent } from './hooks'
import { wave } from './ui'

/**
Expand Down Expand Up @@ -55,24 +56,28 @@ export interface Checkbox {
}

export const
XCheckbox = ({ model: m }: { model: Checkbox }) => {
const onChange = (_e?: React.FormEvent<HTMLElement>, checked?: B) => {
wave.args[m.name] = checked === null ? null : !!checked
if (m.trigger) wave.push()
}
XCheckbox = (props: { model: Checkbox }) => {
const
{ name, label, disabled, indeterminate, trigger } = props.model,
onChange = (_e?: React.FormEvent<HTMLElement>, checked?: B) => {
setValue(checked)
wave.args[name] = checked === null ? null : !!checked
if (trigger) wave.push()
},
[value, setValue] = useControlledComponent(props, props.model.value)

// eslint-disable-next-line react-hooks/exhaustive-deps
React.useEffect(() => { wave.args[m.name] = !!m.value }, [])
React.useEffect(() => { wave.args[name] = !!value }, [])

return (
<Fluent.Checkbox
data-test={m.name}
inputProps={{ 'data-test': m.name } as React.ButtonHTMLAttributes<HTMLButtonElement>}
label={m.label}
defaultIndeterminate={m.indeterminate}
defaultChecked={m.value}
data-test={name}
inputProps={{ 'data-test': name } as React.ButtonHTMLAttributes<HTMLButtonElement>}
label={label}
defaultIndeterminate={indeterminate}
checked={!!value}
onChange={onChange}
disabled={m.disabled}
disabled={disabled}
/>
)
}
9 changes: 5 additions & 4 deletions ui/src/checklist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { B, Id, S, U } from 'h2o-wave'
import React from 'react'
import { stylesheet } from 'typestyle'
import { Choice } from './choice_group'
import { useControlledComponent } from './hooks'
import { clas, margin } from './theme'
import { wave } from './ui'

Expand Down Expand Up @@ -74,13 +75,13 @@ export const
const
defaultSelection = React.useMemo(() => new Set<S>(m.values), [m.values]),
getMappedChoices = React.useCallback(() => m.choices?.map(c => ({ c, selected: defaultSelection.has(c.name) })) || [], [defaultSelection, m.choices]),
[choices, setChoices] = React.useState(getMappedChoices()),
[choices, setChoices] = useControlledComponent(m, getMappedChoices()),
capture = (choices: { c: Choice, selected: B }[]) => {
wave.args[m.name] = choices.filter(({ selected }) => selected).map(({ c }) => c.name)
if (m.trigger) wave.push()
},
select = (value: B) => {
const _choices = choices.map(({ c, selected }) => ({ c, selected: c.disabled ? selected : value }))
const _choices = choices.map(({ c, selected }: { c: Choice, selected: B}) => ({ c, selected: c.disabled ? selected : value }))
setChoices(_choices)
capture(_choices)
},
Expand All @@ -92,7 +93,7 @@ export const
setChoices(_choices)
capture(_choices)
},
items = choices.map(({ c, selected }, i) => (
items = choices.map(({ c, selected }: { c: Choice, selected: B}, i: number) => (
<Fluent.Checkbox
key={i}
data-test={`checkbox-${i + 1}`}
Expand All @@ -106,7 +107,7 @@ export const
))
// eslint-disable-next-line react-hooks/exhaustive-deps
React.useEffect(() => { wave.args[m.name] = m.values || [] }, [])
React.useEffect(() => { setChoices(getMappedChoices()) }, [getMappedChoices, m.choices])
React.useEffect(() => { setChoices(getMappedChoices()) }, [getMappedChoices, m.choices, setChoices])
return (
<div data-test={m.name}>
<Fluent.Label>{m.label}</Fluent.Label>
Expand Down
22 changes: 13 additions & 9 deletions ui/src/choice_group.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import * as Fluent from '@fluentui/react'
import { B, Id, S } from 'h2o-wave'
import React from 'react'
import { useControlledComponent } from './hooks'
import { wave } from './ui'

/**
Expand Down Expand Up @@ -65,25 +66,28 @@ export interface ChoiceGroup {
}

export const
XChoiceGroup = ({ model: m }: { model: ChoiceGroup }) => {
XChoiceGroup = (props: { model: ChoiceGroup }) => {
const
{ name, label, required, trigger } = props.model,
[value, setValue] = useControlledComponent(props, props.model.value),
optionStyles = { choiceFieldWrapper: { marginRight: 15 } },
options = (m.choices || []).map(({ name, label, disabled }): Fluent.IChoiceGroupOption => ({ key: name, text: label || name, disabled, styles: optionStyles })),
options = (props.model.choices || []).map(({ name, label, disabled }): Fluent.IChoiceGroupOption => ({ key: name, text: label || name, disabled, styles: optionStyles })),
onChange = (_e?: React.FormEvent<HTMLElement>, option?: Fluent.IChoiceGroupOption) => {
if (option) wave.args[m.name] = option.key
if (m.trigger) wave.push()
setValue(option?.key)
if (option) wave.args[name] = option.key
if (trigger) wave.push()
}

// eslint-disable-next-line react-hooks/exhaustive-deps
React.useEffect(() => { wave.args[m.name] = m.value || null }, [])
React.useEffect(() => { wave.args[name] = value || null }, [])

return (
<Fluent.ChoiceGroup
styles={{ flexContainer: { display: 'flex', flexWrap: 'wrap' } }}
data-test={m.name}
label={m.label}
required={m.required}
defaultSelectedKey={m.value}
data-test={name}
label={label}
required={required}
selectedKey={value}
options={options}
onChange={onChange}
/>
Expand Down
6 changes: 4 additions & 2 deletions ui/src/combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import * as Fluent from '@fluentui/react'
import { B, Id, S } from 'h2o-wave'
import React from 'react'
import { useControlledComponent } from './hooks'
import { wave } from './ui'

/**
Expand Down Expand Up @@ -57,9 +58,10 @@ export interface Combobox {


export const
XCombobox = ({ model: m }: { model: Combobox }) => {
XCombobox = (props: { model: Combobox }) => {
const
[text, setText] = React.useState(m.value),
m = props.model,
[text, setText] = useControlledComponent(props, m.value),
options = (m.choices || []).map((text, i): Fluent.IComboBoxOption => ({ key: `${i}`, text })),
onChange = (_e: React.FormEvent<Fluent.IComboBox>, option?: Fluent.IComboBoxOption, _index?: number, value?: string) => {
const v = option?.text || value || ''
Expand Down
2 changes: 1 addition & 1 deletion ui/src/copyable_text.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export const XCopyableText = ({ model }: { model: CopyableText }) => {

return (
<div data-test={name} className={multiline ? css.multiContainer : css.compactContainer}>
<Fluent.TextField componentRef={ref} defaultValue={value} label={label} multiline={multiline} styles={{ root: { width: pc(100) } }} readOnly />
<Fluent.TextField componentRef={ref} value={value} label={label} multiline={multiline} styles={{ root: { width: pc(100) } }} readOnly />
<Fluent.PrimaryButton
title='Copy to clipboard'
onClick={onClick}
Expand Down
15 changes: 8 additions & 7 deletions ui/src/date_picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import * as Fluent from '@fluentui/react'
import { B, Id, S, U } from 'h2o-wave'
import React from 'react'
import { useControlledComponent } from './hooks'
import { wave } from './ui'

/**
Expand Down Expand Up @@ -56,19 +57,19 @@ const
if (ss.length !== 3) return undefined
const ymd = ss.map(s => parseInt(s, 10)).filter(n => !isNaN(n))
if (ymd.length !== 3) return undefined
return new Date(ymd[0], ymd[1] - 1, ymd[2])
return new Date(ymd[0], ymd[1] - 1, ymd[2], 0, 0, 0)
}

export const
XDatePicker = ({ model: m }: { model: DatePicker }) => {
XDatePicker = (props: { model: DatePicker }) => {
const
m = props.model,
defaultVal = m.value || null,
parsedVal = defaultVal ? parseDate(defaultVal) : null,
[value, setValue] = React.useState<Date | undefined>(parsedVal ? new Date(parsedVal) : undefined),
[value, setValue] = useControlledComponent(props, m.value),
onSelectDate = (d: Date | null | undefined) => {
const val = (d === null || d === undefined) ? defaultVal : formatDate(d)
const val = !d ? defaultVal : formatDate(d)
wave.args[m.name] = val
setValue(val ? new Date(`${val} 00:00:00`) : undefined)
setValue(val)
if (m.trigger) wave.push()
}

Expand All @@ -79,7 +80,7 @@ export const
<Fluent.DatePicker
data-test={m.name}
label={m.label}
value={value}
value={parseDate(value)}
placeholder={m.placeholder}
disabled={m.disabled}
onSelectDate={onSelectDate}
Expand Down
Loading

0 comments on commit 656c47c

Please sign in to comment.