Coverage for src / lexigram / ui / molecules / toggle.py: 63%
30 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-10 04:11 +0800
1from __future__ import annotations
3from typing import Any
5from lexigram.ui.atoms.icons import get_icon
6from lexigram.ui.core.base import Component, el
9class Toggle(Component):
10 """Switch-like toggle component for forms.
12 Renders a labeled switch with the same structure used by `Switch` atom but
13 centralizes classes and props for consistency.
14 """
16 def __init__(
17 self,
18 name: str,
19 checked: bool = False,
20 label: str | None = None,
21 size: str = "md",
22 **props,
23 ) -> None:
24 super().__init__(name=name, checked=checked, label=label, size=size, **props)
25 self.name = name
26 self.checked = checked
27 self.label = label
28 self.size = size
29 self.props = props
31 def render(self) -> Any:
32 knob_cls = "inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
33 wrapper_cls = "relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
35 return el(
36 "div",
37 el(
38 "div",
39 el(
40 "div",
41 el(
42 "span",
43 self.label or "",
44 class_="text-sm font-medium text-foreground",
45 id=f"{self.name}-label",
46 ),
47 el(
48 "button",
49 el(
50 "span",
51 aria_hidden="true",
52 class_=knob_cls,
53 x_bind__class="enabled ? 'translate-x-5' : 'translate-x-0'",
54 ),
55 type="button",
56 role="switch",
57 aria_checked=str(self.checked).lower(),
58 aria_labelledby=f"{self.name}-label",
59 x_data=f"{{ enabled: {'true' if self.checked else 'false'} }}",
60 x_on_click="enabled = !enabled; $refs.hiddenInput.checked = enabled",
61 x_bind__class="enabled ? 'bg-primary' : 'bg-input'",
62 class_=wrapper_cls,
63 **self.props,
64 ),
65 class_="flex-grow flex flex-col",
66 ),
67 # Hidden input for form submission
68 el(
69 "input",
70 type="checkbox",
71 name=self.name,
72 x_ref="hiddenInput",
73 checked=self.checked,
74 class_="hidden",
75 ),
76 class_="flex items-center justify-between",
77 ),
78 class_="mb-4",
79 )
82class ToggleIcon(Component):
83 """Icon-based toggle button useful for theme toggles.
85 Args:
86 icon_on: icon name to show when state var is true
87 icon_off: icon name to show when state var is false
88 state_var: the JS state variable in scope to toggle (e.g., 'darkMode')
89 aria_label: accessible label
90 """
92 def __init__(
93 self,
94 icon_on: str = "sun",
95 icon_off: str = "moon",
96 state_var: str = "darkMode",
97 aria_label: str = "Toggle",
98 size: str = "sm",
99 **props,
100 ) -> None:
101 super().__init__(
102 icon_on=icon_on,
103 icon_off=icon_off,
104 state_var=state_var,
105 aria_label=aria_label,
106 size=size,
107 **props,
108 )
109 self.icon_on = icon_on
110 self.icon_off = icon_off
111 self.state_var = state_var
112 self.aria_label = aria_label
113 self.size = size
114 self.props = props
116 def render(self) -> Any:
117 icon_size = "w-5 h-5" if self.size == "sm" else "w-6 h-6"
118 on_icon = get_icon(self.icon_on, size=icon_size)
119 off_icon = get_icon(self.icon_off, size=icon_size)
121 return el(
122 "button",
123 el("span", on_icon, x_show=f"{self.state_var}", class_="text-warning", aria_hidden="true"),
124 el(
125 "span",
126 off_icon,
127 x_show=f"!{self.state_var}",
128 class_="text-muted-foreground",
129 aria_hidden="true",
130 ),
131 class_="p-2 rounded-lg hover:bg-accent transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
132 **{"x-on:click": f"{self.state_var} = !{self.state_var}"},
133 aria_label=self.aria_label,
134 **self.props,
135 )