diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala index 0caff74..5f17be5 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala @@ -1,5 +1,6 @@ package works.iterative -package ui.components.tailwind +package ui.components +package tailwind import com.raquo.laminar.api.L.{*, given} @@ -7,4 +8,4 @@ def card(content: Modifier[HtmlElement]*)(using cctx: ComponentContext[_] ): Div = - div(cls(cctx.style.card), content) + div(cls("bg-white shadow sm:rounded-md overflow-hidden"), content) diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala index 0caff74..5f17be5 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala @@ -1,5 +1,6 @@ package works.iterative -package ui.components.tailwind +package ui.components +package tailwind import com.raquo.laminar.api.L.{*, given} @@ -7,4 +8,4 @@ def card(content: Modifier[HtmlElement]*)(using cctx: ComponentContext[_] ): Div = - div(cls(cctx.style.card), content) + div(cls("bg-white shadow sm:rounded-md overflow-hidden"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala deleted file mode 100644 index f210a37..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala +++ /dev/null @@ -1,50 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonStyles: - def basic: String - def primary: String - def secondary: String - def positive: String - def negative: String - -trait StyleGuide: - def button: ButtonStyles - def label: String - def cardContent: String - def card: String - def input: String - -object StyleGuide: - object default extends StyleGuide: - override val label: String = - "text-sm font-medium text-gray-500" - override val cardContent: String = "px-4 py-5 sm:p-6" - override val card: String = - "bg-white shadow sm:rounded-md overflow-hidden" - override val input: String = - "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" - - override object button extends ButtonStyles: - private def common(extra: String) = - s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" - override val basic: String = - common( - "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" - ) - override val primary: String = - common( - "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" - ) - override val secondary: String = - common( - "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" - ) - override val positive: String = - common( - "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" - ) - override val negative: String = - common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala index 0caff74..5f17be5 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala @@ -1,5 +1,6 @@ package works.iterative -package ui.components.tailwind +package ui.components +package tailwind import com.raquo.laminar.api.L.{*, given} @@ -7,4 +8,4 @@ def card(content: Modifier[HtmlElement]*)(using cctx: ComponentContext[_] ): Div = - div(cls(cctx.style.card), content) + div(cls("bg-white shadow sm:rounded-md overflow-hidden"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala deleted file mode 100644 index f210a37..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala +++ /dev/null @@ -1,50 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonStyles: - def basic: String - def primary: String - def secondary: String - def positive: String - def negative: String - -trait StyleGuide: - def button: ButtonStyles - def label: String - def cardContent: String - def card: String - def input: String - -object StyleGuide: - object default extends StyleGuide: - override val label: String = - "text-sm font-medium text-gray-500" - override val cardContent: String = "px-4 py-5 sm:p-6" - override val card: String = - "bg-white shadow sm:rounded-md overflow-hidden" - override val input: String = - "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" - - override object button extends ButtonStyles: - private def common(extra: String) = - s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" - override val basic: String = - common( - "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" - ) - override val primary: String = - common( - "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" - ) - override val secondary: String = - common( - "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" - ) - override val positive: String = - common( - "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" - ) - override val negative: String = - common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala deleted file mode 100644 index d5a78ee..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala +++ /dev/null @@ -1,46 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle -import java.time.ZoneId -import java.time.Instant -import java.time.temporal.TemporalAccessor - -object TimeUtils: - val dateTimeFormat = - DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val dateFormat = - DateTimeFormatter - .ofLocalizedDate(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val htmlDateFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd") - .withZone(ZoneId.systemDefault()) - - val htmlDateTimeFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss") - .withZone(ZoneId.systemDefault()) - - def formatDateTime(i: TemporalAccessor): String = - dateTimeFormat.format(i) - - def formatDate(i: TemporalAccessor): String = - dateFormat.format(i) - - def formatHtmlDate(i: TemporalAccessor): String = - htmlDateFormat.format(i) - - def formatHtmlDateTime(i: TemporalAccessor): String = - htmlDateTimeFormat.format(i) diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala index 0caff74..5f17be5 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala @@ -1,5 +1,6 @@ package works.iterative -package ui.components.tailwind +package ui.components +package tailwind import com.raquo.laminar.api.L.{*, given} @@ -7,4 +8,4 @@ def card(content: Modifier[HtmlElement]*)(using cctx: ComponentContext[_] ): Div = - div(cls(cctx.style.card), content) + div(cls("bg-white shadow sm:rounded-md overflow-hidden"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala deleted file mode 100644 index f210a37..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala +++ /dev/null @@ -1,50 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonStyles: - def basic: String - def primary: String - def secondary: String - def positive: String - def negative: String - -trait StyleGuide: - def button: ButtonStyles - def label: String - def cardContent: String - def card: String - def input: String - -object StyleGuide: - object default extends StyleGuide: - override val label: String = - "text-sm font-medium text-gray-500" - override val cardContent: String = "px-4 py-5 sm:p-6" - override val card: String = - "bg-white shadow sm:rounded-md overflow-hidden" - override val input: String = - "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" - - override object button extends ButtonStyles: - private def common(extra: String) = - s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" - override val basic: String = - common( - "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" - ) - override val primary: String = - common( - "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" - ) - override val secondary: String = - common( - "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" - ) - override val positive: String = - common( - "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" - ) - override val negative: String = - common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala deleted file mode 100644 index d5a78ee..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala +++ /dev/null @@ -1,46 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle -import java.time.ZoneId -import java.time.Instant -import java.time.temporal.TemporalAccessor - -object TimeUtils: - val dateTimeFormat = - DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val dateFormat = - DateTimeFormatter - .ofLocalizedDate(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val htmlDateFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd") - .withZone(ZoneId.systemDefault()) - - val htmlDateTimeFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss") - .withZone(ZoneId.systemDefault()) - - def formatDateTime(i: TemporalAccessor): String = - dateTimeFormat.format(i) - - def formatDate(i: TemporalAccessor): String = - dateFormat.format(i) - - def formatHtmlDate(i: TemporalAccessor): String = - htmlDateFormat.format(i) - - def formatHtmlDateTime(i: TemporalAccessor): String = - htmlDateTimeFormat.format(i) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala index 8caee39..b5af03c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala @@ -2,14 +2,14 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.UIString -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.LocalDate import works.iterative.ui.components.tailwind.BaseHtmlComponent -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable import works.iterative.ui.components.tailwind.form.ActionButtons import works.iterative.ui.components.tailwind.HtmlComponent import works.iterative.ui.components.tailwind.form.ActionButton -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.ui.components.tailwind.Icons import scala.reflect.ClassTag diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala index 0caff74..5f17be5 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala @@ -1,5 +1,6 @@ package works.iterative -package ui.components.tailwind +package ui.components +package tailwind import com.raquo.laminar.api.L.{*, given} @@ -7,4 +8,4 @@ def card(content: Modifier[HtmlElement]*)(using cctx: ComponentContext[_] ): Div = - div(cls(cctx.style.card), content) + div(cls("bg-white shadow sm:rounded-md overflow-hidden"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala deleted file mode 100644 index f210a37..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala +++ /dev/null @@ -1,50 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonStyles: - def basic: String - def primary: String - def secondary: String - def positive: String - def negative: String - -trait StyleGuide: - def button: ButtonStyles - def label: String - def cardContent: String - def card: String - def input: String - -object StyleGuide: - object default extends StyleGuide: - override val label: String = - "text-sm font-medium text-gray-500" - override val cardContent: String = "px-4 py-5 sm:p-6" - override val card: String = - "bg-white shadow sm:rounded-md overflow-hidden" - override val input: String = - "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" - - override object button extends ButtonStyles: - private def common(extra: String) = - s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" - override val basic: String = - common( - "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" - ) - override val primary: String = - common( - "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" - ) - override val secondary: String = - common( - "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" - ) - override val positive: String = - common( - "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" - ) - override val negative: String = - common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala deleted file mode 100644 index d5a78ee..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala +++ /dev/null @@ -1,46 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle -import java.time.ZoneId -import java.time.Instant -import java.time.temporal.TemporalAccessor - -object TimeUtils: - val dateTimeFormat = - DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val dateFormat = - DateTimeFormatter - .ofLocalizedDate(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val htmlDateFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd") - .withZone(ZoneId.systemDefault()) - - val htmlDateTimeFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss") - .withZone(ZoneId.systemDefault()) - - def formatDateTime(i: TemporalAccessor): String = - dateTimeFormat.format(i) - - def formatDate(i: TemporalAccessor): String = - dateFormat.format(i) - - def formatHtmlDate(i: TemporalAccessor): String = - htmlDateFormat.format(i) - - def formatHtmlDateTime(i: TemporalAccessor): String = - htmlDateTimeFormat.format(i) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala index 8caee39..b5af03c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala @@ -2,14 +2,14 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.UIString -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.LocalDate import works.iterative.ui.components.tailwind.BaseHtmlComponent -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable import works.iterative.ui.components.tailwind.form.ActionButtons import works.iterative.ui.components.tailwind.HtmlComponent import works.iterative.ui.components.tailwind.form.ActionButton -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.ui.components.tailwind.Icons import scala.reflect.ClassTag diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala index 28d1e6e..f5ac8cb 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala @@ -4,7 +4,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.core.MessageId import works.iterative.ui.components.tailwind.HtmlComponent -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext case class ActionButtonStyle( border: String, diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala index 0caff74..5f17be5 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala @@ -1,5 +1,6 @@ package works.iterative -package ui.components.tailwind +package ui.components +package tailwind import com.raquo.laminar.api.L.{*, given} @@ -7,4 +8,4 @@ def card(content: Modifier[HtmlElement]*)(using cctx: ComponentContext[_] ): Div = - div(cls(cctx.style.card), content) + div(cls("bg-white shadow sm:rounded-md overflow-hidden"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala deleted file mode 100644 index f210a37..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala +++ /dev/null @@ -1,50 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonStyles: - def basic: String - def primary: String - def secondary: String - def positive: String - def negative: String - -trait StyleGuide: - def button: ButtonStyles - def label: String - def cardContent: String - def card: String - def input: String - -object StyleGuide: - object default extends StyleGuide: - override val label: String = - "text-sm font-medium text-gray-500" - override val cardContent: String = "px-4 py-5 sm:p-6" - override val card: String = - "bg-white shadow sm:rounded-md overflow-hidden" - override val input: String = - "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" - - override object button extends ButtonStyles: - private def common(extra: String) = - s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" - override val basic: String = - common( - "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" - ) - override val primary: String = - common( - "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" - ) - override val secondary: String = - common( - "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" - ) - override val positive: String = - common( - "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" - ) - override val negative: String = - common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala deleted file mode 100644 index d5a78ee..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala +++ /dev/null @@ -1,46 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle -import java.time.ZoneId -import java.time.Instant -import java.time.temporal.TemporalAccessor - -object TimeUtils: - val dateTimeFormat = - DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val dateFormat = - DateTimeFormatter - .ofLocalizedDate(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val htmlDateFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd") - .withZone(ZoneId.systemDefault()) - - val htmlDateTimeFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss") - .withZone(ZoneId.systemDefault()) - - def formatDateTime(i: TemporalAccessor): String = - dateTimeFormat.format(i) - - def formatDate(i: TemporalAccessor): String = - dateFormat.format(i) - - def formatHtmlDate(i: TemporalAccessor): String = - htmlDateFormat.format(i) - - def formatHtmlDateTime(i: TemporalAccessor): String = - htmlDateTimeFormat.format(i) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala index 8caee39..b5af03c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala @@ -2,14 +2,14 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.UIString -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.LocalDate import works.iterative.ui.components.tailwind.BaseHtmlComponent -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable import works.iterative.ui.components.tailwind.form.ActionButtons import works.iterative.ui.components.tailwind.HtmlComponent import works.iterative.ui.components.tailwind.form.ActionButton -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.ui.components.tailwind.Icons import scala.reflect.ClassTag diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala index 28d1e6e..f5ac8cb 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala @@ -4,7 +4,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.core.MessageId import works.iterative.ui.components.tailwind.HtmlComponent -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext case class ActionButtonStyle( border: String, diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala index 8c5b8a1..d9ee9aa 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind package form import com.raquo.laminar.api.L.{*, given} @@ -35,7 +36,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", @@ -79,7 +80,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala index 0caff74..5f17be5 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala @@ -1,5 +1,6 @@ package works.iterative -package ui.components.tailwind +package ui.components +package tailwind import com.raquo.laminar.api.L.{*, given} @@ -7,4 +8,4 @@ def card(content: Modifier[HtmlElement]*)(using cctx: ComponentContext[_] ): Div = - div(cls(cctx.style.card), content) + div(cls("bg-white shadow sm:rounded-md overflow-hidden"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala deleted file mode 100644 index f210a37..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala +++ /dev/null @@ -1,50 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonStyles: - def basic: String - def primary: String - def secondary: String - def positive: String - def negative: String - -trait StyleGuide: - def button: ButtonStyles - def label: String - def cardContent: String - def card: String - def input: String - -object StyleGuide: - object default extends StyleGuide: - override val label: String = - "text-sm font-medium text-gray-500" - override val cardContent: String = "px-4 py-5 sm:p-6" - override val card: String = - "bg-white shadow sm:rounded-md overflow-hidden" - override val input: String = - "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" - - override object button extends ButtonStyles: - private def common(extra: String) = - s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" - override val basic: String = - common( - "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" - ) - override val primary: String = - common( - "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" - ) - override val secondary: String = - common( - "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" - ) - override val positive: String = - common( - "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" - ) - override val negative: String = - common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala deleted file mode 100644 index d5a78ee..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala +++ /dev/null @@ -1,46 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle -import java.time.ZoneId -import java.time.Instant -import java.time.temporal.TemporalAccessor - -object TimeUtils: - val dateTimeFormat = - DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val dateFormat = - DateTimeFormatter - .ofLocalizedDate(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val htmlDateFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd") - .withZone(ZoneId.systemDefault()) - - val htmlDateTimeFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss") - .withZone(ZoneId.systemDefault()) - - def formatDateTime(i: TemporalAccessor): String = - dateTimeFormat.format(i) - - def formatDate(i: TemporalAccessor): String = - dateFormat.format(i) - - def formatHtmlDate(i: TemporalAccessor): String = - htmlDateFormat.format(i) - - def formatHtmlDateTime(i: TemporalAccessor): String = - htmlDateTimeFormat.format(i) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala index 8caee39..b5af03c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala @@ -2,14 +2,14 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.UIString -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.LocalDate import works.iterative.ui.components.tailwind.BaseHtmlComponent -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable import works.iterative.ui.components.tailwind.form.ActionButtons import works.iterative.ui.components.tailwind.HtmlComponent import works.iterative.ui.components.tailwind.form.ActionButton -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.ui.components.tailwind.Icons import scala.reflect.ClassTag diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala index 28d1e6e..f5ac8cb 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala @@ -4,7 +4,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.core.MessageId import works.iterative.ui.components.tailwind.HtmlComponent -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext case class ActionButtonStyle( border: String, diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala index 8c5b8a1..d9ee9aa 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind package form import com.raquo.laminar.api.L.{*, given} @@ -35,7 +36,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", @@ -79,7 +80,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala index 023f0b0..587a35a 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala @@ -5,7 +5,7 @@ import com.raquo.laminar.api.L.{*, given} import java.time.LocalDate -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormInput[V]: def render( diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala index 0caff74..5f17be5 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala @@ -1,5 +1,6 @@ package works.iterative -package ui.components.tailwind +package ui.components +package tailwind import com.raquo.laminar.api.L.{*, given} @@ -7,4 +8,4 @@ def card(content: Modifier[HtmlElement]*)(using cctx: ComponentContext[_] ): Div = - div(cls(cctx.style.card), content) + div(cls("bg-white shadow sm:rounded-md overflow-hidden"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala deleted file mode 100644 index f210a37..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala +++ /dev/null @@ -1,50 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonStyles: - def basic: String - def primary: String - def secondary: String - def positive: String - def negative: String - -trait StyleGuide: - def button: ButtonStyles - def label: String - def cardContent: String - def card: String - def input: String - -object StyleGuide: - object default extends StyleGuide: - override val label: String = - "text-sm font-medium text-gray-500" - override val cardContent: String = "px-4 py-5 sm:p-6" - override val card: String = - "bg-white shadow sm:rounded-md overflow-hidden" - override val input: String = - "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" - - override object button extends ButtonStyles: - private def common(extra: String) = - s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" - override val basic: String = - common( - "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" - ) - override val primary: String = - common( - "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" - ) - override val secondary: String = - common( - "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" - ) - override val positive: String = - common( - "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" - ) - override val negative: String = - common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala deleted file mode 100644 index d5a78ee..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala +++ /dev/null @@ -1,46 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle -import java.time.ZoneId -import java.time.Instant -import java.time.temporal.TemporalAccessor - -object TimeUtils: - val dateTimeFormat = - DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val dateFormat = - DateTimeFormatter - .ofLocalizedDate(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val htmlDateFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd") - .withZone(ZoneId.systemDefault()) - - val htmlDateTimeFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss") - .withZone(ZoneId.systemDefault()) - - def formatDateTime(i: TemporalAccessor): String = - dateTimeFormat.format(i) - - def formatDate(i: TemporalAccessor): String = - dateFormat.format(i) - - def formatHtmlDate(i: TemporalAccessor): String = - htmlDateFormat.format(i) - - def formatHtmlDateTime(i: TemporalAccessor): String = - htmlDateTimeFormat.format(i) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala index 8caee39..b5af03c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala @@ -2,14 +2,14 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.UIString -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.LocalDate import works.iterative.ui.components.tailwind.BaseHtmlComponent -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable import works.iterative.ui.components.tailwind.form.ActionButtons import works.iterative.ui.components.tailwind.HtmlComponent import works.iterative.ui.components.tailwind.form.ActionButton -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.ui.components.tailwind.Icons import scala.reflect.ClassTag diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala index 28d1e6e..f5ac8cb 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala @@ -4,7 +4,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.core.MessageId import works.iterative.ui.components.tailwind.HtmlComponent -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext case class ActionButtonStyle( border: String, diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala index 8c5b8a1..d9ee9aa 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind package form import com.raquo.laminar.api.L.{*, given} @@ -35,7 +36,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", @@ -79,7 +80,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala index 023f0b0..587a35a 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala @@ -5,7 +5,7 @@ import com.raquo.laminar.api.L.{*, given} import java.time.LocalDate -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormInput[V]: def render( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala index e7294be..73ce0a0 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import zio.prelude.Validation -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext class Switch[V](using codec: FormCodec[V, Boolean], ctx: ComponentContext[_]) extends FormInput[V]: diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala index 0caff74..5f17be5 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala @@ -1,5 +1,6 @@ package works.iterative -package ui.components.tailwind +package ui.components +package tailwind import com.raquo.laminar.api.L.{*, given} @@ -7,4 +8,4 @@ def card(content: Modifier[HtmlElement]*)(using cctx: ComponentContext[_] ): Div = - div(cls(cctx.style.card), content) + div(cls("bg-white shadow sm:rounded-md overflow-hidden"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala deleted file mode 100644 index f210a37..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala +++ /dev/null @@ -1,50 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonStyles: - def basic: String - def primary: String - def secondary: String - def positive: String - def negative: String - -trait StyleGuide: - def button: ButtonStyles - def label: String - def cardContent: String - def card: String - def input: String - -object StyleGuide: - object default extends StyleGuide: - override val label: String = - "text-sm font-medium text-gray-500" - override val cardContent: String = "px-4 py-5 sm:p-6" - override val card: String = - "bg-white shadow sm:rounded-md overflow-hidden" - override val input: String = - "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" - - override object button extends ButtonStyles: - private def common(extra: String) = - s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" - override val basic: String = - common( - "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" - ) - override val primary: String = - common( - "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" - ) - override val secondary: String = - common( - "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" - ) - override val positive: String = - common( - "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" - ) - override val negative: String = - common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala deleted file mode 100644 index d5a78ee..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala +++ /dev/null @@ -1,46 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle -import java.time.ZoneId -import java.time.Instant -import java.time.temporal.TemporalAccessor - -object TimeUtils: - val dateTimeFormat = - DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val dateFormat = - DateTimeFormatter - .ofLocalizedDate(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val htmlDateFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd") - .withZone(ZoneId.systemDefault()) - - val htmlDateTimeFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss") - .withZone(ZoneId.systemDefault()) - - def formatDateTime(i: TemporalAccessor): String = - dateTimeFormat.format(i) - - def formatDate(i: TemporalAccessor): String = - dateFormat.format(i) - - def formatHtmlDate(i: TemporalAccessor): String = - htmlDateFormat.format(i) - - def formatHtmlDateTime(i: TemporalAccessor): String = - htmlDateTimeFormat.format(i) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala index 8caee39..b5af03c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala @@ -2,14 +2,14 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.UIString -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.LocalDate import works.iterative.ui.components.tailwind.BaseHtmlComponent -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable import works.iterative.ui.components.tailwind.form.ActionButtons import works.iterative.ui.components.tailwind.HtmlComponent import works.iterative.ui.components.tailwind.form.ActionButton -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.ui.components.tailwind.Icons import scala.reflect.ClassTag diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala index 28d1e6e..f5ac8cb 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala @@ -4,7 +4,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.core.MessageId import works.iterative.ui.components.tailwind.HtmlComponent -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext case class ActionButtonStyle( border: String, diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala index 8c5b8a1..d9ee9aa 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind package form import com.raquo.laminar.api.L.{*, given} @@ -35,7 +36,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", @@ -79,7 +80,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala index 023f0b0..587a35a 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala @@ -5,7 +5,7 @@ import com.raquo.laminar.api.L.{*, given} import java.time.LocalDate -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormInput[V]: def render( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala index e7294be..73ce0a0 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import zio.prelude.Validation -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext class Switch[V](using codec: FormCodec[V, Boolean], ctx: ComponentContext[_]) extends FormInput[V]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala deleted file mode 100644 index 2e3b7b8..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala +++ /dev/null @@ -1,30 +0,0 @@ -package works.iterative.ui.components.tailwind -package laminar - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.core.UserMessage -import io.laminext.syntax.core.* -import works.iterative.core.MessageId - -object LaminarExtensions: - extension (msg: UserMessage) - inline def asElement(using ctx: ComponentContext[_]): HtmlElement = - span(msg.asMod) - - inline def asOptionalElement(using - ctx: ComponentContext[_] - ): Option[HtmlElement] = - ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) - - inline def asString(using ctx: ComponentContext[_]): String = - ctx.messages(msg) - - inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = - msgAttrs(msg.id, ctx.messages(msg)) - - private inline def msgAttrs(id: MessageId, text: String): HtmlMod = - nodeSeq(dataAttr("msgid")(id.toString()), text) - - given (using ComponentContext[_]): HtmlRenderable[UserMessage] with - def toHtml(msg: UserMessage): Modifier[HtmlElement] = - msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala index 0caff74..5f17be5 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala @@ -1,5 +1,6 @@ package works.iterative -package ui.components.tailwind +package ui.components +package tailwind import com.raquo.laminar.api.L.{*, given} @@ -7,4 +8,4 @@ def card(content: Modifier[HtmlElement]*)(using cctx: ComponentContext[_] ): Div = - div(cls(cctx.style.card), content) + div(cls("bg-white shadow sm:rounded-md overflow-hidden"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala deleted file mode 100644 index f210a37..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala +++ /dev/null @@ -1,50 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonStyles: - def basic: String - def primary: String - def secondary: String - def positive: String - def negative: String - -trait StyleGuide: - def button: ButtonStyles - def label: String - def cardContent: String - def card: String - def input: String - -object StyleGuide: - object default extends StyleGuide: - override val label: String = - "text-sm font-medium text-gray-500" - override val cardContent: String = "px-4 py-5 sm:p-6" - override val card: String = - "bg-white shadow sm:rounded-md overflow-hidden" - override val input: String = - "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" - - override object button extends ButtonStyles: - private def common(extra: String) = - s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" - override val basic: String = - common( - "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" - ) - override val primary: String = - common( - "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" - ) - override val secondary: String = - common( - "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" - ) - override val positive: String = - common( - "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" - ) - override val negative: String = - common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala deleted file mode 100644 index d5a78ee..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala +++ /dev/null @@ -1,46 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle -import java.time.ZoneId -import java.time.Instant -import java.time.temporal.TemporalAccessor - -object TimeUtils: - val dateTimeFormat = - DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val dateFormat = - DateTimeFormatter - .ofLocalizedDate(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val htmlDateFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd") - .withZone(ZoneId.systemDefault()) - - val htmlDateTimeFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss") - .withZone(ZoneId.systemDefault()) - - def formatDateTime(i: TemporalAccessor): String = - dateTimeFormat.format(i) - - def formatDate(i: TemporalAccessor): String = - dateFormat.format(i) - - def formatHtmlDate(i: TemporalAccessor): String = - htmlDateFormat.format(i) - - def formatHtmlDateTime(i: TemporalAccessor): String = - htmlDateTimeFormat.format(i) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala index 8caee39..b5af03c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala @@ -2,14 +2,14 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.UIString -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.LocalDate import works.iterative.ui.components.tailwind.BaseHtmlComponent -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable import works.iterative.ui.components.tailwind.form.ActionButtons import works.iterative.ui.components.tailwind.HtmlComponent import works.iterative.ui.components.tailwind.form.ActionButton -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.ui.components.tailwind.Icons import scala.reflect.ClassTag diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala index 28d1e6e..f5ac8cb 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala @@ -4,7 +4,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.core.MessageId import works.iterative.ui.components.tailwind.HtmlComponent -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext case class ActionButtonStyle( border: String, diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala index 8c5b8a1..d9ee9aa 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind package form import com.raquo.laminar.api.L.{*, given} @@ -35,7 +36,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", @@ -79,7 +80,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala index 023f0b0..587a35a 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala @@ -5,7 +5,7 @@ import com.raquo.laminar.api.L.{*, given} import java.time.LocalDate -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormInput[V]: def render( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala index e7294be..73ce0a0 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import zio.prelude.Validation -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext class Switch[V](using codec: FormCodec[V, Boolean], ctx: ComponentContext[_]) extends FormInput[V]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala deleted file mode 100644 index 2e3b7b8..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala +++ /dev/null @@ -1,30 +0,0 @@ -package works.iterative.ui.components.tailwind -package laminar - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.core.UserMessage -import io.laminext.syntax.core.* -import works.iterative.core.MessageId - -object LaminarExtensions: - extension (msg: UserMessage) - inline def asElement(using ctx: ComponentContext[_]): HtmlElement = - span(msg.asMod) - - inline def asOptionalElement(using - ctx: ComponentContext[_] - ): Option[HtmlElement] = - ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) - - inline def asString(using ctx: ComponentContext[_]): String = - ctx.messages(msg) - - inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = - msgAttrs(msg.id, ctx.messages(msg)) - - private inline def msgAttrs(id: MessageId, text: String): HtmlMod = - nodeSeq(dataAttr("msgid")(id.toString()), text) - - given (using ComponentContext[_]): HtmlRenderable[UserMessage] with - def toHtml(msg: UserMessage): Modifier[HtmlElement] = - msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala index 5771e4d..2458be4 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import java.time.Instant -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.temporal.TemporalAccessor import java.text.DateFormat import com.raquo.laminar.codecs.StringAsIsCodec diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala index 0caff74..5f17be5 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala @@ -1,5 +1,6 @@ package works.iterative -package ui.components.tailwind +package ui.components +package tailwind import com.raquo.laminar.api.L.{*, given} @@ -7,4 +8,4 @@ def card(content: Modifier[HtmlElement]*)(using cctx: ComponentContext[_] ): Div = - div(cls(cctx.style.card), content) + div(cls("bg-white shadow sm:rounded-md overflow-hidden"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala deleted file mode 100644 index f210a37..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala +++ /dev/null @@ -1,50 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonStyles: - def basic: String - def primary: String - def secondary: String - def positive: String - def negative: String - -trait StyleGuide: - def button: ButtonStyles - def label: String - def cardContent: String - def card: String - def input: String - -object StyleGuide: - object default extends StyleGuide: - override val label: String = - "text-sm font-medium text-gray-500" - override val cardContent: String = "px-4 py-5 sm:p-6" - override val card: String = - "bg-white shadow sm:rounded-md overflow-hidden" - override val input: String = - "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" - - override object button extends ButtonStyles: - private def common(extra: String) = - s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" - override val basic: String = - common( - "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" - ) - override val primary: String = - common( - "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" - ) - override val secondary: String = - common( - "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" - ) - override val positive: String = - common( - "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" - ) - override val negative: String = - common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala deleted file mode 100644 index d5a78ee..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala +++ /dev/null @@ -1,46 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle -import java.time.ZoneId -import java.time.Instant -import java.time.temporal.TemporalAccessor - -object TimeUtils: - val dateTimeFormat = - DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val dateFormat = - DateTimeFormatter - .ofLocalizedDate(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val htmlDateFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd") - .withZone(ZoneId.systemDefault()) - - val htmlDateTimeFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss") - .withZone(ZoneId.systemDefault()) - - def formatDateTime(i: TemporalAccessor): String = - dateTimeFormat.format(i) - - def formatDate(i: TemporalAccessor): String = - dateFormat.format(i) - - def formatHtmlDate(i: TemporalAccessor): String = - htmlDateFormat.format(i) - - def formatHtmlDateTime(i: TemporalAccessor): String = - htmlDateTimeFormat.format(i) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala index 8caee39..b5af03c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala @@ -2,14 +2,14 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.UIString -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.LocalDate import works.iterative.ui.components.tailwind.BaseHtmlComponent -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable import works.iterative.ui.components.tailwind.form.ActionButtons import works.iterative.ui.components.tailwind.HtmlComponent import works.iterative.ui.components.tailwind.form.ActionButton -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.ui.components.tailwind.Icons import scala.reflect.ClassTag diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala index 28d1e6e..f5ac8cb 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala @@ -4,7 +4,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.core.MessageId import works.iterative.ui.components.tailwind.HtmlComponent -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext case class ActionButtonStyle( border: String, diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala index 8c5b8a1..d9ee9aa 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind package form import com.raquo.laminar.api.L.{*, given} @@ -35,7 +36,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", @@ -79,7 +80,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala index 023f0b0..587a35a 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala @@ -5,7 +5,7 @@ import com.raquo.laminar.api.L.{*, given} import java.time.LocalDate -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormInput[V]: def render( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala index e7294be..73ce0a0 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import zio.prelude.Validation -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext class Switch[V](using codec: FormCodec[V, Boolean], ctx: ComponentContext[_]) extends FormInput[V]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala deleted file mode 100644 index 2e3b7b8..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala +++ /dev/null @@ -1,30 +0,0 @@ -package works.iterative.ui.components.tailwind -package laminar - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.core.UserMessage -import io.laminext.syntax.core.* -import works.iterative.core.MessageId - -object LaminarExtensions: - extension (msg: UserMessage) - inline def asElement(using ctx: ComponentContext[_]): HtmlElement = - span(msg.asMod) - - inline def asOptionalElement(using - ctx: ComponentContext[_] - ): Option[HtmlElement] = - ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) - - inline def asString(using ctx: ComponentContext[_]): String = - ctx.messages(msg) - - inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = - msgAttrs(msg.id, ctx.messages(msg)) - - private inline def msgAttrs(id: MessageId, text: String): HtmlMod = - nodeSeq(dataAttr("msgid")(id.toString()), text) - - given (using ComponentContext[_]): HtmlRenderable[UserMessage] with - def toHtml(msg: UserMessage): Modifier[HtmlElement] = - msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala index 5771e4d..2458be4 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import java.time.Instant -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.temporal.TemporalAccessor import java.text.DateFormat import com.raquo.laminar.codecs.StringAsIsCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala index a47b211..a6f4022 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.core.MessageId -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext object Tabs: def apply[T](tabs: Seq[(MessageId, T)], selected: Signal[MessageId])( diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala index 0caff74..5f17be5 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala @@ -1,5 +1,6 @@ package works.iterative -package ui.components.tailwind +package ui.components +package tailwind import com.raquo.laminar.api.L.{*, given} @@ -7,4 +8,4 @@ def card(content: Modifier[HtmlElement]*)(using cctx: ComponentContext[_] ): Div = - div(cls(cctx.style.card), content) + div(cls("bg-white shadow sm:rounded-md overflow-hidden"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala deleted file mode 100644 index f210a37..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala +++ /dev/null @@ -1,50 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonStyles: - def basic: String - def primary: String - def secondary: String - def positive: String - def negative: String - -trait StyleGuide: - def button: ButtonStyles - def label: String - def cardContent: String - def card: String - def input: String - -object StyleGuide: - object default extends StyleGuide: - override val label: String = - "text-sm font-medium text-gray-500" - override val cardContent: String = "px-4 py-5 sm:p-6" - override val card: String = - "bg-white shadow sm:rounded-md overflow-hidden" - override val input: String = - "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" - - override object button extends ButtonStyles: - private def common(extra: String) = - s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" - override val basic: String = - common( - "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" - ) - override val primary: String = - common( - "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" - ) - override val secondary: String = - common( - "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" - ) - override val positive: String = - common( - "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" - ) - override val negative: String = - common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala deleted file mode 100644 index d5a78ee..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala +++ /dev/null @@ -1,46 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle -import java.time.ZoneId -import java.time.Instant -import java.time.temporal.TemporalAccessor - -object TimeUtils: - val dateTimeFormat = - DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val dateFormat = - DateTimeFormatter - .ofLocalizedDate(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val htmlDateFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd") - .withZone(ZoneId.systemDefault()) - - val htmlDateTimeFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss") - .withZone(ZoneId.systemDefault()) - - def formatDateTime(i: TemporalAccessor): String = - dateTimeFormat.format(i) - - def formatDate(i: TemporalAccessor): String = - dateFormat.format(i) - - def formatHtmlDate(i: TemporalAccessor): String = - htmlDateFormat.format(i) - - def formatHtmlDateTime(i: TemporalAccessor): String = - htmlDateTimeFormat.format(i) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala index 8caee39..b5af03c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala @@ -2,14 +2,14 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.UIString -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.LocalDate import works.iterative.ui.components.tailwind.BaseHtmlComponent -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable import works.iterative.ui.components.tailwind.form.ActionButtons import works.iterative.ui.components.tailwind.HtmlComponent import works.iterative.ui.components.tailwind.form.ActionButton -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.ui.components.tailwind.Icons import scala.reflect.ClassTag diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala index 28d1e6e..f5ac8cb 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala @@ -4,7 +4,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.core.MessageId import works.iterative.ui.components.tailwind.HtmlComponent -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext case class ActionButtonStyle( border: String, diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala index 8c5b8a1..d9ee9aa 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind package form import com.raquo.laminar.api.L.{*, given} @@ -35,7 +36,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", @@ -79,7 +80,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala index 023f0b0..587a35a 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala @@ -5,7 +5,7 @@ import com.raquo.laminar.api.L.{*, given} import java.time.LocalDate -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormInput[V]: def render( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala index e7294be..73ce0a0 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import zio.prelude.Validation -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext class Switch[V](using codec: FormCodec[V, Boolean], ctx: ComponentContext[_]) extends FormInput[V]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala deleted file mode 100644 index 2e3b7b8..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala +++ /dev/null @@ -1,30 +0,0 @@ -package works.iterative.ui.components.tailwind -package laminar - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.core.UserMessage -import io.laminext.syntax.core.* -import works.iterative.core.MessageId - -object LaminarExtensions: - extension (msg: UserMessage) - inline def asElement(using ctx: ComponentContext[_]): HtmlElement = - span(msg.asMod) - - inline def asOptionalElement(using - ctx: ComponentContext[_] - ): Option[HtmlElement] = - ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) - - inline def asString(using ctx: ComponentContext[_]): String = - ctx.messages(msg) - - inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = - msgAttrs(msg.id, ctx.messages(msg)) - - private inline def msgAttrs(id: MessageId, text: String): HtmlMod = - nodeSeq(dataAttr("msgid")(id.toString()), text) - - given (using ComponentContext[_]): HtmlRenderable[UserMessage] with - def toHtml(msg: UserMessage): Modifier[HtmlElement] = - msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala index 5771e4d..2458be4 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import java.time.Instant -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.temporal.TemporalAccessor import java.text.DateFormat import com.raquo.laminar.codecs.StringAsIsCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala index a47b211..a6f4022 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.core.MessageId -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext object Tabs: def apply[T](tabs: Seq[(MessageId, T)], selected: Signal[MessageId])( diff --git a/ui/js/src/main/scala/works/iterative/ui/scenarios/Scenario.scala b/ui/js/src/main/scala/works/iterative/ui/scenarios/Scenario.scala index e4e14c4..ff4203e 100644 --- a/ui/js/src/main/scala/works/iterative/ui/scenarios/Scenario.scala +++ b/ui/js/src/main/scala/works/iterative/ui/scenarios/Scenario.scala @@ -11,8 +11,7 @@ import scala.scalajs.js import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.components.tailwind.StyleGuide +import works.iterative.ui.components.ComponentContext object Scenario: type Id = String diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala index 0caff74..5f17be5 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala @@ -1,5 +1,6 @@ package works.iterative -package ui.components.tailwind +package ui.components +package tailwind import com.raquo.laminar.api.L.{*, given} @@ -7,4 +8,4 @@ def card(content: Modifier[HtmlElement]*)(using cctx: ComponentContext[_] ): Div = - div(cls(cctx.style.card), content) + div(cls("bg-white shadow sm:rounded-md overflow-hidden"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala deleted file mode 100644 index f210a37..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala +++ /dev/null @@ -1,50 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonStyles: - def basic: String - def primary: String - def secondary: String - def positive: String - def negative: String - -trait StyleGuide: - def button: ButtonStyles - def label: String - def cardContent: String - def card: String - def input: String - -object StyleGuide: - object default extends StyleGuide: - override val label: String = - "text-sm font-medium text-gray-500" - override val cardContent: String = "px-4 py-5 sm:p-6" - override val card: String = - "bg-white shadow sm:rounded-md overflow-hidden" - override val input: String = - "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" - - override object button extends ButtonStyles: - private def common(extra: String) = - s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" - override val basic: String = - common( - "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" - ) - override val primary: String = - common( - "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" - ) - override val secondary: String = - common( - "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" - ) - override val positive: String = - common( - "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" - ) - override val negative: String = - common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala deleted file mode 100644 index d5a78ee..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala +++ /dev/null @@ -1,46 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle -import java.time.ZoneId -import java.time.Instant -import java.time.temporal.TemporalAccessor - -object TimeUtils: - val dateTimeFormat = - DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val dateFormat = - DateTimeFormatter - .ofLocalizedDate(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val htmlDateFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd") - .withZone(ZoneId.systemDefault()) - - val htmlDateTimeFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss") - .withZone(ZoneId.systemDefault()) - - def formatDateTime(i: TemporalAccessor): String = - dateTimeFormat.format(i) - - def formatDate(i: TemporalAccessor): String = - dateFormat.format(i) - - def formatHtmlDate(i: TemporalAccessor): String = - htmlDateFormat.format(i) - - def formatHtmlDateTime(i: TemporalAccessor): String = - htmlDateTimeFormat.format(i) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala index 8caee39..b5af03c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala @@ -2,14 +2,14 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.UIString -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.LocalDate import works.iterative.ui.components.tailwind.BaseHtmlComponent -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable import works.iterative.ui.components.tailwind.form.ActionButtons import works.iterative.ui.components.tailwind.HtmlComponent import works.iterative.ui.components.tailwind.form.ActionButton -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.ui.components.tailwind.Icons import scala.reflect.ClassTag diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala index 28d1e6e..f5ac8cb 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala @@ -4,7 +4,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.core.MessageId import works.iterative.ui.components.tailwind.HtmlComponent -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext case class ActionButtonStyle( border: String, diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala index 8c5b8a1..d9ee9aa 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind package form import com.raquo.laminar.api.L.{*, given} @@ -35,7 +36,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", @@ -79,7 +80,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala index 023f0b0..587a35a 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala @@ -5,7 +5,7 @@ import com.raquo.laminar.api.L.{*, given} import java.time.LocalDate -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormInput[V]: def render( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala index e7294be..73ce0a0 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import zio.prelude.Validation -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext class Switch[V](using codec: FormCodec[V, Boolean], ctx: ComponentContext[_]) extends FormInput[V]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala deleted file mode 100644 index 2e3b7b8..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala +++ /dev/null @@ -1,30 +0,0 @@ -package works.iterative.ui.components.tailwind -package laminar - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.core.UserMessage -import io.laminext.syntax.core.* -import works.iterative.core.MessageId - -object LaminarExtensions: - extension (msg: UserMessage) - inline def asElement(using ctx: ComponentContext[_]): HtmlElement = - span(msg.asMod) - - inline def asOptionalElement(using - ctx: ComponentContext[_] - ): Option[HtmlElement] = - ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) - - inline def asString(using ctx: ComponentContext[_]): String = - ctx.messages(msg) - - inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = - msgAttrs(msg.id, ctx.messages(msg)) - - private inline def msgAttrs(id: MessageId, text: String): HtmlMod = - nodeSeq(dataAttr("msgid")(id.toString()), text) - - given (using ComponentContext[_]): HtmlRenderable[UserMessage] with - def toHtml(msg: UserMessage): Modifier[HtmlElement] = - msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala index 5771e4d..2458be4 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import java.time.Instant -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.temporal.TemporalAccessor import java.text.DateFormat import com.raquo.laminar.codecs.StringAsIsCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala index a47b211..a6f4022 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.core.MessageId -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext object Tabs: def apply[T](tabs: Seq[(MessageId, T)], selected: Signal[MessageId])( diff --git a/ui/js/src/main/scala/works/iterative/ui/scenarios/Scenario.scala b/ui/js/src/main/scala/works/iterative/ui/scenarios/Scenario.scala index e4e14c4..ff4203e 100644 --- a/ui/js/src/main/scala/works/iterative/ui/scenarios/Scenario.scala +++ b/ui/js/src/main/scala/works/iterative/ui/scenarios/Scenario.scala @@ -11,8 +11,7 @@ import scala.scalajs.js import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.components.tailwind.StyleGuide +import works.iterative.ui.components.ComponentContext object Scenario: type Id = String diff --git a/ui/js/src/main/scala/works/iterative/ui/scenarios/ScenarioMain.scala b/ui/js/src/main/scala/works/iterative/ui/scenarios/ScenarioMain.scala index 5265c5a..cc15f90 100644 --- a/ui/js/src/main/scala/works/iterative/ui/scenarios/ScenarioMain.scala +++ b/ui/js/src/main/scala/works/iterative/ui/scenarios/ScenarioMain.scala @@ -7,8 +7,7 @@ import scala.scalajs.js import works.iterative.ui.JsonMessageCatalogue import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.components.tailwind.StyleGuide +import works.iterative.ui.components.ComponentContext import ui.components.tailwind.TailwindSupport import com.raquo.waypoint.* @@ -52,7 +51,6 @@ given ComponentContext[Unit] with val app: Unit = () val messages: MessageCatalogue = messageCatalogue - val style: StyleGuide = StyleGuide.default def container: HtmlElement = div( diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala index 66eb6e7..af9cf58 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/File.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.components.tailwind.Icons -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable given HtmlRenderable[File] with def toHtml(m: File): HtmlElement = diff --git a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala index fb129be..41cb94c 100644 --- a/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala +++ b/ui/js/src/main/scala/works/iterative/services/files/components/tailwind/FileTable.scala @@ -8,7 +8,7 @@ import java.time.format.FormatStyle import java.time.ZoneId import java.util.Locale -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import works.iterative.core.CzechSupport def FileTable( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala new file mode 100644 index 0000000..36fd8ea --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/ComponentContext.scala @@ -0,0 +1,19 @@ +package works.iterative +package ui.components + +import works.iterative.core.MessageCatalogue + +trait ComponentContext[App]: + def app: App + def messages: MessageCatalogue + + def nested(prefixes: String*): ComponentContext[App] = + ComponentContext.Nested[App](this, prefixes) + +object ComponentContext: + case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) + extends ComponentContext[App]: + export parent.{messages => _, *} + + override lazy val messages: MessageCatalogue = + parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala new file mode 100644 index 0000000..257391a --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/CustomAttrs.scala @@ -0,0 +1,18 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} + +object CustomAttrs { + // Made a pull request to add aria-current to scala-dom-types, remove after + val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) + val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) + + val datetime = htmlAttr("datetime", StringAsIsCodec) + + object svg { + import com.raquo.laminar.api.L.svg.{*, given} + val ariaHidden = + svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) + } +} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala index 2b116cb..519ab18 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/FormComponents.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormComponents(using ctx: ComponentContext[_]) extends LocalDateSelectModule: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala new file mode 100644 index 0000000..01ba363 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/HtmlRenderable.scala @@ -0,0 +1,39 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import org.scalajs.dom +import java.time.LocalDate +import works.iterative.core.PlainMultiLine +import java.time.Instant +import java.time.format.DateTimeFormatter +import works.iterative.ui.TimeUtils + +trait HtmlRenderable[A]: + def toHtml(a: A): Modifier[HtmlElement] + extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) + +object HtmlRenderable: + given elementValue: HtmlRenderable[HtmlElement] with + def toHtml(a: HtmlElement): Modifier[HtmlElement] = a + + given stringValue: HtmlRenderable[String] with + def toHtml(v: String): Modifier[HtmlElement] = + com.raquo.laminar.nodes.TextNode(v) + + given dateValue: HtmlRenderable[LocalDate] with + def toHtml(v: LocalDate): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDate(v)), + TimeUtils.formatDate(v) + ) + + given instantValue: HtmlRenderable[Instant] with + def toHtml(v: Instant): Modifier[HtmlElement] = + timeTag( + CustomAttrs.datetime(TimeUtils.formatHtmlDateTime(v)), + TimeUtils.formatDateTime(v) + ) + + given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with + def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = + p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala new file mode 100644 index 0000000..71e8529 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LaminarExtensions.scala @@ -0,0 +1,33 @@ +package works.iterative.ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} +import works.iterative.core.UserMessage +import io.laminext.syntax.core.* +import works.iterative.core.MessageId +import com.raquo.laminar.modifiers.RenderableNode +import com.raquo.laminar.nodes.ChildNode.Base +import works.iterative.ui.components.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable + +object LaminarExtensions: + extension (msg: UserMessage) + inline def asElement(using ctx: ComponentContext[_]): HtmlElement = + span(msg.asMod) + + inline def asOptionalElement(using + ctx: ComponentContext[_] + ): Option[HtmlElement] = + ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) + + inline def asString(using ctx: ComponentContext[_]): String = + ctx.messages(msg) + + inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = + msgAttrs(msg.id, ctx.messages(msg)) + + private inline def msgAttrs(id: MessageId, text: String): HtmlMod = + nodeSeq(dataAttr("msgid")(id.toString()), text) + + given (using ComponentContext[_]): HtmlRenderable[UserMessage] with + def toHtml(msg: UserMessage): Modifier[HtmlElement] = + msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala index 4a2af6a..26a7e77 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/LocalDateSelect.scala @@ -2,7 +2,7 @@ import com.raquo.laminar.api.L import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import java.time.LocalDate import java.time.format.DateTimeFormatter diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala new file mode 100644 index 0000000..6d66554 --- /dev/null +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/StyleGuide.scala @@ -0,0 +1,50 @@ +package works.iterative +package ui.components.laminar + +import com.raquo.laminar.api.L.{*, given} + +trait ButtonStyles: + def basic: String + def primary: String + def secondary: String + def positive: String + def negative: String + +trait StyleGuide: + def button: ButtonStyles + def label: String + def cardContent: String + def card: String + def input: String + +object StyleGuide: + object default extends StyleGuide: + override val label: String = + "text-sm font-medium text-gray-500" + override val cardContent: String = "px-4 py-5 sm:p-6" + override val card: String = + "bg-white shadow sm:rounded-md overflow-hidden" + override val input: String = + "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" + + override object button extends ButtonStyles: + private def common(extra: String) = + s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" + override val basic: String = + common( + "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" + ) + override val primary: String = + common( + "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" + ) + override val secondary: String = + common( + "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" + ) + override val positive: String = + common( + "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" + ) + override val negative: String = + common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala index ef24ec3..6b0ebcd 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldBuilder.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FieldBuilder[A]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala index 03b1a57..28ef399 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FieldDescriptor.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.PlainMultiLine -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FieldDescriptor: def id: FieldId diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala index 2a72409..fa55207 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/Form.scala @@ -2,7 +2,7 @@ import zio.prelude.* import com.raquo.laminar.api.L.{*, given} -import works.iterative.ui.components.tailwind.HtmlRenderable.given +import works.iterative.ui.components.laminar.HtmlRenderable.given sealed trait Form[A] extends FormBuilder[A] diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala index c8a1b53..7cb5ee2 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormBuilderModule.scala @@ -8,8 +8,8 @@ import works.iterative.core.UserMessage import works.iterative.core.PlainMultiLine import com.raquo.airstream.core.Signal -import works.iterative.ui.components.tailwind.HtmlRenderable.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.HtmlRenderable.given +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue trait FormBuilderModule(using fctx: FormBuilderContext): diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala index bd1ab7b..d4c21cf 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/FormMessagesResolver.scala @@ -1,7 +1,7 @@ package works.iterative.ui.components.laminar.forms import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.UserMessage trait FormMessagesResolver: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala index 276d464..44a50a6 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/forms/SectionDescriptor.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.forms -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait SectionDescriptor: def title: String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala index 5fa6b79..7ee9c63 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableBuilderModule.scala @@ -4,8 +4,8 @@ import works.iterative.ui.components.laminar.HtmlTabular import works.iterative.ui.model.tables.Tabular import works.iterative.core.UserMessage -import works.iterative.ui.components.tailwind.laminar.LaminarExtensions.given -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.laminar.LaminarExtensions.given +import works.iterative.ui.components.ComponentContext import com.raquo.laminar.nodes.ReactiveHtmlElement import org.scalajs.dom.html diff --git a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala index 2862dd5..782b912 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/laminar/tables/TableHeaderResolver.scala @@ -1,6 +1,6 @@ package works.iterative.ui.components.laminar.tables -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.core.MessageCatalogue opaque type TableHeaderResolver = String => String diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala index 7d6cc70..305f467 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Avatar.scala @@ -1,6 +1,7 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind -import CustomAttrs.ariaHidden +import laminar.CustomAttrs.ariaHidden import com.raquo.laminar.api.L.{*, given} import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala deleted file mode 100644 index 23bd049..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/ComponentContext.scala +++ /dev/null @@ -1,20 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import works.iterative.core.MessageCatalogue - -trait ComponentContext[App]: - def app: App - def messages: MessageCatalogue - def style: StyleGuide - - def nested(prefixes: String*): ComponentContext[App] = - ComponentContext.Nested[App](this, prefixes) - -object ComponentContext: - case class Nested[App](parent: ComponentContext[App], prefixes: Seq[String]) - extends ComponentContext[App]: - export parent.{messages => _, *} - - override lazy val messages: MessageCatalogue = - parent.messages.nested(prefixes*) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala deleted file mode 100644 index b803b06..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/CustomAttrs.scala +++ /dev/null @@ -1,18 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import com.raquo.laminar.codecs.{StringAsIsCodec, BooleanAsTrueFalseStringCodec} - -object CustomAttrs { - // Made a pull request to add aria-current to scala-dom-types, remove after - val ariaCurrent = htmlAttr("aria-current", StringAsIsCodec) - val ariaHidden = htmlAttr("aria-hidden", BooleanAsTrueFalseStringCodec) - - val datetime = htmlAttr("datetime", StringAsIsCodec) - - object svg { - import com.raquo.laminar.api.L.svg.{*, given} - val ariaHidden = - svgAttr("aria-hidden", BooleanAsTrueFalseStringCodec, None) - } -} diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala deleted file mode 100644 index f2b20ae..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/HtmlRenderable.scala +++ /dev/null @@ -1,36 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} -import org.scalajs.dom -import java.time.LocalDate -import works.iterative.core.PlainMultiLine -import java.time.Instant -import java.time.format.DateTimeFormatter -import works.iterative.ui.components.tailwind.CustomAttrs.datetime - -trait HtmlRenderable[A]: - def toHtml(a: A): Modifier[HtmlElement] - extension (a: A) def render: Modifier[HtmlElement] = toHtml(a) - -object HtmlRenderable: - given elementValue: HtmlRenderable[HtmlElement] with - def toHtml(a: HtmlElement): Modifier[HtmlElement] = a - - given stringValue: HtmlRenderable[String] with - def toHtml(v: String): Modifier[HtmlElement] = - com.raquo.laminar.nodes.TextNode(v) - - given dateValue: HtmlRenderable[LocalDate] with - def toHtml(v: LocalDate): Modifier[HtmlElement] = - timeTag(datetime(TimeUtils.formatHtmlDate(v)), TimeUtils.formatDate(v)) - - given instantValue: HtmlRenderable[Instant] with - def toHtml(v: Instant): Modifier[HtmlElement] = - timeTag( - datetime(TimeUtils.formatHtmlDateTime(v)), - TimeUtils.formatDateTime(v) - ) - - given plainMultiLineValue: HtmlRenderable[PlainMultiLine] with - def toHtml(v: PlainMultiLine): Modifier[HtmlElement] = - p(cls("whitespace-pre-wrap"), v.toString) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala index 4cb815d..796c251 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Icons.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind import com.raquo.laminar.codecs.BooleanAsTrueFalseStringCodec import com.raquo.laminar.api.L.svg.{*, given} @@ -8,7 +9,7 @@ object Icons: object aria: - val hidden = CustomAttrs.svg.ariaHidden + val hidden = laminar.CustomAttrs.svg.ariaHidden inline def spinner(extraClasses: String): SvgElement = svg( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala index 0caff74..5f17be5 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/Layout.scala @@ -1,5 +1,6 @@ package works.iterative -package ui.components.tailwind +package ui.components +package tailwind import com.raquo.laminar.api.L.{*, given} @@ -7,4 +8,4 @@ def card(content: Modifier[HtmlElement]*)(using cctx: ComponentContext[_] ): Div = - div(cls(cctx.style.card), content) + div(cls("bg-white shadow sm:rounded-md overflow-hidden"), content) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala deleted file mode 100644 index f210a37..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/StyleGuide.scala +++ /dev/null @@ -1,50 +0,0 @@ -package works.iterative -package ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -trait ButtonStyles: - def basic: String - def primary: String - def secondary: String - def positive: String - def negative: String - -trait StyleGuide: - def button: ButtonStyles - def label: String - def cardContent: String - def card: String - def input: String - -object StyleGuide: - object default extends StyleGuide: - override val label: String = - "text-sm font-medium text-gray-500" - override val cardContent: String = "px-4 py-5 sm:p-6" - override val card: String = - "bg-white shadow sm:rounded-md overflow-hidden" - override val input: String = - "shadow-sm block focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border border-gray-300 rounded-md" - - override object button extends ButtonStyles: - private def common(extra: String) = - s"inline-flex items-center px-4 py-2 $extra border rounded-md shadow-sm text-sm font-medium focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-75" - override val basic: String = - common( - "border-gray-300 text-gray-700 bg-white enabled:hover:bg-gray-50" - ) - override val primary: String = - common( - "border-transparent text-white bg-indigo-600 enabled:hover:bg-indigo-700" - ) - override val secondary: String = - common( - "border-gray-300 text-indigo-700 bg-indigo-100 enabled:hover:bg-indigo-200" - ) - override val positive: String = - common( - "border-gray-300 text-white bg-green-600 enabled:hover:bg-green-700" - ) - override val negative: String = - common("border-gray-300 text-white bg-red-600 enabled:hover:bg-red-700") diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala deleted file mode 100644 index d5a78ee..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/TimeUtils.scala +++ /dev/null @@ -1,46 +0,0 @@ -package works.iterative.ui.components.tailwind - -import com.raquo.laminar.api.L.{*, given} - -import java.time.format.DateTimeFormatter -import java.time.format.FormatStyle -import java.time.ZoneId -import java.time.Instant -import java.time.temporal.TemporalAccessor - -object TimeUtils: - val dateTimeFormat = - DateTimeFormatter - .ofLocalizedDateTime(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val dateFormat = - DateTimeFormatter - .ofLocalizedDate(FormatStyle.SHORT) - // TODO: locale - // .withLocale(Locale("cs", "CZ")) - .withZone(ZoneId.systemDefault()) - - val htmlDateFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd") - .withZone(ZoneId.systemDefault()) - - val htmlDateTimeFormat = - DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss") - .withZone(ZoneId.systemDefault()) - - def formatDateTime(i: TemporalAccessor): String = - dateTimeFormat.format(i) - - def formatDate(i: TemporalAccessor): String = - dateFormat.format(i) - - def formatHtmlDate(i: TemporalAccessor): String = - htmlDateFormat.format(i) - - def formatHtmlDateTime(i: TemporalAccessor): String = - htmlDateTimeFormat.format(i) diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala index 8caee39..b5af03c 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/data_display/description_lists/LeftAlignedInCard.scala @@ -2,14 +2,14 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.ui.UIString -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.LocalDate import works.iterative.ui.components.tailwind.BaseHtmlComponent -import works.iterative.ui.components.tailwind.HtmlRenderable +import works.iterative.ui.components.laminar.HtmlRenderable import works.iterative.ui.components.tailwind.form.ActionButtons import works.iterative.ui.components.tailwind.HtmlComponent import works.iterative.ui.components.tailwind.form.ActionButton -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext import works.iterative.ui.components.tailwind.Icons import scala.reflect.ClassTag diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala index 28d1e6e..f5ac8cb 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ActionButtons.scala @@ -4,7 +4,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.core.MessageId import works.iterative.ui.components.tailwind.HtmlComponent -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext case class ActionButtonStyle( border: String, diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala index 8c5b8a1..d9ee9aa 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/ComboBox.scala @@ -1,4 +1,5 @@ -package works.iterative.ui.components.tailwind +package works.iterative.ui.components +package tailwind package form import com.raquo.laminar.api.L.{*, given} @@ -35,7 +36,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z", @@ -79,7 +80,7 @@ xmlns := "http://www.w3.org/2000/svg", viewBox := "0 0 20 20", fill := "currentColor", - CustomAttrs.svg.ariaHidden := true, + laminar.CustomAttrs.svg.ariaHidden := true, path( fillRule := "evenodd", d := "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala index 023f0b0..587a35a 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/FormInput.scala @@ -5,7 +5,7 @@ import com.raquo.laminar.api.L.{*, given} import java.time.LocalDate -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext trait FormInput[V]: def render( diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala index e7294be..73ce0a0 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/form/Switch.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import zio.prelude.Validation -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext class Switch[V](using codec: FormCodec[V, Boolean], ctx: ComponentContext[_]) extends FormInput[V]: diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala deleted file mode 100644 index 2e3b7b8..0000000 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/laminar/LaminarExtensions.scala +++ /dev/null @@ -1,30 +0,0 @@ -package works.iterative.ui.components.tailwind -package laminar - -import com.raquo.laminar.api.L.{*, given} -import works.iterative.core.UserMessage -import io.laminext.syntax.core.* -import works.iterative.core.MessageId - -object LaminarExtensions: - extension (msg: UserMessage) - inline def asElement(using ctx: ComponentContext[_]): HtmlElement = - span(msg.asMod) - - inline def asOptionalElement(using - ctx: ComponentContext[_] - ): Option[HtmlElement] = - ctx.messages.get(msg).map(t => span(msgAttrs(msg.id, t))) - - inline def asString(using ctx: ComponentContext[_]): String = - ctx.messages(msg) - - inline def asMod(using ctx: ComponentContext[_]): Mod[HtmlElement] = - msgAttrs(msg.id, ctx.messages(msg)) - - private inline def msgAttrs(id: MessageId, text: String): HtmlMod = - nodeSeq(dataAttr("msgid")(id.toString()), text) - - given (using ComponentContext[_]): HtmlRenderable[UserMessage] with - def toHtml(msg: UserMessage): Modifier[HtmlElement] = - msg.asElement diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala index 5771e4d..2458be4 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/lists/feeds/SimpleWithIcons.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import java.time.Instant -import works.iterative.ui.components.tailwind.TimeUtils +import works.iterative.ui.TimeUtils import java.time.temporal.TemporalAccessor import java.text.DateFormat import com.raquo.laminar.codecs.StringAsIsCodec diff --git a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala index a47b211..a6f4022 100644 --- a/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala +++ b/ui/js/src/main/scala/works/iterative/ui/components/tailwind/navigation/Tabs.scala @@ -3,7 +3,7 @@ import com.raquo.laminar.api.L.{*, given} import works.iterative.core.MessageId -import works.iterative.ui.components.tailwind.ComponentContext +import works.iterative.ui.components.ComponentContext object Tabs: def apply[T](tabs: Seq[(MessageId, T)], selected: Signal[MessageId])( diff --git a/ui/js/src/main/scala/works/iterative/ui/scenarios/Scenario.scala b/ui/js/src/main/scala/works/iterative/ui/scenarios/Scenario.scala index e4e14c4..ff4203e 100644 --- a/ui/js/src/main/scala/works/iterative/ui/scenarios/Scenario.scala +++ b/ui/js/src/main/scala/works/iterative/ui/scenarios/Scenario.scala @@ -11,8 +11,7 @@ import scala.scalajs.js import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.components.tailwind.StyleGuide +import works.iterative.ui.components.ComponentContext object Scenario: type Id = String diff --git a/ui/js/src/main/scala/works/iterative/ui/scenarios/ScenarioMain.scala b/ui/js/src/main/scala/works/iterative/ui/scenarios/ScenarioMain.scala index 5265c5a..cc15f90 100644 --- a/ui/js/src/main/scala/works/iterative/ui/scenarios/ScenarioMain.scala +++ b/ui/js/src/main/scala/works/iterative/ui/scenarios/ScenarioMain.scala @@ -7,8 +7,7 @@ import scala.scalajs.js import works.iterative.ui.JsonMessageCatalogue import works.iterative.core.MessageCatalogue -import works.iterative.ui.components.tailwind.ComponentContext -import works.iterative.ui.components.tailwind.StyleGuide +import works.iterative.ui.components.ComponentContext import ui.components.tailwind.TailwindSupport import com.raquo.waypoint.* @@ -52,7 +51,6 @@ given ComponentContext[Unit] with val app: Unit = () val messages: MessageCatalogue = messageCatalogue - val style: StyleGuide = StyleGuide.default def container: HtmlElement = div( diff --git a/ui/shared/src/main/scala/works/iterative/ui/TimeUtils.scala b/ui/shared/src/main/scala/works/iterative/ui/TimeUtils.scala new file mode 100644 index 0000000..112786c --- /dev/null +++ b/ui/shared/src/main/scala/works/iterative/ui/TimeUtils.scala @@ -0,0 +1,44 @@ +package works.iterative.ui + +import java.time.format.DateTimeFormatter +import java.time.format.FormatStyle +import java.time.ZoneId +import java.time.Instant +import java.time.temporal.TemporalAccessor + +object TimeUtils: + val dateTimeFormat = + DateTimeFormatter + .ofLocalizedDateTime(FormatStyle.SHORT) + // TODO: locale + // .withLocale(Locale("cs", "CZ")) + .withZone(ZoneId.systemDefault()) + + val dateFormat = + DateTimeFormatter + .ofLocalizedDate(FormatStyle.SHORT) + // TODO: locale + // .withLocale(Locale("cs", "CZ")) + .withZone(ZoneId.systemDefault()) + + val htmlDateFormat = + DateTimeFormatter + .ofPattern("yyyy-MM-dd") + .withZone(ZoneId.systemDefault()) + + val htmlDateTimeFormat = + DateTimeFormatter + .ofPattern("yyyy-MM-dd HH:mm:ss") + .withZone(ZoneId.systemDefault()) + + def formatDateTime(i: TemporalAccessor): String = + dateTimeFormat.format(i) + + def formatDate(i: TemporalAccessor): String = + dateFormat.format(i) + + def formatHtmlDate(i: TemporalAccessor): String = + htmlDateFormat.format(i) + + def formatHtmlDateTime(i: TemporalAccessor): String = + htmlDateTimeFormat.format(i)