Skip to content

Commit 675b5d5

Browse files
committed
Experiments with controlled/uncontrolled inputs
1 parent 452aeeb commit 675b5d5

File tree

5 files changed

+229
-23
lines changed

5 files changed

+229
-23
lines changed

src/jsMain/kotlin/Application.kt

+14-17
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import app.softwork.routingcompose.BrowserRouter
1+
import androidx.compose.runtime.mutableStateOf
22
import app.softwork.routingcompose.HashRouter
33
import common.BaseStyles
44
import common.Theme
5+
import common.Theme.Light.applyStyle
56
import common.ThemeProvider
6-
import common.ThemeVariables
77
import components.Layout
88
import components.PageContent
99
import components.PageFooter
@@ -16,7 +16,6 @@ import io.ktor.client.plugins.contentnegotiation.*
1616
import io.ktor.client.plugins.logging.*
1717
import io.ktor.serialization.kotlinx.json.*
1818
import kotlinx.serialization.json.Json
19-
import org.jetbrains.compose.web.css.Color
2019
import org.jetbrains.compose.web.css.Style
2120
import org.jetbrains.compose.web.dom.Div
2221
import org.jetbrains.compose.web.dom.H1
@@ -39,30 +38,28 @@ fun main() {
3938
renderComposableInBody {
4039
Style {
4140
root {
42-
when (ThemeProvider.theme) {
43-
Theme.Light -> {
44-
ThemeVariables.mainColor(Color("#201e1f"))
45-
ThemeVariables.accentColor(Color("#1d7874"))
46-
ThemeVariables.backgroundColor(Color("#F8F4E3"))
47-
}
48-
49-
Theme.Dark -> {
50-
ThemeVariables.mainColor(Color("#F8F4E3"))
51-
ThemeVariables.accentColor(Color("#1d7874"))
52-
ThemeVariables.backgroundColor(Color("#201e1f"))
53-
}
54-
}
41+
ThemeProvider.theme.applyStyle()
5542
}
5643
}
5744

5845
Style(BaseStyles)
5946

6047
Layout {
6148
PageHeader {
62-
H1 { Text("My delicious site") }
49+
H1 { Text("LIMEBECK.DEV") }
6350
Div {
51+
val themeState = mutableStateOf(false)
52+
components.Switch("Theme", invertedColors = true, state = themeState) { checked ->
53+
ThemeProvider.theme =
54+
if (checked) {
55+
Theme.Dark
56+
} else {
57+
Theme.Light
58+
}
59+
}
6460
components.Button("Switch theme") {
6561
ThemeProvider.theme = ThemeProvider.theme.opposite
62+
themeState.value = !themeState.value
6663
}
6764
}
6865
}

src/jsMain/kotlin/common/Theme.kt

+28-5
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,34 @@ package common
33
import androidx.compose.runtime.getValue
44
import androidx.compose.runtime.mutableStateOf
55
import androidx.compose.runtime.setValue
6+
import org.jetbrains.compose.web.css.CSSColorValue
7+
import org.jetbrains.compose.web.css.Color
8+
import org.jetbrains.compose.web.css.StyleScope
9+
import org.jetbrains.compose.web.css.StyleSheetBuilder
610

7-
enum class Theme {
8-
Dark,
9-
Light,
10-
;
11+
sealed class Theme(
12+
val mainColor: CSSColorValue,
13+
val accentColor: CSSColorValue,
14+
val backgroundColor: CSSColorValue,
15+
) {
16+
data object Dark : Theme(
17+
mainColor = Color("#201e1f"),
18+
accentColor = Color("#1d7874"),
19+
backgroundColor = Color("#F8F4E3"),
20+
)
21+
22+
data object Light : Theme(
23+
mainColor = Color("#F8F4E3"),
24+
accentColor = Color("#1d7874"),
25+
backgroundColor = Color("#201e1f"),
26+
)
27+
28+
context(StyleScope)
29+
fun applyStyle() {
30+
ThemeVariables.mainColor(mainColor)
31+
ThemeVariables.accentColor(accentColor)
32+
ThemeVariables.backgroundColor(backgroundColor)
33+
}
1134

1235
val opposite
1336
get() =
@@ -18,5 +41,5 @@ enum class Theme {
1841
}
1942

2043
object ThemeProvider {
21-
var theme by mutableStateOf(Theme.Light)
44+
var theme by mutableStateOf<Theme>(Theme.Light)
2245
}

src/jsMain/kotlin/components/Button.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import org.jetbrains.compose.web.dom.ElementScope
88
import org.jetbrains.compose.web.dom.Text
99
import org.w3c.dom.HTMLElement
1010

11-
object ButtonStyles : StyleSheet() {
11+
internal object ButtonStyles : StyleSheet() {
1212
@OptIn(ExperimentalComposeWebApi::class)
1313
val button by style {
1414
display(DisplayStyle.InlineBlock)
+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package components
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.MutableState
5+
import androidx.compose.runtime.mutableStateOf
6+
import androidx.compose.runtime.remember
7+
import common.ThemeVariables
8+
import kotlinx.uuid.UUID
9+
import kotlinx.uuid.generateUUID
10+
import org.jetbrains.compose.web.ExperimentalComposeWebApi
11+
import org.jetbrains.compose.web.attributes.InputType
12+
import org.jetbrains.compose.web.attributes.disabled
13+
import org.jetbrains.compose.web.attributes.forId
14+
import org.jetbrains.compose.web.css.*
15+
import org.jetbrains.compose.web.dom.Input
16+
import org.jetbrains.compose.web.dom.Label
17+
import org.jetbrains.compose.web.dom.Span
18+
import org.jetbrains.compose.web.dom.Text
19+
20+
@OptIn(ExperimentalComposeWebApi::class)
21+
internal class SwitchStyles(
22+
invertedColors: Boolean,
23+
) : StyleSheet() {
24+
val switch by style {
25+
position(Position.Relative)
26+
display(DisplayStyle.InlineBlock)
27+
width(60.px)
28+
height(34.px)
29+
margin(10.px)
30+
}
31+
32+
val switchInput by style {
33+
opacity(0)
34+
width(0.px)
35+
height(0.px)
36+
}
37+
38+
val switchSlider by style {
39+
position(Position.Absolute)
40+
cursor("pointer")
41+
top(0.px)
42+
left(0.px)
43+
right(0.px)
44+
bottom(0.px)
45+
backgroundColor(Color.transparent)
46+
border(
47+
2.px,
48+
LineStyle.Solid,
49+
if (invertedColors) {
50+
ThemeVariables.backgroundColor.value()
51+
} else {
52+
ThemeVariables.mainColor.value()
53+
},
54+
)
55+
transitions {
56+
defaultDuration(0.4.s)
57+
}
58+
borderRadius(34.px)
59+
60+
self + "::before" style {
61+
position(Position.Absolute)
62+
property("content", "\"\"")
63+
height(20.px)
64+
width(20.px)
65+
left(4.px)
66+
top(50.percent) // Центрируем по вертикали
67+
transform { translateY((-50).percent) } // Корректируем смещение
68+
backgroundColor(
69+
if (invertedColors) {
70+
ThemeVariables.backgroundColor.value()
71+
} else {
72+
ThemeVariables.mainColor.value()
73+
},
74+
)
75+
border(
76+
2.px,
77+
LineStyle.Solid,
78+
if (invertedColors) {
79+
ThemeVariables.backgroundColor.value()
80+
} else {
81+
ThemeVariables.mainColor.value()
82+
},
83+
)
84+
transitions {
85+
defaultDuration(0.4.s)
86+
}
87+
borderRadius(50.percent)
88+
}
89+
}
90+
91+
val switchLabel by style {
92+
display(DisplayStyle.InlineBlock)
93+
marginLeft(10.px)
94+
attr("vertical-align", "middle")
95+
fontSize(16.px)
96+
}
97+
98+
init {
99+
(".$switchInput:checked + .$switchSlider") style {
100+
property(
101+
"border-color",
102+
if (invertedColors) {
103+
ThemeVariables.backgroundColor.value()
104+
} else {
105+
ThemeVariables.mainColor.value()
106+
},
107+
)
108+
}
109+
110+
(".$switchInput:checked + .$switchSlider::before") style {
111+
transform {
112+
translateX(24.px)
113+
translateY((-50).percent)
114+
}
115+
property(
116+
"border-color",
117+
if (invertedColors) {
118+
ThemeVariables.backgroundColor.value()
119+
} else {
120+
ThemeVariables.mainColor.value()
121+
},
122+
)
123+
}
124+
125+
(".$switchInput:disabled + .$switchSlider") style {
126+
property("borderColor", (rgb(224, 224, 224)))
127+
cursor("not-allowed")
128+
}
129+
130+
(".$switchInput:disabled + .$switchSlider::before") style {
131+
backgroundColor(rgb(189, 189, 189))
132+
property("borderColor", (rgb(189, 189, 189)))
133+
}
134+
}
135+
}
136+
137+
@Composable
138+
fun Switch(
139+
label: String,
140+
disabled: Boolean = false,
141+
invertedColors: Boolean = false,
142+
state: MutableState<Boolean> = mutableStateOf(false),
143+
onSwitchChanged: (newValue: Boolean) -> Unit = {},
144+
) {
145+
val styles = remember { SwitchStyles(invertedColors = invertedColors) }
146+
Style(styles)
147+
148+
val inputId = UUID.generateUUID().toString()
149+
Label(
150+
attrs = {
151+
classes(styles.switch)
152+
},
153+
) {
154+
Input(type = InputType.Checkbox, attrs = {
155+
id(inputId)
156+
if (disabled) {
157+
disabled()
158+
}
159+
classes(styles.switchInput)
160+
checked(state.value)
161+
onChange {
162+
state.value = it.value
163+
onSwitchChanged(state.value)
164+
}
165+
})
166+
Span(
167+
attrs = {
168+
classes(styles.switchSlider)
169+
},
170+
)
171+
}
172+
173+
Label(
174+
attrs = {
175+
forId(inputId)
176+
classes(styles.switchLabel)
177+
},
178+
) {
179+
Text(label)
180+
}
181+
}

src/jsMain/kotlin/pages/HomePage.kt

+5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package pages
22

33
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.mutableStateOf
45
import app.softwork.routingcompose.NavLink
6+
import components.Switch
57
import org.jetbrains.compose.web.dom.Text
68

79
@Composable
810
fun HomePage() {
11+
val state = mutableStateOf(false)
12+
Switch("123", state = state)
13+
Switch("Disabled", disabled = true, state = state)
914
NavLink("/hello-world") {
1015
Text("To Hello world!")
1116
}

0 commit comments

Comments
 (0)