Skip to content

Commit

Permalink
feat: Add “align” parameter for ui.inline #1535 (#1737)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicolapps committed Dec 12, 2022
1 parent be32c42 commit 3773c0f
Show file tree
Hide file tree
Showing 13 changed files with 115 additions and 12 deletions.
24 changes: 24 additions & 0 deletions py/examples/inline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Inline
# Create an inline (horizontal) list of components.
# ---

from h2o_wave import main, app, Q, ui

all_justify = ['start', 'end', 'center', 'between', 'around']
all_align = ['start', 'end', 'center']

justify_choices = [ui.choice(opt, opt) for opt in all_justify]
align_choices = [ui.choice(opt, opt) for opt in all_align]

@app('/demo')
async def serve(q: Q):
justify_current = q.args.justify if q.args.justify else 'start'
align_current = q.args.align if q.args.align else 'center'

q.page['example'] = ui.form_card(box='1 1 -1 3', items=[
ui.inline([
ui.choice_group(name='justify', label='justify', value=justify_current, choices=justify_choices, trigger=True),
ui.choice_group(name='align', label='align', value=align_current, choices=align_choices, trigger=True),
], justify=justify_current, align=align_current)
])
await q.page.save()
1 change: 1 addition & 0 deletions py/examples/tour.conf
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ tags.py
image.py
image_popup.py
image_annotator.py
inline.py
file_stream.py
frame.py
frame_path.py
Expand Down
20 changes: 20 additions & 0 deletions py/h2o_wave/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -5925,33 +5925,49 @@ class InlineJustify:
AROUND = 'around'


_InlineAlign = ['start', 'end', 'center', 'baseline']


class InlineAlign:
START = 'start'
END = 'end'
CENTER = 'center'
BASELINE = 'baseline'


class Inline:
"""Create an inline (horizontal) list of components.
"""
def __init__(
self,
items: List['Component'],
justify: Optional[str] = None,
align: Optional[str] = None,
inset: Optional[bool] = None,
):
_guard_vector('Inline.items', items, (Component,), False, False, False)
_guard_enum('Inline.justify', justify, _InlineJustify, True)
_guard_enum('Inline.align', align, _InlineAlign, True)
_guard_scalar('Inline.inset', inset, (bool,), False, True, False)
self.items = items
"""The components laid out inline."""
self.justify = justify
"""Specifies how to lay out the individual components. Defaults to 'start'. One of 'start', 'end', 'center', 'between', 'around'. See enum h2o_wave.ui.InlineJustify."""
self.align = align
"""Specifies how the individual components are aligned on the vertical axis. Defaults to 'center'. One of 'start', 'end', 'center', 'baseline'. See enum h2o_wave.ui.InlineAlign."""
self.inset = inset
"""Whether to display the components inset from the parent form, with a contrasting background."""

def dump(self) -> Dict:
"""Returns the contents of this object as a dict."""
_guard_vector('Inline.items', self.items, (Component,), False, False, False)
_guard_enum('Inline.justify', self.justify, _InlineJustify, True)
_guard_enum('Inline.align', self.align, _InlineAlign, True)
_guard_scalar('Inline.inset', self.inset, (bool,), False, True, False)
return _dump(
items=[__e.dump() for __e in self.items],
justify=self.justify,
align=self.align,
inset=self.inset,
)

Expand All @@ -5962,14 +5978,18 @@ def load(__d: Dict) -> 'Inline':
_guard_vector('Inline.items', __d_items, (dict,), False, False, False)
__d_justify: Any = __d.get('justify')
_guard_enum('Inline.justify', __d_justify, _InlineJustify, True)
__d_align: Any = __d.get('align')
_guard_enum('Inline.align', __d_align, _InlineAlign, True)
__d_inset: Any = __d.get('inset')
_guard_scalar('Inline.inset', __d_inset, (bool,), False, True, False)
items: List['Component'] = [Component.load(__e) for __e in __d_items]
justify: Optional[str] = __d_justify
align: Optional[str] = __d_align
inset: Optional[bool] = __d_inset
return Inline(
items,
justify,
align,
inset,
)

Expand Down
3 changes: 3 additions & 0 deletions py/h2o_wave/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -2197,20 +2197,23 @@ def stats(
def inline(
items: List[Component],
justify: Optional[str] = None,
align: Optional[str] = None,
inset: Optional[bool] = None,
) -> Component:
"""Create an inline (horizontal) list of components.
Args:
items: The components laid out inline.
justify: Specifies how to lay out the individual components. Defaults to 'start'. One of 'start', 'end', 'center', 'between', 'around'. See enum h2o_wave.ui.InlineJustify.
align: Specifies how the individual components are aligned on the vertical axis. Defaults to 'center'. One of 'start', 'end', 'center', 'baseline'. See enum h2o_wave.ui.InlineAlign.
inset: Whether to display the components inset from the parent form, with a contrasting background.
Returns:
A `h2o_wave.types.Inline` instance.
"""
return Component(inline=Inline(
items,
justify,
align,
inset,
))

Expand Down
5 changes: 5 additions & 0 deletions r/R/ui.R
Original file line number Diff line number Diff line change
Expand Up @@ -2577,19 +2577,24 @@ ui_stats <- function(
#' @param items The components laid out inline.
#' @param justify Specifies how to lay out the individual components. Defaults to 'start'.
#' One of 'start', 'end', 'center', 'between', 'around'. See enum h2o_wave.ui.InlineJustify.
#' @param align Specifies how the individual components are aligned on the vertical axis. Defaults to 'center'.
#' One of 'start', 'end', 'center', 'baseline'. See enum h2o_wave.ui.InlineAlign.
#' @param inset Whether to display the components inset from the parent form, with a contrasting background.
#' @return A Inline instance.
#' @export
ui_inline <- function(
items,
justify = NULL,
align = NULL,
inset = NULL) {
.guard_vector("items", "WaveComponent", items)
# TODO Validate justify
# TODO Validate align
.guard_scalar("inset", "logical", inset)
.o <- list(inline=list(
items=items,
justify=justify,
align=align,
inset=inset))
class(.o) <- append(class(.o), c(.wave_obj, "WaveComponent"))
return(.o)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1304,8 +1304,9 @@
<option name="Python" value="true"/>
</context>
</template>
<template name="w_full_inline" value="ui.inline(justify='$justify$',inset=$inset$,items=[&#10; $items$ &#10;]),$END$" description="Create Wave Inline with full attributes." toReformat="true" toShortenFQNames="true">
<template name="w_full_inline" value="ui.inline(justify='$justify$',align='$align$',inset=$inset$,items=[&#10; $items$ &#10;]),$END$" description="Create Wave Inline with full attributes." toReformat="true" toShortenFQNames="true">
<variable name="justify" expression="" defaultValue="&quot;start&quot;" alwaysStopAt="true"/>
<variable name="align" expression="" defaultValue="&quot;center&quot;" alwaysStopAt="true"/>
<variable name="inset" expression="" defaultValue="&quot;False&quot;" alwaysStopAt="true"/>
<variable name="items" expression="" defaultValue="" alwaysStopAt="true"/>
<context>
Expand Down
2 changes: 1 addition & 1 deletion tools/vscode-extension/component-snippets.json
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,7 @@
"Wave Full Inline": {
"prefix": "w_full_inline",
"body": [
"ui.inline(justify='${1:start}', inset=${2:False}, items=[\n\t\t$3\t\t\n]),$0"
"ui.inline(justify='${1:start}', align='${2:center}', inset=${3:False}, items=[\n\t\t$4\t\t\n]),$0"
],
"description": "Create a full Wave Inline."
},
Expand Down
22 changes: 16 additions & 6 deletions ui/src/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import { Text, TextL, TextM, TextS, TextXl, TextXs, XText } from './text'
import { Textbox, XTextbox } from './textbox'
import { TextAnnotator, XTextAnnotator } from './text_annotator'
import { ImageAnnotator, XImageAnnotator } from './image_annotator'
import { clas, cssVar, justifications, padding } from './theme'
import { clas, cssVar, justifications, alignments, padding } from './theme'
import { Toggle, XToggle } from './toggle'
import { XToolTip } from './tooltip'
import { bond } from './ui'
Expand Down Expand Up @@ -171,6 +171,8 @@ interface Inline {
items: Component[]
/** Specifies how to lay out the individual components. Defaults to 'start'. */
justify?: 'start' | 'end' | 'center' | 'between' | 'around'
/** Specifies how the individual components are aligned on the vertical axis. Defaults to 'center'. */
align?: 'start' | 'end' | 'center' | 'baseline'
/** Whether to display the components inset from the parent form, with a contrasting background. */
inset?: B
}
Expand Down Expand Up @@ -214,18 +216,19 @@ const
},
})

type XComponentAlignment = 'start' | 'end' | 'center' | 'between' | 'around'
type Justification = 'start' | 'end' | 'center' | 'between' | 'around'
type Alignment = 'start' | 'end' | 'center' | 'baseline'

export const
XComponents = ({ items, alignment, inset }: { items: Component[], alignment?: XComponentAlignment, inset?: B }) => {
XComponents = ({ items, justify, align, inset }: { items: Component[], justify?: Justification, align?: Alignment, inset?: B }) => {
const
components = items.map((m: any, i) => {
const
// All form items are wrapped by their component name (first and only prop of "m").
[componentKey] = Object.keys(m),
{ name, visible = true, width = 'auto' } = m[componentKey],
visibleStyles: React.CSSProperties = visible ? {} : { display: 'none' },
// TODO: Ugly, maybe introduce 'align' prop to ui.inline?
// TODO: Ugly, maybe use ui.inline's 'align' prop instead?
alignSelf = componentKey === 'links' ? 'flex-start' : undefined

return (
Expand All @@ -235,12 +238,19 @@ export const
</div>
)
})
return <div className={clas(alignment ? css.horizontal : css.vertical, inset ? css.inset : '')} style={{ justifyContent: justifications[alignment || ''] }}>{components}</div>
return <div
className={clas(justify ? css.horizontal : css.vertical, inset ? css.inset : '')}
style={{
justifyContent: justifications[justify || ''],
alignItems: alignments[align || ''],
}}
>{components}</div>
},
XInline = ({ model: m }: { model: Inline }) => (
<XComponents
items={m.items}
alignment={m.justify || 'start'}
justify={m.justify || 'start'}
align={m.align || 'center'}
inset={m.inset}
/>
)
Expand Down
2 changes: 1 addition & 1 deletion ui/src/message_bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export const
messageBarType={toMessageBarType(type)}
className={css.messageBar}
isMultiline={false}
actions={btns?.length ? <XComponents items={btns || []} alignment='end' /> : undefined}
actions={btns?.length ? <XComponents items={btns || []} justify='end' /> : undefined}
messageBarIconProps={{ iconName }}
>
<Markdown source={text} />
Expand Down
2 changes: 1 addition & 1 deletion ui/src/notification_bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export const
<Fluent.MessageBar
messageBarType={toMessageBarType(currentModel?.type)}
messageBarIconProps={{ iconName }}
actions={buttons?.length ? <XComponents items={buttons || []} alignment='end' /> : undefined}
actions={buttons?.length ? <XComponents items={buttons || []} justify='end' /> : undefined}
isMultiline={isMultiline}
onDismiss={onDismiss}
className={css.messageBar}
Expand Down
2 changes: 1 addition & 1 deletion ui/src/section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const
components = unpack<Component[]>(items), // XXX ugly
form = items && (
<div className={css.rhs}>
<XComponents items={components} alignment='end' />
<XComponents items={components} justify='end' />
</div>
)

Expand Down
Binary file added website/docs/examples/assets/inline.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 40 additions & 1 deletion website/widgets/form/inline.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ custom_edit_url: null

By default, all form items are laid out vertically (top to bottom). For more complex forms though, this might not be enough and
that's where [ui.inline](/docs/api/ui#inline) comes into play. All you need to do is specify the `items` attribute, which accepts any form
component. Optionally, you can also specify `justify` if you want to control the alignment too.
component.

```py
q.page['form'] = ui.form_card(
Expand All @@ -22,3 +22,42 @@ q.page['form'] = ui.form_card(
```

Check the full API at [ui.inline](/docs/api/ui#inline).

## Horizontal alignment (`justify`)

You can specify how the elements should be horizontally aligned using the `justify` parameter. The default value is `start`.

```py
items = [
ui.button(name='primary_button', label='Primary', primary=True),
ui.button(name='basic_caption_button', label='Secondary', caption='Caption'),
ui.button(name='icon_button', icon='Heart', caption='Like'),
]

q.page['justify'] = ui.form_card(box='1 1 3 5', items=[
ui.inline(items, justify='start'),
ui.inline(items, justify='center'),
ui.inline(items, justify='end'),
ui.inline(items, justify='between'),
ui.inline(items, justify='around'),
])
```

## Vertical alignment (`align`)

You can specify how the elements should be horizontally aligned using the `align` parameter. The default value is `center`.

```py
items = [
ui.button(name='primary_button', label='Primary', primary=True),
ui.button(name='basic_caption_button', label='Secondary', caption='Caption'),
ui.button(name='icon_button', icon='Heart', caption='Like'),
]

q.page['align'] = ui.form_card(box='1 1 3 5', items=[
ui.inline(items, align='start'),
ui.inline(items, align='baseline'),
ui.inline(items, align='center'),
ui.inline(items, align='end'),
])
```

0 comments on commit 3773c0f

Please sign in to comment.