Headless Components
A headless component gives a component the behavior of a common control,
such as which keys edit text and where the cursor goes, or which keys scroll a box,
and leaves drawing it to the component,
as Headless UI
and React Aria do for the web.
Each one is a hook in counterweight.headless,
and counterweight.widgets draws each one way, for when you don't need your own look:
| Hook | Widget | Behavior |
|---|---|---|
use_text_input |
text_input |
Edits one line of text |
use_text_area |
text_area |
Edits text that wraps onto rows |
use_scroll_area |
scroll_area |
Scrolls a box with the keys and the wheel |
Styling the widgets
The text field widgets take a style, merged over their box's own style, for a size or a border,
and styles, a TextFieldStyles that says how they draw
their box while unfocused, focused, and unfocusable, their cursor, and their placeholder.
Pass the fields you want to change, and share one TextFieldStyles between all the fields of a form:
AMBER = TextFieldStyles(
box=border | border_heavy | border_color("slate", 500) | pad_x(1),
focused_box=border_color("amber", 400),
cursor=(text_color("slate", 900) | text_bg("amber", 300)).text_style,
placeholder=CellStyle(italic=True) | text_color("slate", 400).text_style,
)
@component
def amber_sign_up() -> Div:
name, set_name = use_state("")
email, set_email = use_state("")
return Div(
style=col | width(30),
on_input=tab_focus,
children=(
text_input(name, set_name, placeholder="Name", styles=AMBER, auto_focus=True),
text_input(email, set_email, placeholder="Email", styles=AMBER),
),
)
The box's styles merge as a stylesheet's rules for an <input> apply:
box, then the widget's style, then focused_box while it's focused,
as a rule for input:focus overrides one for input,
or unfocusable_box while it isn't focusable, as a rule for input:disabled does.
So a style with a border color sets the color the box has while it isn't focused,
and the focused box still shows which field has focus.
scroll_area takes a style and styles the same way,
with a ScrollAreaStyles for its box while unfocused and focused.
API
counterweight.widgets.TextFieldStyles
dataclass
TextFieldStyles(
*,
box: Style = border
| border_lightrounded
| border_color("slate", 600)
| pad_x(1),
focused_box: Style = border_color("indigo", 400),
unfocusable_box: Style = text_color("slate", 600),
cursor: CellStyle = text_style,
placeholder: CellStyle = text_style,
)
How text_input and text_area
draw their box, their cursor, and their placeholder.
A widget's box is styled by box, then the style passed to the widget,
then focused_box while it's focused, or unfocusable_box while it isn't focusable,
as a CSS rule for input:focus or input:disabled overrides one for input,
so a style with another border color still shows which widget is focused.
Pass the fields you want to change: TextFieldStyles(focused_box=border_color("amber", 400)).
box
class-attribute
instance-attribute
box: Style = (
border
| border_lightrounded
| border_color("slate", 600)
| pad_x(1)
)
The box around the text.
focused_box
class-attribute
instance-attribute
focused_box: Style = border_color('indigo', 400)
Merged over box and the widget's style while the widget is focused.
unfocusable_box
class-attribute
instance-attribute
unfocusable_box: Style = text_color('slate', 600)
Merged over box and the widget's style while the widget isn't focusable.
cursor
class-attribute
instance-attribute
cursor: CellStyle = (
text_color("slate", 900) | text_bg("slate", 100)
).text_style
Merged over the text for the cell the cursor is on, while the widget is focused.
placeholder
class-attribute
instance-attribute
placeholder: CellStyle = text_color("slate", 500).text_style
Merged over the text for the placeholder, which the widget shows in place of an empty value.