Skip to content

Commit f51ee5a

Browse files
Implement searchbar_card for wave
searchbar_card can be used to horizontally align textbox and button on the requirement. Fixes: #372
1 parent 80def55 commit f51ee5a

File tree

7 files changed

+301
-0
lines changed

7 files changed

+301
-0
lines changed

py/examples/searchbar.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# SearchBar
2+
# SearchBar is used to align textbox and button components horizontally.
3+
# searchbar_card can be used to horizontally align textbox and button on the requirement.
4+
# ---
5+
from h2o_wave import site, ui
6+
7+
page = site['/demo']
8+
9+
10+
page["search_bar"] = ui.searchbar_card(
11+
box="1 1 -1 1", items=[
12+
ui.textbox(name='text', label='', placeholder='#wave', multiline=False, trigger=False),
13+
ui.button(name="search", label="search", primary=True),
14+
], direction=ui.SearchBarDirection.ROW, justify=ui.SearchBarJustify.CENTER)
15+
16+
page.save()

py/examples/tour.conf

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ form_menu.py
5454
form_template.py
5555
form_markup.py
5656
stepper.py
57+
searchbar.py
5758
table_markdown.py
5859
table.py
5960
table_sort.py

py/h2o_wave/types.py

+105
Original file line numberDiff line numberDiff line change
@@ -5459,6 +5459,111 @@ def load(__d: Dict) -> 'Layout':
54595459
max_height,
54605460
)
54615461

5462+
class SearchBarDirection:
5463+
ROW = 'row'
5464+
COLUMN = 'column'
5465+
5466+
5467+
class SearchBarJustify:
5468+
START = 'start'
5469+
END = 'end'
5470+
CENTER = 'center'
5471+
BETWEEN = 'between'
5472+
AROUND = 'around'
5473+
5474+
5475+
class SearchBarAlign:
5476+
START = 'start'
5477+
END = 'end'
5478+
CENTER = 'center'
5479+
STRETCH = 'stretch'
5480+
5481+
5482+
class SearchBarWrap:
5483+
START = 'start'
5484+
END = 'end'
5485+
CENTER = 'center'
5486+
BETWEEN = 'between'
5487+
AROUND = 'around'
5488+
STRETCH = 'stretch'
5489+
5490+
class SearchBarCard:
5491+
"""Create a searchbar.
5492+
"""
5493+
def __init__(
5494+
self,
5495+
box: str,
5496+
items: Union[List[Component], str],
5497+
direction: Optional[str] = None,
5498+
justify: Optional[str] = None,
5499+
align: Optional[str] = None,
5500+
wrap: Optional[str] = None,
5501+
commands: Optional[List[Command]] = None,
5502+
):
5503+
self.box = box
5504+
"""A string indicating how to place this component on the page."""
5505+
self.items = items
5506+
"""The components in this searchbar."""
5507+
self.direction = direction
5508+
"""SearchBar direction. One of 'horizontal', 'vertical'. See enum h2o_wave.ui.SearchBarDirection."""
5509+
self.justify = justify
5510+
"""SearchBar strategy for main axis. One of 'start', 'end', 'center', 'between', 'around'. See enum h2o_wave.ui.SearchBarJustify."""
5511+
self.align = align
5512+
"""SearchBar strategy for cross axis. One of 'start', 'end', 'center', 'baseline', 'stretch'. See enum h2o_wave.ui.SearchBarAlign."""
5513+
self.wrap = wrap
5514+
"""SearchBar strategy. One of 'start', 'end', 'center', 'between', 'around', 'stretch'. See enum h2o_wave.ui.SearchBarWrap."""
5515+
self.commands = commands
5516+
"""Contextual menu commands for this component."""
5517+
5518+
def dump(self) -> Dict:
5519+
"""Returns the contents of this object as a dict."""
5520+
if self.box is None:
5521+
raise ValueError('SearchBarCard.box is required.')
5522+
if self.items is None:
5523+
raise ValueError('SearchBarCard.items is required.')
5524+
return _dump(
5525+
view='searchbar',
5526+
box=self.box,
5527+
items=self.items if isinstance(self.items, str) else [__e.dump() for __e in self.items],
5528+
direction=self.direction,
5529+
justify=self.justify,
5530+
align=self.align,
5531+
wrap=self.wrap,
5532+
commands=None if self.commands is None else [__e.dump() for __e in self.commands],
5533+
)
5534+
5535+
@staticmethod
5536+
def load(__d: Dict) -> 'SearchBarCard':
5537+
"""Creates an instance of this class using the contents of a dict."""
5538+
__d_box: Any = __d.get('box')
5539+
if __d_box is None:
5540+
raise ValueError('SearchBarCard.box is required.')
5541+
__d_items: Any = __d.get('items')
5542+
if __d_items is None:
5543+
raise ValueError('SearchBarCard.items is required.')
5544+
__d_direction: Any = __d.get('direction')
5545+
__d_justify: Any = __d.get('justify')
5546+
__d_align: Any = __d.get('align')
5547+
__d_wrap: Any = __d.get('wrap')
5548+
# if __d_direction is None:
5549+
# raise ValueError('SearchBarCard.direction is required.')
5550+
__d_commands: Any = __d.get('commands')
5551+
box: str = __d_box
5552+
items: Union[List[Component], str] = __d_items if isinstance(__d_items, str) else [Component.load(__e) for __e in __d_items]
5553+
direction: Optional[str] = __d_direction
5554+
justify: Optional[str] = __d_justify
5555+
align: Optional[str] = __d_align
5556+
wrap: Optional[str] = __d_wrap
5557+
commands: Optional[List[Command]] = None if __d_commands is None else [Command.load(__e) for __e in __d_commands]
5558+
return SearchBarCard(
5559+
box,
5560+
items,
5561+
direction,
5562+
justify,
5563+
align,
5564+
wrap,
5565+
commands,
5566+
)
54625567

54635568
class Dialog:
54645569
"""A dialog box (Dialog) is a temporary pop-up that takes focus from the page or app

py/h2o_wave/ui.py

+33
Original file line numberDiff line numberDiff line change
@@ -1835,6 +1835,39 @@ def form_card(
18351835
)
18361836

18371837

1838+
def searchbar_card(
1839+
box: str,
1840+
items: Union[List[Component], str],
1841+
direction: Optional[str] = None,
1842+
justify: Optional[str] = None,
1843+
align: Optional[str] = None,
1844+
wrap: Optional[str] = None,
1845+
commands: Optional[List[Command]] = None,
1846+
) -> SearchBarCard:
1847+
"""Create a searchbar.
1848+
1849+
Args:
1850+
box: A string indicating how to place this component on the page.
1851+
items: The components in this searchbar.
1852+
direction: Layout direction. One of 'horizontal', 'vertical'. See enum h2o_wave.ui.FlexCardDirection.
1853+
justify: Layout strategy for main axis. One of 'start', 'end', 'center', 'between', 'around'. See enum h2o_wave.ui.FlexCardJustify.
1854+
align: Layout strategy for cross axis. One of 'start', 'end', 'center', 'baseline', 'stretch'. See enum h2o_wave.ui.FlexCardAlign.
1855+
wrap: Wrapping strategy. One of 'start', 'end', 'center', 'between', 'around', 'stretch'. See enum h2o_wave.ui.FlexCardWrap.
1856+
commands: Contextual menu commands for this component.
1857+
Returns:
1858+
A `h2o_wave.types.SearchBarCard` instance.
1859+
"""
1860+
return SearchBarCard(
1861+
box,
1862+
items,
1863+
direction,
1864+
justify,
1865+
align,
1866+
wrap,
1867+
commands,
1868+
)
1869+
1870+
18381871
def frame_card(
18391872
box: str,
18401873
title: str,

ui/src/cards.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import './none'
1818
import './pixel_art'
1919
import './plot'
2020
import './repeat'
21+
import './searchbar'
2122
import './small_series_stat'
2223
import './small_stat'
2324
import './tab'

ui/src/searchbar.test.tsx

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react'
2+
import { render } from '@testing-library/react'
3+
import { View } from './form'
4+
import * as T from './qd'
5+
6+
const
7+
name = 'searchbar',
8+
formProps: T.Card<any> = {
9+
name,
10+
state: {},
11+
changed: T.box(false)
12+
}
13+
14+
describe('Searchbar.tsx', () => {
15+
16+
it('Renders data-test attr', () => {
17+
const { queryByTestId } = render(<View {...formProps} />)
18+
expect(queryByTestId(name)).toBeInTheDocument()
19+
})
20+
})

ui/src/searchbar.tsx

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import * as Fluent from '@fluentui/react'
2+
import React from 'react'
3+
import {Button, Buttons, XButtons, XStandAloneButton} from './button'
4+
import {Label, XLabel} from './label'
5+
import {cards} from './layout'
6+
import {bond, Card, Dict, Packed, S, unpack, xid} from './qd'
7+
import {Textbox, XTextbox} from './textbox'
8+
import {getTheme} from './theme'
9+
import {XToolTip} from './tooltip'
10+
11+
12+
/** Create a SearchBar. */
13+
export interface SearchBar {
14+
/** Label. */
15+
label?: Label;
16+
/** Textbox. */
17+
textbox?: Textbox;
18+
/** Button. */
19+
button?: Button;
20+
/** Button set. */
21+
buttons?: Buttons;
22+
}
23+
24+
/** Create a searchbar. */
25+
interface State {
26+
/** The components in this searchbar. */
27+
items: Packed<SearchBar[]>;
28+
29+
/** SearchBar direction. */
30+
direction?: 'horizontal' | 'vertical'
31+
32+
/** SearchBar strategy for main axis. */
33+
justify?: 'start' | 'end' | 'center' | 'between' | 'around'
34+
35+
/** SearchBar strategy for cross axis. */
36+
align?: 'start' | 'end' | 'center' | 'baseline' | 'stretch'
37+
38+
/** SearchBar wrapping strategy. */
39+
wrap?: 'start' | 'end' | 'center' | 'between' | 'around' | 'stretch'
40+
}
41+
42+
43+
const
44+
theme = getTheme(),
45+
defaults: Partial<State> = {items: []},
46+
directions: Dict<S> = {
47+
horizontal: 'row',
48+
vertical: 'column',
49+
},
50+
justifications: Dict<S> = {
51+
start: 'flex-start',
52+
end: 'flex-end',
53+
center: 'center',
54+
between: 'space-between',
55+
around: 'space-around',
56+
},
57+
alignments: Dict<S> = {
58+
start: 'flex-start',
59+
end: 'flex-end',
60+
center: 'center',
61+
baseline: 'baseline',
62+
stretch: 'stretch',
63+
},
64+
wrappings: Dict<S> = {
65+
start: 'flex-start',
66+
end: 'flex-end',
67+
center: 'center',
68+
between: 'space-between',
69+
around: 'space-around',
70+
stretch: 'stretch',
71+
},
72+
toFlexStyle = (state: State): React.CSSProperties => {
73+
const
74+
css: React.CSSProperties = { display: 'flex', flexGrow: 1 },
75+
direction = directions[state.direction || ''],
76+
justify = justifications[state.justify || ''],
77+
align = alignments[state.align || ''],
78+
wrap = wrappings[state.wrap || '']
79+
80+
if (direction) css.flexDirection = direction as any
81+
if (justify) css.justifyContent = justify
82+
if (align) css.alignItems = align
83+
if (wrap) {
84+
css.flexWrap = 'wrap'
85+
css.alignContent = wrap
86+
}
87+
return css
88+
}
89+
90+
91+
export const
92+
XSearchBar = ({ items }: { items: SearchBar[] }) => {
93+
const components = items.map(m => <XStandardSearchBar key={xid()} model={m} />)
94+
return <>{components}</>
95+
}
96+
97+
const
98+
XStandardSearchBar = ({ model: m }: { model: SearchBar }) => {
99+
if (m.label) return <XToolTip content={m.label.tooltip} expand={false}><XLabel model={m.label} /></XToolTip>
100+
if (m.textbox) return <XToolTip content={m.textbox.tooltip}><XTextbox model={m.textbox} /></XToolTip>
101+
if (m.buttons) return <XButtons model={m.buttons} />
102+
if (m.button) return <XToolTip content={m.button.tooltip} showIcon={false} expand={false}><XStandAloneButton model={m.button} /></XToolTip>
103+
return <Fluent.MessageBar messageBarType={Fluent.MessageBarType.severeWarning}>This component could not be rendered.</Fluent.MessageBar>
104+
}
105+
106+
export const
107+
View = bond(({ name, state, changed }: Card<State>) => {
108+
109+
const
110+
render = () => {
111+
const
112+
s = theme.merge(defaults, state),
113+
items = unpack<SearchBar[]>(s.items)
114+
return (
115+
<div data-test={name} style={toFlexStyle(state)}>
116+
<XSearchBar items={items} />
117+
</div>
118+
)
119+
120+
}
121+
return { render, changed }
122+
})
123+
124+
cards.register('searchbar', View)
125+

0 commit comments

Comments
 (0)