diff --git a/py/examples/searchbar.py b/py/examples/searchbar.py new file mode 100644 index 00000000000..400edee2208 --- /dev/null +++ b/py/examples/searchbar.py @@ -0,0 +1,16 @@ +# SearchBar +# SearchBar is used to align textbox and button components horizontally. +# searchbar_card can be used to horizontally align textbox and button on the requirement. +# --- +from h2o_wave import site, ui + +page = site['/demo'] + + +page["search_bar"] = ui.searchbar_card( + box="1 1 -1 1", items=[ + ui.textbox(name='text', label='', placeholder='#wave', multiline=False, trigger=False), + ui.button(name="search", label="search", primary=True), + ], direction=ui.SearchBarDirection.ROW, justify=ui.SearchBarJustify.CENTER) + +page.save() diff --git a/py/examples/tour.conf b/py/examples/tour.conf index eac168fa201..6d3e3df9fbc 100644 --- a/py/examples/tour.conf +++ b/py/examples/tour.conf @@ -54,6 +54,7 @@ form_menu.py form_template.py form_markup.py stepper.py +searchbar.py table_markdown.py table.py table_sort.py diff --git a/py/h2o_wave/types.py b/py/h2o_wave/types.py index 66238fe189c..0a68758044c 100644 --- a/py/h2o_wave/types.py +++ b/py/h2o_wave/types.py @@ -5460,6 +5460,114 @@ def load(__d: Dict) -> 'Layout': ) +class SearchBarDirection: + ROW = 'row' + COLUMN = 'column' + + +class SearchBarJustify: + START = 'start' + END = 'end' + CENTER = 'center' + BETWEEN = 'between' + AROUND = 'around' + + +class SearchBarAlign: + START = 'start' + END = 'end' + CENTER = 'center' + STRETCH = 'stretch' + + +class SearchBarWrap: + START = 'start' + END = 'end' + CENTER = 'center' + BETWEEN = 'between' + AROUND = 'around' + STRETCH = 'stretch' + + +class SearchBarCard: + """Create a searchbar. + """ + def __init__( + self, + box: str, + items: Union[List[Component], str], + direction: Optional[str] = None, + justify: Optional[str] = None, + align: Optional[str] = None, + wrap: Optional[str] = None, + commands: Optional[List[Command]] = None, + ): + self.box = box + """A string indicating how to place this component on the page.""" + self.items = items + """The components in this searchbar.""" + self.direction = direction + """SearchBar direction. One of 'horizontal', 'vertical'. See enum h2o_wave.ui.SearchBarDirection.""" + self.justify = justify + """SearchBar strategy for main axis. One of 'start', 'end', 'center', 'between', 'around'. See enum h2o_wave.ui.SearchBarJustify.""" + self.align = align + """SearchBar strategy for cross axis. One of 'start', 'end', 'center', 'baseline', 'stretch'. See enum h2o_wave.ui.SearchBarAlign.""" + self.wrap = wrap + """SearchBar strategy. One of 'start', 'end', 'center', 'between', 'around', 'stretch'. See enum h2o_wave.ui.SearchBarWrap.""" + self.commands = commands + """Contextual menu commands for this component.""" + + def dump(self) -> Dict: + """Returns the contents of this object as a dict.""" + if self.box is None: + raise ValueError('SearchBarCard.box is required.') + if self.items is None: + raise ValueError('SearchBarCard.items is required.') + return _dump( + view='searchbar', + box=self.box, + items=self.items if isinstance(self.items, str) else [__e.dump() for __e in self.items], + direction=self.direction, + justify=self.justify, + align=self.align, + wrap=self.wrap, + commands=None if self.commands is None else [__e.dump() for __e in self.commands], + ) + + @staticmethod + def load(__d: Dict) -> 'SearchBarCard': + """Creates an instance of this class using the contents of a dict.""" + __d_box: Any = __d.get('box') + if __d_box is None: + raise ValueError('SearchBarCard.box is required.') + __d_items: Any = __d.get('items') + if __d_items is None: + raise ValueError('SearchBarCard.items is required.') + __d_direction: Any = __d.get('direction') + __d_justify: Any = __d.get('justify') + __d_align: Any = __d.get('align') + __d_wrap: Any = __d.get('wrap') + # if __d_direction is None: + # raise ValueError('SearchBarCard.direction is required.') + __d_commands: Any = __d.get('commands') + box: str = __d_box + items: Union[List[Component], str] = __d_items if isinstance(__d_items, str) else [Component.load(__e) for __e in __d_items] + direction: Optional[str] = __d_direction + justify: Optional[str] = __d_justify + align: Optional[str] = __d_align + wrap: Optional[str] = __d_wrap + commands: Optional[List[Command]] = None if __d_commands is None else [Command.load(__e) for __e in __d_commands] + return SearchBarCard( + box, + items, + direction, + justify, + align, + wrap, + commands, + ) + + class Dialog: """A dialog box (Dialog) is a temporary pop-up that takes focus from the page or app and requires people to interact with it. It’s primarily used for confirming actions, diff --git a/py/h2o_wave/ui.py b/py/h2o_wave/ui.py index 4fe18e37125..9994e5bbbdb 100644 --- a/py/h2o_wave/ui.py +++ b/py/h2o_wave/ui.py @@ -1835,6 +1835,39 @@ def form_card( ) +def searchbar_card( + box: str, + items: Union[List[Component], str], + direction: Optional[str] = None, + justify: Optional[str] = None, + align: Optional[str] = None, + wrap: Optional[str] = None, + commands: Optional[List[Command]] = None, +) -> SearchBarCard: + """Create a searchbar. + + Args: + box: A string indicating how to place this component on the page. + items: The components in this searchbar. + direction: Layout direction. One of 'horizontal', 'vertical'. See enum h2o_wave.ui.FlexCardDirection. + justify: Layout strategy for main axis. One of 'start', 'end', 'center', 'between', 'around'. See enum h2o_wave.ui.FlexCardJustify. + align: Layout strategy for cross axis. One of 'start', 'end', 'center', 'baseline', 'stretch'. See enum h2o_wave.ui.FlexCardAlign. + wrap: Wrapping strategy. One of 'start', 'end', 'center', 'between', 'around', 'stretch'. See enum h2o_wave.ui.FlexCardWrap. + commands: Contextual menu commands for this component. + Returns: + A `h2o_wave.types.SearchBarCard` instance. + """ + return SearchBarCard( + box, + items, + direction, + justify, + align, + wrap, + commands, + ) + + def frame_card( box: str, title: str, diff --git a/ui/src/cards.tsx b/ui/src/cards.tsx index 3aa055f7b30..dddb6bb48c3 100644 --- a/ui/src/cards.tsx +++ b/ui/src/cards.tsx @@ -18,6 +18,7 @@ import './none' import './pixel_art' import './plot' import './repeat' +import './searchbar' import './small_series_stat' import './small_stat' import './tab' diff --git a/ui/src/searchbar.test.tsx b/ui/src/searchbar.test.tsx new file mode 100644 index 00000000000..09f0fe45707 --- /dev/null +++ b/ui/src/searchbar.test.tsx @@ -0,0 +1,20 @@ +import React from 'react' +import { render } from '@testing-library/react' +import { View } from './form' +import * as T from './qd' + +const + name = 'searchbar', + formProps: T.Card = { + name, + state: {}, + changed: T.box(false) + } + +describe('Searchbar.tsx', () => { + + it('Renders data-test attr', () => { + const { queryByTestId } = render() + expect(queryByTestId(name)).toBeInTheDocument() + }) +}) diff --git a/ui/src/searchbar.tsx b/ui/src/searchbar.tsx new file mode 100644 index 00000000000..11abcf1bd84 --- /dev/null +++ b/ui/src/searchbar.tsx @@ -0,0 +1,123 @@ +import * as Fluent from '@fluentui/react' +import React from 'react' +import {Button, Buttons, XButtons, XStandAloneButton} from './button' +import {Label, XLabel} from './label' +import {cards} from './layout' +import {bond, Card, Dict, Packed, S, unpack, xid} from './qd' +import {Textbox, XTextbox} from './textbox' +import {getTheme} from './theme' +import {XToolTip} from './tooltip' + + +/** Create a SearchBar. */ +export interface SearchBar { + /** Label. */ + label?: Label; + /** Textbox. */ + textbox?: Textbox; + /** Button. */ + button?: Button; + /** Button set. */ + buttons?: Buttons; +} + +/** Create a searchbar. */ +interface State { + /** The components in this searchbar. */ + items: Packed; + + /** SearchBar direction. */ + direction?: 'horizontal' | 'vertical' + + /** SearchBar strategy for main axis. */ + justify?: 'start' | 'end' | 'center' | 'between' | 'around' + + /** SearchBar strategy for cross axis. */ + align?: 'start' | 'end' | 'center' | 'baseline' | 'stretch' + + /** SearchBar wrapping strategy. */ + wrap?: 'start' | 'end' | 'center' | 'between' | 'around' | 'stretch' +} + + +const + theme = getTheme(), + defaults: Partial = {items: []}, + directions: Dict = { + horizontal: 'row', + vertical: 'column', + }, + justifications: Dict = { + start: 'flex-start', + end: 'flex-end', + center: 'center', + between: 'space-between', + around: 'space-around', + }, + alignments: Dict = { + start: 'flex-start', + end: 'flex-end', + center: 'center', + baseline: 'baseline', + stretch: 'stretch', + }, + wrappings: Dict = { + start: 'flex-start', + end: 'flex-end', + center: 'center', + between: 'space-between', + around: 'space-around', + stretch: 'stretch', + }, + toFlexStyle = (state: State): React.CSSProperties => { + const + css: React.CSSProperties = { display: 'flex', flexGrow: 1 }, + direction = directions[state.direction || ''], + justify = justifications[state.justify || ''], + align = alignments[state.align || ''], + wrap = wrappings[state.wrap || ''] + + if (direction) css.flexDirection = direction as any + if (justify) css.justifyContent = justify + if (align) css.alignItems = align + if (wrap) { + css.flexWrap = 'wrap' + css.alignContent = wrap + } + return css + } + + +export const + XSearchBar = ({ items }: { items: SearchBar[] }) => { + const components = items.map(m => ) + return <>{components} + } + +const + XStandardSearchBar = ({ model: m }: { model: SearchBar }) => { + if (m.label) return + if (m.textbox) return + if (m.buttons) return + if (m.button) return + return This component could not be rendered. + } + +export const + View = bond(({ name, state, changed }: Card) => { + + const + render = () => { + const + s = theme.merge(defaults, state), + items = unpack(s.items) + return ( +
+ +
+ ) + } + return { render, changed } + }) + +cards.register('searchbar', View)