Skip to content
This repository was archived by the owner on Nov 20, 2025. It is now read-only.

Commit 2925888

Browse files
InputTheme
1 parent b1e5014 commit 2925888

9 files changed

Lines changed: 517 additions & 37 deletions

File tree

sandbox-ui/src/main/scala/example/Model.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ object Model:
3737
TopNav.initial,
3838
Input(UIKey("name-input"))
3939
.withPlaceholder("Type here...")
40+
.withTextColor(RGBA.fromHex("#1f2937"))
41+
.withBackgroundColor(RGBA.fromHex("#f9fafb"))
42+
.solidBorder(BorderWidth.Thin, RGBA.fromHex("#d1d5db"))
43+
.rounded
44+
.withPadding(Spacing.Small)
4045
)
4146

4247
def tmpView(m: Model)(using Theme): HtmlFragment =

tyrian-next/src/main/scala/tyrian/ui/elements/stateful/input/Input.scala

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ import tyrian.next.Outcome
88
import tyrian.ui
99
import tyrian.ui.UIElement
1010
import tyrian.ui.UIKey
11+
import tyrian.ui.datatypes.Border
12+
import tyrian.ui.datatypes.BorderRadius
13+
import tyrian.ui.datatypes.BorderWidth
14+
import tyrian.ui.datatypes.FontSize
15+
import tyrian.ui.datatypes.FontWeight
16+
import tyrian.ui.datatypes.RGBA
17+
import tyrian.ui.datatypes.Spacing
1118
import tyrian.ui.theme.Theme
1219

1320
final case class Input(
@@ -47,7 +54,68 @@ final case class Input(
4754
this.copy(classNames = classes)
4855

4956
def withThemeOverride(f: Theme => Theme): Input =
50-
this.copy(overrideLocalTheme = Some(f))
57+
val h =
58+
overrideLocalTheme match
59+
case Some(g) => f andThen g
60+
case None => f
61+
62+
this.copy(overrideLocalTheme = Some(h))
63+
64+
def overrideInputTheme(f: InputTheme => InputTheme): Input =
65+
val g: Theme => Theme = theme => theme.copy(input = f(theme.input))
66+
withThemeOverride(g)
67+
68+
def withFontSize(size: FontSize): Input =
69+
overrideInputTheme(_.withFontSize(size))
70+
71+
def withFontWeight(weight: FontWeight): Input =
72+
overrideInputTheme(_.withFontWeight(weight))
73+
74+
def withTextColor(color: RGBA): Input =
75+
overrideInputTheme(_.withTextColor(color))
76+
77+
def withBackgroundColor(color: RGBA): Input =
78+
overrideInputTheme(_.withBackgroundColor(color))
79+
80+
def withBorder(border: Border): Input =
81+
overrideInputTheme(_.withBorder(border))
82+
def noBorder: Input =
83+
overrideInputTheme(_.noBorder)
84+
def modifyBorder(f: Border => Border): Input =
85+
overrideInputTheme(_.modifyBorder(f))
86+
def solidBorder(width: BorderWidth, color: RGBA): Input =
87+
overrideInputTheme(_.solidBorder(width, color))
88+
def dashedBorder(width: BorderWidth, color: RGBA): Input =
89+
overrideInputTheme(_.dashedBorder(width, color))
90+
91+
def withBorderColor(color: RGBA): Input =
92+
overrideInputTheme(_.modifyBorder(_.withColor(color)))
93+
94+
def withBorderRadius(radius: BorderRadius): Input =
95+
overrideInputTheme(_.withBorderRadius(radius))
96+
97+
def square: Input =
98+
overrideInputTheme(_.square)
99+
def rounded: Input =
100+
overrideInputTheme(_.rounded)
101+
def roundedSmall: Input =
102+
overrideInputTheme(_.roundedSmall)
103+
def roundedLarge: Input =
104+
overrideInputTheme(_.roundedLarge)
105+
def circular: Input =
106+
overrideInputTheme(_.circular)
107+
108+
def withPadding(padding: Spacing): Input =
109+
overrideInputTheme(_.withPadding(padding))
110+
111+
def withDisabledBackgroundColor(value: RGBA): Input =
112+
overrideInputTheme(_.withDisabledBackgroundColor(value))
113+
114+
def withDisabledTextColor(value: RGBA): Input =
115+
overrideInputTheme(_.withDisabledTextColor(value))
116+
117+
def withDisabledBorderColor(value: RGBA): Input =
118+
overrideInputTheme(_.withDisabledBorderColor(value))
51119

52120
def update: GlobalMsg => Outcome[Input] =
53121
case TextInputMsg.Changed(_key, v) if _key == key =>
@@ -60,16 +128,37 @@ final case class Input(
60128
Outcome(this)
61129

62130
def view: Theme ?=> Elem[GlobalMsg] =
63-
val disabledAttr = if isDisabled then attribute("disabled", "true") else EmptyAttribute
64-
val readonlyAttr = if isReadOnly then attribute("readonly", "true") else EmptyAttribute
131+
val theme = summon[Theme]
132+
133+
val inputTheme = overrideLocalTheme match
134+
case Some(f) => f(theme).input
135+
case None => theme.input
136+
137+
val disabledAttr =
138+
if isDisabled then attribute("disabled", "true")
139+
else EmptyAttribute
140+
141+
val readonlyAttr =
142+
if isReadOnly then attribute("readonly", "true")
143+
else EmptyAttribute
144+
145+
val styles =
146+
if isDisabled then inputTheme.toDisabledStyles(theme)
147+
else inputTheme.toStyles(theme)
148+
149+
val classAttribute =
150+
if classNames.isEmpty then EmptyAttribute
151+
else cls := classNames.mkString(" ")
65152

66153
val inputAttrs = List(
67154
tyrian.Html.placeholder := placeholder,
68155
tyrian.Html.value := value,
69156
typ := "text",
70157
onInput((s: String) => TextInputMsg.Changed(key, s)),
71158
disabledAttr,
72-
readonlyAttr
159+
readonlyAttr,
160+
style(styles),
161+
classAttribute
73162
)
74163

75164
input(inputAttrs*)
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package tyrian.ui.elements.stateful.input
2+
3+
import tyrian.Style
4+
import tyrian.ui.datatypes.Border
5+
import tyrian.ui.datatypes.BorderRadius
6+
import tyrian.ui.datatypes.BorderStyle
7+
import tyrian.ui.datatypes.BorderWidth
8+
import tyrian.ui.datatypes.FontSize
9+
import tyrian.ui.datatypes.FontWeight
10+
import tyrian.ui.datatypes.RGBA
11+
import tyrian.ui.datatypes.Spacing
12+
import tyrian.ui.theme.Theme
13+
14+
final case class InputTheme(
15+
fontSize: FontSize,
16+
fontWeight: FontWeight,
17+
textColor: RGBA,
18+
backgroundColor: RGBA,
19+
border: Option[Border],
20+
padding: Spacing,
21+
disabledBackgroundColor: RGBA,
22+
disabledTextColor: RGBA,
23+
disabledBorderColor: RGBA
24+
):
25+
26+
def withFontSize(value: FontSize): InputTheme =
27+
this.copy(fontSize = value)
28+
29+
def withFontWeight(value: FontWeight): InputTheme =
30+
this.copy(fontWeight = value)
31+
32+
def withTextColor(value: RGBA): InputTheme =
33+
this.copy(textColor = value)
34+
35+
def withBackgroundColor(value: RGBA): InputTheme =
36+
this.copy(backgroundColor = value)
37+
38+
def withBorder(border: Border): InputTheme =
39+
this.copy(border = Some(border))
40+
41+
def noBorder: InputTheme =
42+
this.copy(border = None)
43+
44+
def modifyBorder(f: Border => Border): InputTheme =
45+
withBorder(
46+
border match
47+
case Some(b) => f(b)
48+
case None => f(Border.default)
49+
)
50+
51+
def solidBorder(width: BorderWidth, color: RGBA): InputTheme =
52+
modifyBorder(_.withStyle(BorderStyle.Solid).withWidth(width).withColor(color))
53+
def dashedBorder(width: BorderWidth, color: RGBA): InputTheme =
54+
modifyBorder(_.withStyle(BorderStyle.Dashed).withWidth(width).withColor(color))
55+
56+
def withBorderRadius(radius: BorderRadius): InputTheme =
57+
withBorder(
58+
border match
59+
case Some(b) => b.withRadius(radius)
60+
case None => Border.default.withRadius(radius)
61+
)
62+
def square: InputTheme = withBorderRadius(BorderRadius.None)
63+
def rounded: InputTheme = withBorderRadius(BorderRadius.Medium)
64+
def roundedSmall: InputTheme = withBorderRadius(BorderRadius.Small)
65+
def roundedLarge: InputTheme = withBorderRadius(BorderRadius.Large)
66+
def circular: InputTheme = withBorderRadius(BorderRadius.Full)
67+
68+
def withBorderColor(value: RGBA): InputTheme =
69+
modifyBorder(_.withColor(value))
70+
71+
def withPadding(value: Spacing): InputTheme =
72+
this.copy(padding = value)
73+
74+
def withDisabledBackgroundColor(value: RGBA): InputTheme =
75+
this.copy(disabledBackgroundColor = value)
76+
77+
def withDisabledTextColor(value: RGBA): InputTheme =
78+
this.copy(disabledTextColor = value)
79+
80+
def withDisabledBorderColor(value: RGBA): InputTheme =
81+
this.copy(disabledBorderColor = value)
82+
83+
def toStyles(theme: Theme): Style =
84+
val borderStyle = border.map(_.toStyle).getOrElse(Style.empty)
85+
86+
Style(
87+
"font-family" -> theme.fonts.body,
88+
"font-size" -> fontSize.toCSSValue,
89+
"font-weight" -> fontWeight.toCSSValue,
90+
"color" -> textColor.toCSSValue,
91+
"background-color" -> backgroundColor.toCSSValue,
92+
"padding" -> padding.toCSSValue,
93+
"box-sizing" -> "border-box",
94+
"outline" -> "none"
95+
) |+| borderStyle
96+
97+
def toDisabledStyles(theme: Theme): Style =
98+
toStyles(theme) |+| Style(
99+
"color" -> disabledTextColor.toCSSValue,
100+
"background-color" -> disabledBackgroundColor.toCSSValue,
101+
"border-color" -> disabledBorderColor.toCSSValue,
102+
"cursor" -> "not-allowed"
103+
)
104+
105+
object InputTheme:
106+
107+
val default: InputTheme =
108+
InputTheme(
109+
fontSize = FontSize.Small,
110+
fontWeight = FontWeight.Normal,
111+
textColor = RGBA.fromHex("#374151"),
112+
backgroundColor = RGBA.White,
113+
border = None,
114+
padding = Spacing.px(8),
115+
disabledBackgroundColor = RGBA.fromHex("#f9fafb"),
116+
disabledTextColor = RGBA.fromHex("#9ca3af"),
117+
disabledBorderColor = RGBA.fromHex("#e5e7eb")
118+
)

tyrian-next/src/main/scala/tyrian/ui/elements/stateless/text/TextBlock.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,17 @@ final case class TextBlock(
3333
def toCode: TextBlock = this.copy(variant = TextVariant.Code)
3434
def toLabel: TextBlock = this.copy(variant = TextVariant.Label)
3535

36-
def bold: TextBlock = overrideTextTheme(_.withWeight(FontWeight.Bold))
36+
def bold: TextBlock = overrideTextTheme(_.withFontWeight(FontWeight.Bold))
3737
def italic: TextBlock = overrideTextTheme(_.withStyle(TextStyle.Italic))
3838
def underlined: TextBlock = overrideTextTheme(_.withDecoration(TextDecoration.Underline))
3939
def strikethrough: TextBlock = overrideTextTheme(_.withDecoration(TextDecoration.Strikethrough))
40-
def withColor(color: RGBA): TextBlock = overrideTextTheme(_.withColor(color))
40+
def withColor(color: RGBA): TextBlock = overrideTextTheme(_.withTextColor(color))
4141
def withSize(size: FontSize): TextBlock = overrideTextTheme(_.withFontSize(size))
4242
def withLineHeight(height: LineHeight): TextBlock = overrideTextTheme(_.withLineHeight(height))
4343
def wrap: TextBlock = overrideTextTheme(_.withWrap(true))
4444
def noWrap: TextBlock = overrideTextTheme(_.withWrap(false))
4545

46-
def clearWeight: TextBlock = overrideTextTheme(_.withWeight(FontWeight.Normal))
46+
def clearWeight: TextBlock = overrideTextTheme(_.withFontWeight(FontWeight.Normal))
4747
def clearStyle: TextBlock = overrideTextTheme(_.withStyle(TextStyle.Normal))
4848
def clearDecoration: TextBlock = overrideTextTheme(_.withDecoration(TextDecoration.None))
4949

tyrian-next/src/main/scala/tyrian/ui/elements/stateless/text/TextTheme.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ import tyrian.ui.theme.Theme
1212

1313
final case class TextTheme(
1414
fontSize: FontSize,
15-
weight: FontWeight,
16-
color: RGBA,
15+
fontWeight: FontWeight,
16+
textColor: RGBA,
1717
alignment: TextAlignment,
1818
lineHeight: LineHeight,
1919
wrap: Boolean,
@@ -24,11 +24,11 @@ final case class TextTheme(
2424
def withFontSize(value: FontSize): TextTheme =
2525
this.copy(fontSize = value)
2626

27-
def withWeight(value: FontWeight): TextTheme =
28-
this.copy(weight = value)
27+
def withFontWeight(value: FontWeight): TextTheme =
28+
this.copy(fontWeight = value)
2929

30-
def withColor(value: RGBA): TextTheme =
31-
this.copy(color = value)
30+
def withTextColor(value: RGBA): TextTheme =
31+
this.copy(textColor = value)
3232

3333
def withAlignment(value: TextAlignment): TextTheme =
3434
this.copy(alignment = value)
@@ -53,8 +53,8 @@ final case class TextTheme(
5353
val baseStyle = Style(
5454
"font-family" -> theme.fonts.body,
5555
"font-size" -> fontSize.toCSSValue,
56-
"font-weight" -> weight.toCSSValue,
57-
"color" -> color.toCSSValue,
56+
"font-weight" -> fontWeight.toCSSValue,
57+
"color" -> textColor.toCSSValue,
5858
"text-align" -> alignment.toCSSValue,
5959
"line-height" -> lineHeight.toCSSValue,
6060
"white-space" -> (if wrap then "normal" else "nowrap")

0 commit comments

Comments
 (0)