Skip to content

nafg/scalajs-facades

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

886 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CI

Scala.js Facades

Simple Facade — a macro-free library for zero-boilerplate scalajs-react facades, plus facades for several React components built with it.

Cross-published for Scala 2.13 and Scala 3.

Installation

resolvers += Resolver.jcenterRepo
// Facade for @material-ui/core version 4.12.3
libraryDependencies += "io.github.nafg.scalajs-facades" %%% "material-ui-core_4" % "0.16.0"
// Facade for @material-ui/lab version 4.0.0-alpha.60
libraryDependencies += "io.github.nafg.scalajs-facades" %%% "material-ui-lab_4" % "0.16.0"
// Facade for react-autocomplete version 1.8.1
libraryDependencies += "io.github.nafg.scalajs-facades" %%% "react-autocomplete_1" % "0.16.0"
// Facade for react-datepicker version 4.6.0
libraryDependencies += "io.github.nafg.scalajs-facades" %%% "react-datepicker_4" % "0.16.0"
// Facade for react-input-mask version 2.0.4
libraryDependencies += "io.github.nafg.scalajs-facades" %%% "react-input-mask_2" % "0.16.0"
// Facade for react-phone-number-input version 3.1.44
libraryDependencies += "io.github.nafg.scalajs-facades" %%% "react-phone-number-input_3" % "0.16.0"
// Facade for react-select version 5.2.1
libraryDependencies += "io.github.nafg.scalajs-facades" %%% "react-select_5" % "0.16.0"
// Facade for react-waypoint version 10.1.0
libraryDependencies += "io.github.nafg.scalajs-facades" %%% "react-waypoint_10" % "0.16.0"
// Facade for react-widgets version 5.5.1
libraryDependencies += "io.github.nafg.scalajs-facades" %%% "react-widgets_5" % "0.16.0"
// Library for react component facades that are simple to write and simple to use
libraryDependencies += "io.github.nafg.scalajs-facades" %%% "simplefacade" % "0.16.0"

Simple Facade

A better way to define and use Scala.js facades for React components.

Existing approaches suffer from some issues:

  1. Require dealing with low-level JavaScript interop types like js.UndefOr and union types
  2. Pass all props, necessitating everything to be optional
  3. Reliance on macros
  4. Tedious to define or to use

Simple Facade solves all of these. Props are set with a concise _.propName := value syntax, only the props you set are passed, no macros are involved, and defining a new facade is just a few lines.

Quick example

import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport
import japgolly.scalajs.react.vdom.VdomNode
import japgolly.scalajs.react.vdom.html_<^._
import io.github.nafg.simplefacade.{FacadeModule, PropTypes}
import io.github.nafg.simplefacade.Implicits.vdomNodeWriter

object Badge extends FacadeModule.NodeChildren.Simple {
  @JSImport("@mui/material/Badge", JSImport.Default)
  @js.native object raw extends js.Object

  class Props extends PropTypes.WithChildren[VdomNode] {
    val badgeContent = of[VdomNode]
    val children     = of[VdomNode]
    val color        = of[String]
    val variant      = of[String]
  }
  override def mkProps = new Props
}

// Usage — settings then children:
Badge(_.badgeContent := "3", _.color := "secondary")(
  <.span("mail")
)

Choosing the right base trait

Your component... Extend
Has no children FacadeModule.Simple
Has VdomNode children (varargs) FacadeModule.NodeChildren.Simple
Has VdomNode children (keyed array) FacadeModule.ArrayChildren.Simple
Has typed children of type C FacadeModule.ChildrenOf.Simple[C]
Has props parametric in a type A FacadeModuleP

Setting props

Props are set using the _.propName := value lambda syntax:

MyComponent(_.color := "red", _.size := 42)

Under the hood, _.color := "red" is a function Props => Setting — the _ is the Props instance and := creates a Setting that writes the value to the JS props object.

Available operators:

Operator Description
:= Set a prop to a value
:=? Set from an OptionNone passes undefined
.setAs[B] Set a prop using a different type's Writer
.setRaw Set a prop to a raw js.Any, bypassing the Writer

Boolean shorthand: A boolean prop can be set by just listing it (no := true needed):

MyComponent(_.isClearable, _.showYearDropdown)
// equivalent to:
MyComponent(_.isClearable := true, _.showYearDropdown := true)

Dynamic props for prop names not defined in the Props class:

MyComponent(_.dyn.`aria-label`("Close"), _.dyn.tabIndex(0))

Prop merging

When the same prop key is set more than once, Simple Facade merges values intelligently rather than using last-value-wins:

  • className — concatenated with a space ("btn" + " " + "btn-primary")
  • style — shallow-merged via Object.assign
  • on* event handlers — chained so both fire in sequence
  • children arrays — concatenated
  • Everything else — last value wins

This means you can layer settings without worrying about overwriting:

val defaults: Seq[MyComponent.Setting] = MyComponent.Settings(
  _.className := "base-style",
  _.variant := "outlined"
)
// Later, add more settings — className values are merged:
MyComponent(defaults, _.className := "extra-style")
// renders with className="base-style extra-style"

Writing a facade

Minimal facade (no children)

import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport
import io.github.nafg.simplefacade.{FacadeModule, PropTypes}

object ReactInputMask extends FacadeModule.Simple {
  @JSImport("react-input-mask", JSImport.Default)
  @js.native object raw extends js.Object

  class Props extends PropTypes {
    val mask           = of[String]
    val maskChar       = of[String]
    val alwaysShowMask = of[Boolean]
  }
  override def mkProps = new Props
}

// Usage:
ReactInputMask(_.mask := "99/99/9999", _.alwaysShowMask)

Facade with children

import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport
import japgolly.scalajs.react.vdom.VdomNode
import japgolly.scalajs.react.vdom.html_<^._
import io.github.nafg.simplefacade.{FacadeModule, PropTypes}
import io.github.nafg.simplefacade.Implicits.vdomNodeWriter

object MyCard extends FacadeModule.NodeChildren.Simple {
  @JSImport("my-card", JSImport.Default)
  @js.native object raw extends js.Object

  class Props extends PropTypes.WithChildren[VdomNode] {
    val children = of[VdomNode]
    val elevated = of[Boolean]
    val title    = of[String]
  }
  override def mkProps = new Props
}

// Usage — settings, then children:
MyCard(_.title := "Hello", _.elevated)(
  <.p("Card content"),
  <.p("More content")
)

// No children — the component renders as a VdomElement directly:
val element: VdomElement = MyCard(_.title := "Empty card")

Convenience apply methods

For frequently-used props, you can add convenience overloads:

import java.time.LocalDate
import japgolly.scalajs.react.Callback
import japgolly.scalajs.react.extra.StateSnapshot
import io.github.nafg.simplefacade.{FacadeModule, Factory}

object DatePicker extends FacadeModule {
  // ... raw, Props, mkProps as usual ...

  def apply(
    selected: Option[LocalDate]
  )(onChange: Option[LocalDate] => Callback): Factory[Props] =
    factory(_.selected := selected, _.onChange := onChange)

  def apply(snapshot: StateSnapshot[Option[LocalDate]]): Factory[Props] =
    apply(snapshot.value)(snapshot.setState)
}

Generic (parametric) facades

For components generic over a type (e.g., a select component):

import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport
import japgolly.scalajs.react.Callback
import io.github.nafg.simplefacade.{FacadeModuleP, HasOpaqueReaderWriter, PropTypes}
import io.github.nafg.simplefacade.Implicits.callbackToWriter

object Select extends FacadeModuleP {
  @JSImport("react-select", JSImport.Default)
  @js.native object raw extends js.Object

  class Props[A] extends PropTypes with HasOpaqueReaderWriter[A] {
    val options  = of[Seq[A]]
    val onChange = of[A => Callback]
  }
  override def mkProps[A] = new Props[A]
}

HasOpaqueReaderWriter[A] provides Writer/Reader instances that pass values through as-is — use it when the JavaScript side treats values as opaque (e.g., option objects in a select).

Facade code generation using react-docgen

The Material-UI facades are generated by extracting components and props from react-docgen.

About

Facades for scalajs-react

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 7

Languages