├── .circleci └── config.yml ├── .gitignore ├── .sbtopts ├── .scalafmt.conf ├── LICENSE ├── antd ├── src │ └── main │ │ ├── js │ │ ├── index.css │ │ └── index.html │ │ └── scala │ │ └── demo │ │ ├── App.scala │ │ ├── CoordinatedDemo.scala │ │ └── Main.scala └── yarn.lock ├── build.sbt ├── custom.webpack.config.js ├── docs ├── antd │ ├── antd-opt-bundle.js │ └── index.html ├── downshift │ ├── downshift-opt-bundle.js │ └── index.html ├── material-ui │ ├── 8cb96001ce8b133ffa0460ec825923b0.svg │ ├── a0185b043dee68a72aebffdcc939e21e.svg │ ├── index.html │ └── material-ui-opt-bundle.js ├── nivo │ ├── index.html │ └── nivo-opt-bundle.js ├── office-ui-fabric-react │ ├── index.html │ └── office-ui-fabric-react-opt-bundle.js ├── react-big-calendar │ ├── index.html │ └── react-big-calendar-opt-bundle.js ├── react-dnd │ ├── index.html │ └── react-dnd-opt-bundle.js ├── react-i18n │ ├── index.html │ └── react-i18n-opt-bundle.js ├── react-leaflet │ ├── 2273e3d8ad9264b7daa5bdbf8e6b47f8.png │ ├── 4f0283c6ce28e888000e978e537a6a56.png │ ├── a6137456ed160d7606981aa57c559898.png │ ├── index.html │ └── react-leaflet-opt-bundle.js ├── react-markdown │ ├── docs │ │ └── README.md │ ├── index.html │ └── react-markdown-opt-bundle.js ├── react-mobx │ ├── index.html │ └── react-mobx-opt-bundle.js ├── react-redux │ ├── index.html │ └── react-redux-opt-bundle.js ├── react-router-dom │ ├── index.html │ └── react-router-dom-opt-bundle.js ├── react-slick │ ├── index.html │ └── react-slick-opt-bundle.js ├── react-window │ ├── index.html │ └── react-window-opt-bundle.js ├── semantic-ui-react-kitchensink │ ├── index.html │ └── semantic-ui-react-kitchensink-opt-bundle.js └── storybook-react │ ├── favicon.ico │ ├── iframe.html │ ├── index.html │ ├── main.25f83ce417c194d85b6d.bundle.js │ ├── main.311a062f112ecb6ac41a.bundle.js │ ├── main.311a062f112ecb6ac41a.bundle.js.map │ ├── runtime~main.286b69a873d49f2f199c.bundle.js │ ├── runtime~main.311a062f112ecb6ac41a.bundle.js │ ├── runtime~main.311a062f112ecb6ac41a.bundle.js.map │ ├── sb_dll │ ├── storybook_ui-manifest.json │ ├── storybook_ui_dll.LICENCE │ └── storybook_ui_dll.js │ ├── vendors~main.311a062f112ecb6ac41a.bundle.js │ ├── vendors~main.311a062f112ecb6ac41a.bundle.js.LICENSE.txt │ ├── vendors~main.311a062f112ecb6ac41a.bundle.js.map │ └── vendors~main.eee964e324deca5095da.bundle.js ├── downshift ├── src │ └── main │ │ ├── js │ │ └── index.html │ │ └── scala │ │ └── demo │ │ └── Demo.scala └── yarn.lock ├── material-ui ├── src │ └── main │ │ ├── js │ │ ├── google.svg │ │ ├── index.html │ │ └── logo.svg │ │ └── scala │ │ └── demo │ │ ├── Demo.scala │ │ ├── StyleBuilder.scala │ │ ├── album │ │ └── Album.scala │ │ ├── button │ │ └── Button.scala │ │ ├── components │ │ └── AppTheme.scala │ │ ├── customization │ │ ├── DarkTheme.scala │ │ ├── Palette.scala │ │ └── WithTheme.scala │ │ ├── dashboard │ │ ├── Dashboard.scala │ │ ├── ListItems.scala │ │ ├── SimpleLineChart.scala │ │ └── SimpleTable.scala │ │ ├── login │ │ ├── Login.scala │ │ └── Styles.scala │ │ └── signin │ │ └── SignIn.scala └── yarn.lock ├── nivo ├── src │ └── main │ │ ├── js │ │ ├── data.json │ │ └── index.html │ │ └── scala │ │ └── demo │ │ ├── App.scala │ │ └── Main.scala └── yarn.lock ├── office-ui-fabric-react ├── src │ └── main │ │ ├── js │ │ └── index.html │ │ └── scala │ │ └── demo │ │ └── Demo.scala └── yarn.lock ├── project ├── ScalacOptions.scala ├── build.properties ├── plugins.sbt └── scalafmt.sbt ├── react-big-calendar ├── src │ └── main │ │ ├── js │ │ └── index.html │ │ └── scala │ │ └── demo │ │ └── Main.scala └── yarn.lock ├── react-dnd ├── src │ └── main │ │ ├── js │ │ └── index.html │ │ └── scala │ │ └── demo │ │ ├── App.scala │ │ └── Main.scala └── yarn.lock ├── react-i18n ├── src │ └── main │ │ ├── js │ │ └── index.html │ │ └── scala │ │ └── demo │ │ ├── App.scala │ │ └── I18n.scala └── yarn.lock ├── react-leaflet ├── src │ └── main │ │ ├── js │ │ └── index.html │ │ └── scala │ │ └── demo │ │ ├── App.scala │ │ └── Main.scala └── yarn.lock ├── react-markdown ├── src │ └── main │ │ ├── js │ │ ├── docs │ │ │ └── README.md │ │ └── index.html │ │ └── scala │ │ └── demo │ │ ├── DocPage.scala │ │ └── Main.scala └── yarn.lock ├── react-mobx ├── src │ └── main │ │ ├── js │ │ └── index.html │ │ └── scala │ │ └── demo │ │ ├── Main.scala │ │ ├── ObserverComponent.scala │ │ ├── PeopleStore.scala │ │ ├── TodoList.scala │ │ ├── TodoStore.scala │ │ └── TodoView.scala └── yarn.lock ├── react-native ├── App.js ├── app.json ├── assets │ ├── icon.png │ └── splash.png ├── babel.config.js ├── fastopt-noparse.js ├── metro.config.js ├── package.json ├── src │ └── main │ │ └── scala │ │ └── hello │ │ └── world │ │ ├── Antd.scala │ │ ├── App.scala │ │ ├── Home.scala │ │ ├── LoadFonts.scala │ │ ├── Main.scala │ │ ├── ReactRouter.scala │ │ ├── Styles.scala │ │ └── Topic.scala └── yarn.lock ├── react-redux ├── src │ └── main │ │ ├── js │ │ └── index.html │ │ └── scala │ │ └── demo │ │ ├── Main.scala │ │ ├── advanced │ │ ├── Expense.scala │ │ ├── ExpenseAction.scala │ │ ├── ExpenseContainer.scala │ │ ├── ExpenseReducer.scala │ │ └── ExpenseState.scala │ │ └── basic │ │ ├── CakeAction.scala │ │ ├── CakeContainer.scala │ │ └── CakeReducer.scala └── yarn.lock ├── react-router-dom ├── src │ └── main │ │ ├── js │ │ └── index.html │ │ └── scala │ │ └── demo │ │ └── App.scala └── yarn.lock ├── react-slick ├── src │ └── main │ │ ├── js │ │ └── index.html │ │ └── scala │ │ └── demo │ │ └── Main.scala └── yarn.lock ├── react-window ├── src │ └── main │ │ ├── js │ │ └── index.html │ │ └── scala │ │ └── demo │ │ └── Main.scala └── yarn.lock ├── readme.md ├── semantic-ui-react-kitchensink ├── readme.md ├── src │ └── main │ │ ├── js │ │ └── index.html │ │ └── scala │ │ └── demo │ │ └── App.scala └── yarn.lock └── storybook-react ├── .storybook-dist ├── config.js └── webpack.config.js ├── .storybook ├── config.js └── webpack.config.js ├── package.json ├── src └── main │ └── scala │ └── demo │ └── Demo.scala └── yarn.lock /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Scala CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/sample-config/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/openjdk:8-jdk-node 11 | # Specify service dependencies here if necessary 12 | # CircleCI maintains a library of pre-built images 13 | # documented at https://circleci.com/docs/2.0/circleci-images/ 14 | # - image: circleci/postgres:9.4 15 | 16 | working_directory: ~/repo 17 | 18 | environment: 19 | # Customize the JVM maximum heap limit 20 | JVM_OPTS: -Xmx3200m 21 | TERM: dumb 22 | 23 | steps: 24 | - checkout 25 | 26 | # Download and cache dependencies 27 | - restore_cache: 28 | keys: 29 | - v2-dependencies-{{ checksum "build.sbt" }} 30 | # fallback to using the latest cache if no exact match is found 31 | - v2-dependencies- 32 | 33 | # ideally we would bundle and run things, but the diversity of the demos makes it a bit difficult. 34 | # might revisit this later 35 | - run: cat /dev/null | sbt stPublishCache compile 36 | 37 | - save_cache: 38 | paths: 39 | - ~/.sbt 40 | - ~/.ivy2/cache 41 | - ~/.ivy2/local 42 | - ~/.cache/scalablytyped 43 | key: v2-dependencies--{{ checksum "build.sbt" }} 44 | 45 | # # run scalafmt! 46 | # - run: cat /dev/null | sbt scalafmtCheck 47 | 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | target/ 3 | .bloop/ 4 | node_modules/ 5 | .gradle/ 6 | build/ 7 | local.properties 8 | .expo/ 9 | .metals/ 10 | project/metals.sbt 11 | out/ 12 | .bsp/ -------------------------------------------------------------------------------- /.sbtopts: -------------------------------------------------------------------------------- 1 | -J-XX:+UseG1GC 2 | -J-Xmx4g 3 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version=3.0.0-RC5 2 | runner.dialect = scala3 3 | style = default 4 | danglingParentheses.preset = true 5 | align { 6 | tokens.add = [":", "="] 7 | } 8 | maxColumn = 120 9 | rewrite { 10 | rules = [SortImports, RedundantBraces, RedundantParens, PreferCurlyFors] 11 | scala3 = { 12 | convertToNewSyntax = true, 13 | removeOptionalBraces: yes, 14 | insertEndMarkerMinLines = 10 15 | } 16 | } 17 | project.excludeFilters = [ 18 | node_modules, 19 | target 20 | ] 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ScalablyTyped 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /antd/src/main/js/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | 7 | .App-title, .App-intro { 8 | text-align: center; 9 | } 10 | 11 | div.block { 12 | color: #fff; 13 | margin-top: 8px; 14 | margin-bottom: 8px; 15 | padding-top: 16px; 16 | padding-bottom: 16px; 17 | text-align: center; 18 | } 19 | 20 | div.blue1 { 21 | background: rgba(0, 160, 233, 0.7); 22 | } 23 | 24 | div.blue2 { 25 | background: #00a0e9; 26 | } 27 | 28 | section { 29 | margin-top: 20px; 30 | } 31 | -------------------------------------------------------------------------------- /antd/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Antd-scalajs-react demo 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /antd/src/main/scala/demo/CoordinatedDemo.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.vdom.html_<^.* 4 | import japgolly.scalajs.react.{Callback, ScalaFnComponent} 5 | import org.scalablytyped.runtime.StringDictionary 6 | import typings.antd.antdStrings 7 | import typings.antd.components.* 8 | import typings.antd.libFormFormMod.useForm 9 | import typings.antd.libGridColMod.ColProps 10 | import typings.rcFieldForm.esInterfaceMod.BaseRule 11 | import typings.std.global.console 12 | 13 | object CoordinatedDemo: 14 | val component = ScalaFnComponent[String] { noteTitle => 15 | val form = useForm().head 16 | Form 17 | .form(form) 18 | .labelCol(ColProps().setSpan(5)) 19 | .wrapperCol(ColProps().setSpan(12)) 20 | .onFinish(store => Callback(console.log("Received values of form: ", store)))( 21 | Form.Item 22 | .label(noteTitle) 23 | .name("note") 24 | .rulesVarargs(BaseRule().setRequired(true).setMessage("Please input your note!"))( 25 | Input() 26 | ), 27 | Form.Item 28 | .label("Gender") 29 | .name("gender") 30 | .rulesVarargs(BaseRule().setRequired(true).setMessage("Please select your gender!'"))( 31 | Select[String]() 32 | .placeholder("Select a option and change input text above") 33 | .onChange { (value, _) => 34 | Callback( 35 | form.setFieldsValue( 36 | StringDictionary( 37 | "gender" -> value, 38 | "note" -> s"Hi, ${if value == "male" then "man" else "lady"}!" 39 | ) 40 | ) 41 | ) 42 | }( 43 | Select.Option(value = "male")("Male"), 44 | Select.Option(value = "female")("Female") 45 | ) 46 | ), 47 | Form.Item.wrapperCol(ColProps().setSpan(12).setOffset(5))( 48 | Button.`type`(antdStrings.primary).htmlType(antdStrings.submit)("Submit") 49 | ) 50 | ) 51 | } 52 | end CoordinatedDemo 53 | -------------------------------------------------------------------------------- /antd/src/main/scala/demo/Main.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import org.scalajs.dom 4 | 5 | import scala.scalajs.js 6 | import scala.scalajs.js.annotation.JSImport 7 | 8 | @JSImport("./index.css", JSImport.Namespace) 9 | @js.native 10 | object IndexCSS extends js.Object 11 | 12 | @main 13 | def main: Unit = 14 | IndexCSS 15 | App.component().renderIntoDOM(dom.document.getElementById("container")) 16 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import java.nio.file.Files 2 | import java.nio.file.StandardCopyOption.REPLACE_EXISTING 3 | import scala.sys.process.Process 4 | 5 | Global / excludeLintKeys += webpackExtraArgs 6 | 7 | Global / onLoad := { 8 | println("""* 9 | |* Welcome to ScalablyTyped demos! 10 | |* 11 | |* These demos demonstrate how to use third party react components with Scalajs-react. 12 | |* 13 | |* For documentation see https://scalablytyped.org . 14 | |* 15 | |* Note that the first time you import/compile the projects it'll take a while for the dependencies to build. 16 | |* 17 | |* If you import this the first time in a memory-constrained context like an IDE import, it'll take MUCH longer. 18 | |*""".stripMargin) 19 | (Global / onLoad).value 20 | } 21 | 22 | 23 | Global / stRemoteCache := RemoteCache.S3Aws(bucket = "scalablytyped-demos", region = "eu-central-1", prefix = Some("st-cache")) 24 | 25 | // Uncomment if you want to remove debug output 26 | //Global / stQuiet := true 27 | 28 | /** 29 | * Custom task to start demo with webpack-dev-server, use as `/start`. 30 | * Just `start` also works, and starts all frontend demos 31 | * 32 | * After that, the incantation is this to watch and compile on change: 33 | * `~/fastOptJS::webpack` 34 | */ 35 | lazy val start = TaskKey[Unit]("start") 36 | 37 | /** Say just `dist` or `/dist` to make a production bundle in 38 | * `docs` for github publishing 39 | */ 40 | lazy val dist = TaskKey[File]("dist") 41 | 42 | lazy val baseSettings: Project => Project = 43 | _.enablePlugins(ScalaJSPlugin) 44 | .settings( 45 | version := "0.1-SNAPSHOT", 46 | scalaVersion := "3.3.0", 47 | scalacOptions ++= ScalacOptions.flags, 48 | scalaJSUseMainModuleInitializer := true, 49 | /* disabled because it somehow triggers many warnings */ 50 | scalaJSLinkerConfig := scalaJSLinkerConfig.value.withSourceMap(false) 51 | ) 52 | 53 | lazy val `react-mobx` = 54 | project 55 | .enablePlugins(ScalablyTypedConverterPlugin) 56 | .configure(baseSettings, browserProject, reactNpmDeps, bundlerSettings) 57 | .settings( 58 | useYarn := true, 59 | webpackDevServerPort := 8001, 60 | stFlavour := Flavour.ScalajsReact, 61 | Compile / npmDependencies ++= Seq( 62 | "mobx" -> "5.15.4", 63 | "mobx-react" -> "6.2.2" 64 | ) 65 | ) 66 | 67 | lazy val `react-slick` = 68 | project 69 | .enablePlugins(ScalablyTypedConverterPlugin) 70 | .configure(baseSettings, browserProject, reactNpmDeps, bundlerSettings) 71 | .settings( 72 | useYarn := true, 73 | webpackDevServerPort := 8002, 74 | stFlavour := Flavour.ScalajsReact, 75 | Compile / npmDependencies ++= Seq( 76 | "react-slick" -> "0.23", 77 | "@types/react-slick" -> "0.23.4" 78 | ) 79 | ) 80 | 81 | lazy val `react-big-calendar` = 82 | project 83 | .enablePlugins(ScalablyTypedConverterPlugin) 84 | .configure(baseSettings, browserProject, withCssLoading, reactNpmDeps, bundlerSettings) 85 | .settings( 86 | useYarn := true, 87 | webpackDevServerPort := 8003, 88 | stFlavour := Flavour.ScalajsReact, 89 | Compile / npmDependencies ++= Seq( 90 | "moment" -> "2.24.0", 91 | "react-big-calendar" -> "0.24.4", 92 | "@types/react-big-calendar" -> "0.24.0" 93 | ) 94 | ) 95 | 96 | lazy val `semantic-ui-react-kitchensink` = project 97 | .enablePlugins(ScalablyTypedConverterPlugin) 98 | .configure(baseSettings, browserProject, reactNpmDeps, bundlerSettings) 99 | .settings( 100 | useYarn := true, 101 | webpackDevServerPort := 8004, 102 | stFlavour := Flavour.ScalajsReact, 103 | stReactEnableTreeShaking := Selection.All, 104 | Compile / npmDependencies ++= Seq( 105 | "semantic-ui-react" -> "0.88.2" 106 | ) 107 | ) 108 | 109 | ///** Note: This can't use scalajs-bundler (at least I don't know how), 110 | // * so we run yarn ourselves with an external package.json. 111 | // */ 112 | //lazy val `storybook-react` = project 113 | // .enablePlugins(ScalablyTypedConverterExternalNpmPlugin) 114 | // .configure(baseSettings) 115 | // .settings( 116 | // scalaJSLinkerConfig := scalaJSLinkerConfig.value.withModuleKind(ModuleKind.CommonJSModule), 117 | // /* ScalablyTypedConverterExternalNpmPlugin requires that we define how to install node dependencies and where they are */ 118 | // externalNpm := { 119 | // if (scala.util.Properties.isWin) Process("yarn", baseDirectory.value).run() 120 | // else Process("bash -ci 'yarn'", baseDirectory.value).run() 121 | // baseDirectory.value 122 | // }, 123 | // stFlavour := Flavour.ScalajsReact, 124 | // /** This is not suitable for development, but effective for demo. 125 | // * Run `yarn storybook` commands yourself, and run `~storybook-react/fastOptJS` from sbt 126 | // */ 127 | // start := { 128 | // (Compile / fastOptJS).value 129 | // Process("yarn storybook", baseDirectory.value).run() 130 | // }, 131 | // dist := { 132 | // val distFolder = (ThisBuild / baseDirectory).value / "docs" / moduleName.value 133 | // (Compile / fullOptJS).value 134 | // if (scala.util.Properties.isWin) Process("yarn dist", baseDirectory.value).run() 135 | // else Process("bash -ci 'yarn dist'", baseDirectory.value).run() 136 | // distFolder 137 | // } 138 | // ) 139 | 140 | lazy val antd = 141 | project 142 | .enablePlugins(ScalablyTypedConverterPlugin) 143 | .configure(baseSettings, browserProject, withCssLoading, reactNpmDeps, bundlerSettings) 144 | .settings( 145 | useYarn := true, 146 | webpackDevServerPort := 8006, 147 | stFlavour := Flavour.ScalajsReact, 148 | Compile / npmDependencies ++= Seq("antd" -> "4.5.1") 149 | ) 150 | 151 | lazy val `react-router-dom` = 152 | project 153 | .enablePlugins(ScalablyTypedConverterPlugin) 154 | .configure(baseSettings, browserProject, reactNpmDeps, bundlerSettings) 155 | .settings( 156 | useYarn := true, 157 | webpackDevServerPort := 8007, 158 | stFlavour := Flavour.ScalajsReact, 159 | Compile / npmDependencies ++= Seq( 160 | "react-router-dom" -> "5.1.2", 161 | "@types/react-router-dom" -> "5.1.2" // note 5.1.4 did weird things to the Link component 162 | ) 163 | ) 164 | 165 | lazy val `material-ui` = 166 | project 167 | .enablePlugins(ScalablyTypedConverterPlugin) 168 | .configure(baseSettings, browserProject, reactNpmDeps, withCssLoading, bundlerSettings) 169 | .settings( 170 | useYarn := true, 171 | webpackDevServerPort := 8008, 172 | stFlavour := Flavour.ScalajsReact, 173 | stReactEnableTreeShaking := Selection.All, 174 | Compile / npmDependencies ++= Seq( 175 | "@material-ui/core" -> "3.9.4", // note: version 4 is not supported yet 176 | "@material-ui/styles" -> "3.0.0-alpha.10", // note: version 4 is not supported yet 177 | "@material-ui/icons" -> "3.0.2", 178 | "recharts" -> "1.8.5", 179 | "@types/recharts" -> "1.8.10", 180 | "@types/classnames" -> "2.2.10", 181 | "react-router-dom" -> "5.1.2", 182 | "@types/react-router-dom" -> "5.1.2" 183 | ) 184 | ) 185 | 186 | lazy val `react-leaflet` = project 187 | .enablePlugins(ScalablyTypedConverterPlugin) 188 | .configure(baseSettings, browserProject, reactNpmDeps, withCssLoading, bundlerSettings) 189 | .settings( 190 | useYarn := true, 191 | webpackDevServerPort := 8009, 192 | stFlavour := Flavour.ScalajsReact, 193 | Compile / npmDependencies ++= Seq( 194 | "react-leaflet" -> "2.6.3", 195 | "@types/react-leaflet" -> "2.5.1", 196 | "leaflet" -> "1.6.0" 197 | ) 198 | ) 199 | 200 | lazy val `office-ui-fabric-react` = project 201 | .enablePlugins(ScalablyTypedConverterPlugin) 202 | .configure(baseSettings, browserProject, reactNpmDeps, bundlerSettings) 203 | .settings( 204 | useYarn := true, 205 | webpackDevServerPort := 8010, 206 | stFlavour := Flavour.ScalajsReact, 207 | stReactEnableTreeShaking := Selection.All, 208 | Compile / npmDependencies ++= Seq( 209 | "office-ui-fabric-react" -> "7.107.1" 210 | ) 211 | ) 212 | 213 | lazy val `react-dnd` = project 214 | .enablePlugins(ScalablyTypedConverterPlugin) 215 | .configure(baseSettings, browserProject, reactNpmDeps, bundlerSettings) 216 | .settings( 217 | useYarn := true, 218 | webpackDevServerPort := 8011, 219 | stFlavour := Flavour.ScalajsReact, 220 | Compile / npmDependencies ++= Seq( 221 | "react-dnd" -> "11.1.3", 222 | "react-dnd-html5-backend" -> "11.1.3" 223 | ) 224 | ) 225 | 226 | lazy val `react-i18n` = project 227 | .enablePlugins(ScalablyTypedConverterPlugin) 228 | .configure(baseSettings, browserProject, reactNpmDeps, bundlerSettings) 229 | .settings( 230 | useYarn := true, 231 | webpackDevServerPort := 8012, 232 | stFlavour := Flavour.ScalajsReact, 233 | Compile / npmDependencies ++= Seq( 234 | "i18next" -> "19.5.2", 235 | "i18next-browser-languagedetector" -> "5.0.0", 236 | "react-i18next" -> "11.7.0" 237 | ) 238 | ) 239 | 240 | lazy val `nivo` = project 241 | .enablePlugins(ScalablyTypedConverterPlugin) 242 | .configure(baseSettings, browserProject, reactNpmDeps, bundlerSettings) 243 | .settings( 244 | useYarn := true, 245 | webpackDevServerPort := 8013, 246 | stFlavour := Flavour.ScalajsReact, 247 | Compile / npmDependencies ++= Seq( 248 | "@nivo/line" -> "0.62.0" 249 | ) 250 | ) 251 | 252 | lazy val downshift = project 253 | .enablePlugins(ScalablyTypedConverterPlugin) 254 | .configure(baseSettings, browserProject, reactNpmDeps, bundlerSettings) 255 | .settings( 256 | useYarn := true, 257 | webpackDevServerPort := 8014, 258 | stFlavour := Flavour.ScalajsReact, 259 | Compile / npmDependencies ++= Seq( 260 | "downshift" -> "6.0.5" 261 | ) 262 | ) 263 | 264 | lazy val `react-redux` = project 265 | .enablePlugins(ScalablyTypedConverterPlugin) 266 | .configure(baseSettings, browserProject, reactNpmDeps, bundlerSettings) 267 | .settings( 268 | useYarn := true, 269 | webpackDevServerPort := 8015, 270 | stFlavour := Flavour.ScalajsReact, 271 | stReactEnableTreeShaking := Selection.All, 272 | Compile / npmDependencies ++= Seq( 273 | "react-redux" -> "7.1", 274 | "redux-devtools-extension" -> "2.13.8", 275 | "@types/react-redux" -> "7.1.5" 276 | ), 277 | libraryDependencies += ("org.scala-js" %%% "scalajs-java-securerandom" % "1.0.0").cross(CrossVersion.for3Use2_13) 278 | ) 279 | 280 | lazy val `react-window` = project 281 | .enablePlugins(ScalablyTypedConverterPlugin) 282 | .configure(baseSettings, browserProject, reactNpmDeps, bundlerSettings) 283 | .settings( 284 | useYarn := true, 285 | webpackDevServerPort := 8016, 286 | stFlavour := Flavour.ScalajsReact, 287 | stReactEnableTreeShaking := Selection.All, 288 | Compile / npmDependencies ++= Seq( 289 | "react-window" -> "1.8.6", 290 | "@types/react-window" -> "1.8.2", 291 | "react-virtualized-auto-sizer" -> "1.0.2", // as recommended by react-window 292 | "@types/react-virtualized-auto-sizer" -> "1.0.0", 293 | ) 294 | ) 295 | 296 | lazy val `react-markdown` = project 297 | .enablePlugins(ScalablyTypedConverterPlugin) 298 | .configure(baseSettings, withCssLoading, browserProject, reactNpmDeps, bundlerSettings) 299 | .settings( 300 | useYarn := true, 301 | webpackDevServerPort := 8017, 302 | stFlavour := Flavour.ScalajsReact, 303 | Compile / npmDependencies ++= Seq( 304 | "react-markdown"-> "^5.0.3", 305 | "react-syntax-highlighter"-> "^15.4.3", 306 | "@types/react-syntax-highlighter"-> "^13.5.0" 307 | ) 308 | ) 309 | 310 | /** Note: This can't use scalajs-bundler (at least I don't know how), 311 | * so we run yarn ourselves with an external package.json. 312 | */ 313 | lazy val `react-native` = project 314 | .enablePlugins(ScalablyTypedConverterExternalNpmPlugin) 315 | .configure(baseSettings) 316 | .settings( 317 | scalaJSLinkerConfig := scalaJSLinkerConfig.value.withModuleKind(ModuleKind.CommonJSModule), 318 | scalaJSUseMainModuleInitializer := false, 319 | /* ScalablyTypedConverterExternalNpmPlugin requires that we define how to install node dependencies and where they are */ 320 | externalNpm := { 321 | if (scala.util.Properties.isWin) Process("yarn", baseDirectory.value).run() 322 | else Process("bash -ci 'yarn'", baseDirectory.value).run() 323 | baseDirectory.value 324 | }, 325 | stFlavour := Flavour.ScalajsReact, 326 | stStdlib := List("es5"), 327 | run := { 328 | (Compile / fastOptJS).value 329 | Process("expo start", baseDirectory.value).! 330 | } 331 | ) 332 | 333 | // specify versions for all of reacts dependencies to compile less since we have many demos here 334 | lazy val reactNpmDeps: Project => Project = 335 | _.settings( 336 | stTypescriptVersion := "3.9.3", 337 | Compile / npmDependencies ++= Seq( 338 | "react" -> "16.13.1", 339 | "react-dom" -> "16.13.1", 340 | "@types/react" -> "16.9.42", 341 | "@types/react-dom" -> "16.9.8", 342 | "csstype" -> "2.6.11", 343 | "@types/prop-types" -> "15.7.3" 344 | ) 345 | ) 346 | 347 | lazy val bundlerSettings: Project => Project = 348 | _.settings( 349 | webpackCliVersion := "4.10.0", 350 | webpack / version := "5.88.2", 351 | Compile / fastOptJS / webpackExtraArgs += "--mode=development", 352 | Compile / fullOptJS / webpackExtraArgs += "--mode=production", 353 | Compile / fastOptJS / webpackDevServerExtraArgs += "--mode=development", 354 | Compile / fullOptJS / webpackDevServerExtraArgs += "--mode=production" 355 | ) 356 | 357 | lazy val withCssLoading: Project => Project = 358 | _.settings( 359 | /* custom webpack file to include css */ 360 | webpackConfigFile := Some((ThisBuild / baseDirectory).value / "custom.webpack.config.js"), 361 | Compile / npmDevDependencies ++= Seq( 362 | "webpack-merge" -> "5.9.0", 363 | "css-loader" -> "6.8.1", 364 | "style-loader" -> "3.3.3", 365 | "file-loader" -> "6.2.0", 366 | "url-loader" -> "4.1.1" 367 | ) 368 | ) 369 | 370 | /** 371 | * Implement the `start` and `dist` tasks defined above. 372 | * Most of this is really just to copy the index.html file around. 373 | */ 374 | lazy val browserProject: Project => Project = 375 | _.settings( 376 | start := { 377 | (Compile / fastOptJS / startWebpackDevServer).value 378 | }, 379 | dist := { 380 | val artifacts = (Compile / fullOptJS / webpack).value 381 | val artifactFolder = (Compile / fullOptJS / crossTarget).value 382 | val distFolder = (ThisBuild / baseDirectory).value / "docs" / moduleName.value 383 | 384 | distFolder.mkdirs() 385 | artifacts.foreach { artifact => 386 | val target = artifact.data.relativeTo(artifactFolder) match { 387 | case None => distFolder / artifact.data.name 388 | case Some(relFile) => distFolder / relFile.toString 389 | } 390 | 391 | Files.copy(artifact.data.toPath, target.toPath, REPLACE_EXISTING) 392 | } 393 | 394 | val indexFrom = baseDirectory.value / "src/main/js/index.html" 395 | val indexTo = distFolder / "index.html" 396 | 397 | val indexPatchedContent = { 398 | import collection.JavaConverters._ 399 | Files 400 | .readAllLines(indexFrom.toPath, IO.utf8) 401 | .asScala 402 | .map(_.replaceAllLiterally("-fastopt-", "-opt-")) 403 | .mkString("\n") 404 | } 405 | 406 | Files.write(indexTo.toPath, indexPatchedContent.getBytes(IO.utf8)) 407 | distFolder 408 | } 409 | ) 410 | -------------------------------------------------------------------------------- /custom.webpack.config.js: -------------------------------------------------------------------------------- 1 | var merge = require('webpack-merge').merge; 2 | var generated = require('./scalajs.webpack.config'); 3 | 4 | var local = { 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.css$/, 9 | use: ['style-loader', 'css-loader'] 10 | }, 11 | { 12 | test: /\.(ttf|eot|woff|png|glb|svg|md)$/, 13 | use: 'file-loader' 14 | }, 15 | { 16 | test: /\.(eot)$/, 17 | use: 'url-loader' 18 | } 19 | ] 20 | } 21 | }; 22 | 23 | module.exports = merge(generated, local); 24 | -------------------------------------------------------------------------------- /docs/antd/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Antd-scalajs-react demo 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/downshift/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Downshift demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/material-ui/8cb96001ce8b133ffa0460ec825923b0.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 11 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /docs/material-ui/a0185b043dee68a72aebffdcc939e21e.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logo_white 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/material-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Material-ui demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/nivo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Nivo demo 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /docs/office-ui-fabric-react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | office-ui-fabric-react demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/react-big-calendar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-big-calendar demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/react-dnd/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-dnd demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/react-i18n/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React i18n demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/react-leaflet/2273e3d8ad9264b7daa5bdbf8e6b47f8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScalablyTyped/ScalaJsReactDemos/ee2c2dfad18ffe29f7ad092a20ee5aa4c0eca841/docs/react-leaflet/2273e3d8ad9264b7daa5bdbf8e6b47f8.png -------------------------------------------------------------------------------- /docs/react-leaflet/4f0283c6ce28e888000e978e537a6a56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScalablyTyped/ScalaJsReactDemos/ee2c2dfad18ffe29f7ad092a20ee5aa4c0eca841/docs/react-leaflet/4f0283c6ce28e888000e978e537a6a56.png -------------------------------------------------------------------------------- /docs/react-leaflet/a6137456ed160d7606981aa57c559898.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScalablyTyped/ScalaJsReactDemos/ee2c2dfad18ffe29f7ad092a20ee5aa4c0eca841/docs/react-leaflet/a6137456ed160d7606981aa57c559898.png -------------------------------------------------------------------------------- /docs/react-leaflet/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-leaflet demo 6 | 7 | 8 |
9 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/react-markdown/docs/README.md: -------------------------------------------------------------------------------- 1 | # Working with objects 2 | 3 | Javascript is remarkably flexible. When we integrate with arbitrary Javascript code in Scala.js, we need a very flexible 4 | encoding to tag along. The encoding chosen for ScalablyTyped is the result of years of experimentation, and has 5 | a much more dynamic feeling than what you may be used to. 6 | 7 | Let's start with an example of a type definition we want to use: 8 | 9 | ```scala 10 | @js.native 11 | trait Point extends StObject { 12 | 13 | var x: Double = js.native 14 | 15 | var y: Double = js.native 16 | } 17 | object Point { 18 | 19 | @scala.inline 20 | def apply(x: Double, y: Double): Point = { 21 | val __obj = js.Dynamic.literal(x = x.asInstanceOf[js.Any], y = y.asInstanceOf[js.Any]) 22 | __obj.asInstanceOf[Point] 23 | } 24 | 25 | @scala.inline 26 | implicit class PointMutableBuilder[Self <: Point] (val x: Self) extends AnyVal { 27 | 28 | @scala.inline 29 | def setX(value: Double): Self = StObject.set(x, "x", value.asInstanceOf[js.Any]) 30 | 31 | @scala.inline 32 | def setY(value: Double): Self = StObject.set(x, "y", value.asInstanceOf[js.Any]) 33 | } 34 | } 35 | ``` 36 | 37 | We notice several things: 38 | - it's a `@js.native` trait, so we cannot `new` it ourselves. This can be [`changed`](conversion-options.md#stenablescalajsdefined), but it's not recommended. 39 | - it has two required members (`x` and `y`). Optional members would typically be wrapped in `js.UndefOr` 40 | - it has an `object` with syntax to help us work with it 41 | - the entire syntax is built on mutability. It's Javascript, after all. more on that further down 42 | 43 | ### Basic usage 44 | 45 | ```scala 46 | // At construction time we need to supply all required parameters 47 | val p = Point(x = 1,y = 2) 48 | 49 | // we can mutate what we have 50 | // this is equivalent to typescript `p.x = 3 51 | val p2 = p.setX(3) 52 | 53 | // or we can duplicate and then mutate. 54 | // this is equivalent to typescript `const p2 = {...p, x: 3} 55 | val p3 = p.duplicate.setX(3) 56 | 57 | // we can combine with other javascript objects. 58 | // this is equivalent to javascript `const p3 = {...p, {}}` 59 | val p4: Point with TickOptions = p.combineWith(TickOptions()) 60 | 61 | // fallback, if the type definitions are wrong or for any other reason you can break the contract 62 | val p5: p.duplicate.set("x", "foo") 63 | 64 | // you can also set any other property 65 | val p6: p.duplicate.set("x2", "foo") 66 | ``` 67 | -------------------------------------------------------------------------------- /docs/react-markdown/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-markdown demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/react-mobx/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/react-redux/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-redux demo 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/react-router-dom/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-router-dom-slinky demo 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/react-slick/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-slick demo 6 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/react-window/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-window demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/semantic-ui-react-kitchensink/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Semantic-ui-react demo 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/storybook-react/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScalablyTyped/ScalaJsReactDemos/ee2c2dfad18ffe29f7ad092a20ee5aa4c0eca841/docs/storybook-react/favicon.ico -------------------------------------------------------------------------------- /docs/storybook-react/iframe.html: -------------------------------------------------------------------------------- 1 | Storybook

No Preview

Sorry, but you either have no stories or none are selected somehow.

  • Please check the Storybook config.
  • Try reloading the page.

If the problem persists, check the browser console, or the terminal you've run Storybook from.

-------------------------------------------------------------------------------- /docs/storybook-react/index.html: -------------------------------------------------------------------------------- 1 | Storybook
-------------------------------------------------------------------------------- /docs/storybook-react/main.25f83ce417c194d85b6d.bundle.js: -------------------------------------------------------------------------------- 1 | (window.webpackJsonp=window.webpackJsonp||[]).push([[0],{403:function(n,o,p){p(404),n.exports=p(547)},466:function(n,o){}},[[403,1,2]]]); -------------------------------------------------------------------------------- /docs/storybook-react/main.311a062f112ecb6ac41a.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"main.311a062f112ecb6ac41a.bundle.js","sources":["webpack:///main.311a062f112ecb6ac41a.bundle.js"],"mappings":"AAAA","sourceRoot":""} -------------------------------------------------------------------------------- /docs/storybook-react/runtime~main.286b69a873d49f2f199c.bundle.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,l,f=r[0],i=r[1],a=r[2],c=0,s=[];c 40 | * 41 | * Copyright (c) 2014-2017, Jon Schlinkert. 42 | * Released under the MIT License. 43 | */ 44 | 45 | /** 46 | * @license 47 | * Lodash 48 | * Copyright OpenJS Foundation and other contributors 49 | * Released under MIT license 50 | * Based on Underscore.js 1.8.3 51 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 52 | */ 53 | 54 | /** @license React v0.18.0 55 | * scheduler.production.min.js 56 | * 57 | * Copyright (c) Facebook, Inc. and its affiliates. 58 | * 59 | * This source code is licensed under the MIT license found in the 60 | * LICENSE file in the root directory of this source tree. 61 | */ 62 | 63 | /** @license React v16.12.0 64 | * react-dom.production.min.js 65 | * 66 | * Copyright (c) Facebook, Inc. and its affiliates. 67 | * 68 | * This source code is licensed under the MIT license found in the 69 | * LICENSE file in the root directory of this source tree. 70 | */ 71 | 72 | /** @license React v16.12.0 73 | * react-is.production.min.js 74 | * 75 | * Copyright (c) Facebook, Inc. and its affiliates. 76 | * 77 | * This source code is licensed under the MIT license found in the 78 | * LICENSE file in the root directory of this source tree. 79 | */ 80 | 81 | /** @license React v16.12.0 82 | * react.production.min.js 83 | * 84 | * Copyright (c) Facebook, Inc. and its affiliates. 85 | * 86 | * This source code is licensed under the MIT license found in the 87 | * LICENSE file in the root directory of this source tree. 88 | */ 89 | 90 | /**! 91 | * @fileOverview Kickass library to create and place poppers near their reference elements. 92 | * @version 1.16.1 93 | * @license 94 | * Copyright (c) 2016 Federico Zivolo and contributors 95 | * 96 | * Permission is hereby granted, free of charge, to any person obtaining a copy 97 | * of this software and associated documentation files (the "Software"), to deal 98 | * in the Software without restriction, including without limitation the rights 99 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 100 | * copies of the Software, and to permit persons to whom the Software is 101 | * furnished to do so, subject to the following conditions: 102 | * 103 | * The above copyright notice and this permission notice shall be included in all 104 | * copies or substantial portions of the Software. 105 | * 106 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 107 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 108 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 109 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 110 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 111 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 112 | * SOFTWARE. 113 | */ 114 | -------------------------------------------------------------------------------- /docs/storybook-react/vendors~main.311a062f112ecb6ac41a.bundle.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | * https://github.com/es-shims/es5-shim 9 | * @license es5-shim Copyright 2009-2020 by contributors, MIT License 10 | * see https://github.com/es-shims/es5-shim/blob/master/LICENSE 11 | */ 12 | 13 | /*! 14 | * https://github.com/paulmillr/es6-shim 15 | * @license es6-shim Copyright 2013-2016 by Paul Miller (http://paulmillr.com) 16 | * and contributors, MIT License 17 | * es6-shim: v0.35.4 18 | * see https://github.com/paulmillr/es6-shim/blob/0.35.3/LICENSE 19 | * Details and documentation: 20 | * https://github.com/paulmillr/es6-shim/ 21 | */ 22 | 23 | /*! 24 | * is-plain-object 25 | * 26 | * Copyright (c) 2014-2017, Jon Schlinkert. 27 | * Released under the MIT License. 28 | */ 29 | 30 | /*! 31 | * isobject 32 | * 33 | * Copyright (c) 2014-2017, Jon Schlinkert. 34 | * Released under the MIT License. 35 | */ 36 | 37 | /** @license React v0.19.1 38 | * scheduler.production.min.js 39 | * 40 | * Copyright (c) Facebook, Inc. and its affiliates. 41 | * 42 | * This source code is licensed under the MIT license found in the 43 | * LICENSE file in the root directory of this source tree. 44 | */ 45 | 46 | /** @license React v16.13.1 47 | * react-dom.production.min.js 48 | * 49 | * Copyright (c) Facebook, Inc. and its affiliates. 50 | * 51 | * This source code is licensed under the MIT license found in the 52 | * LICENSE file in the root directory of this source tree. 53 | */ 54 | 55 | /** @license React v16.13.1 56 | * react.production.min.js 57 | * 58 | * Copyright (c) Facebook, Inc. and its affiliates. 59 | * 60 | * This source code is licensed under the MIT license found in the 61 | * LICENSE file in the root directory of this source tree. 62 | */ 63 | 64 | //! stable.js 0.1.8, https://github.com/Two-Screen/stable 65 | 66 | //! © 2018 Angry Bytes and contributors. MIT licensed. 67 | -------------------------------------------------------------------------------- /docs/storybook-react/vendors~main.311a062f112ecb6ac41a.bundle.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"vendors~main.311a062f112ecb6ac41a.bundle.js","sources":["webpack:///vendors~main.311a062f112ecb6ac41a.bundle.js"],"mappings":";AAAA","sourceRoot":""} -------------------------------------------------------------------------------- /downshift/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Downshift demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /downshift/src/main/scala/demo/Demo.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.vdom.Implicits.* 4 | import japgolly.scalajs.react.vdom.TagMod 5 | import japgolly.scalajs.react.{Callback, ScalaFnComponent} 6 | import org.scalajs.dom 7 | import typings.csstype.csstypeStrings.{bold, none, normal} 8 | import typings.csstype.mod.{DisplayLegacy, NamedColor, OverflowYProperty, PositionProperty} 9 | import typings.downshift.components.Downshift 10 | import typings.downshift.mod.{GetItemPropsOptions, GetPropsCommonOptions, GetRootPropsOptions} 11 | import typings.react.components.* 12 | import typings.react.mod.CSSProperties 13 | 14 | import scala.scalajs.js.| 15 | 16 | // https://codesandbox.io/s/github/kentcdodds/downshift-examples/tree/master/?module=/src/ordered-examples/01-basic-autocomplete.js&moduleview=1&file=/src/downshift/ordered-examples/00-get-root-props-example.js 17 | def asOpt[T](t: T | Null): Option[T] = Option(t.asInstanceOf[T]) 18 | 19 | val menuStyles = CSSProperties() 20 | .setMaxHeight(80) 21 | .setMaxWidth(300) 22 | .setOverflowY(OverflowYProperty.scroll) 23 | .setBackgroundColor("#eee") 24 | .setPadding(0) 25 | .setListStyle(none) 26 | .setPosition(PositionProperty.relative) 27 | 28 | val comboboxStyles = CSSProperties().setDisplay(DisplayLegacy.`inline-block`).setMarginLeft("5px") 29 | 30 | case class Item(value: String) 31 | 32 | val items = Seq(Item("apple"), Item("pear"), Item("orange"), Item("grape"), Item("banana")) 33 | 34 | val Main = ScalaFnComponent[Unit] { case () => 35 | Downshift[Item]() 36 | .onChange((selection, _) => 37 | Callback.alert(asOpt(selection).fold("Selection Cleared")(value => s"You selected ${value.value}")) 38 | ) 39 | .itemToString(item => asOpt(item).fold("")(_.value)) 40 | .children(ctrl => 41 | div( 42 | label.unsafeSpread(ctrl.getLabelProps())("Enter a fruit:"), 43 | div 44 | .style(comboboxStyles) 45 | .unsafeSpread( 46 | ctrl.getRootProps(GetRootPropsOptions("refkey"), GetPropsCommonOptions().setSuppressRefError(true)) 47 | )( 48 | input.unsafeSpread(ctrl.getInputProps()), 49 | button.unsafeSpread(ctrl.getToggleButtonProps()).`aria-label`("toggle menu")("toggle menu") 50 | ), 51 | ul.unsafeSpread(ctrl.getMenuProps()) 52 | .style(menuStyles)( 53 | if !ctrl.isOpen then TagMod.empty 54 | else 55 | TagMod.fromTraversableOnce( 56 | items 57 | .filter(item => asOpt(ctrl.inputValue).fold(false)(inputValue => item.value.contains(inputValue))) 58 | .zipWithIndex 59 | .map { case (item, index) => 60 | li.unsafeSpread( 61 | ctrl.getItemProps( 62 | GetItemPropsOptions(item) 63 | .setKey(item.value) 64 | .setIndex(index) 65 | .setStyle { 66 | val isSelected = asOpt(ctrl.highlightedIndex).contains(index) 67 | CSSProperties() 68 | .setBackgroundColor(if isSelected then NamedColor.lightgray else NamedColor.white) 69 | .setFontWeight(if isSelected then bold else normal) 70 | } 71 | ) 72 | )(item.value) 73 | .build 74 | } 75 | ) 76 | ) 77 | ).rawNode 78 | ) 79 | } 80 | 81 | @main 82 | def main: Unit = 83 | Main().renderIntoDOM(dom.document.getElementById("container")) 84 | -------------------------------------------------------------------------------- /material-ui/src/main/js/google.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 11 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /material-ui/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Material-ui demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /material-ui/src/main/js/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | logo_white 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/Demo.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import demo.album.Album 4 | import demo.button.{ButtonTest, SelectDemo, StyledButtonDemo, StyledButtonHooksDemo} 5 | import demo.components.AppTheme 6 | import demo.customization.{DarkTheme, Palette} 7 | import demo.dashboard.Dashboard 8 | import demo.login.Login 9 | import demo.signin.SignIn 10 | import japgolly.scalajs.react.ScalaFnComponent 11 | import japgolly.scalajs.react.vdom.html_<^.* 12 | import org.scalajs.dom 13 | import typings.materialUiCore.components.{List, ListItem, ListItemIcon, ListItemText, ListSubheader, Typography} 14 | import typings.materialUiCore.stylesCreateMuiThemeMod.{Theme, ThemeOptions} 15 | import typings.materialUiCore.stylesCreateTypographyMod.TypographyOptions 16 | import typings.materialUiCore.stylesMod.createMuiTheme 17 | import typings.materialUiCore.typographyTypographyMod.Style 18 | import typings.materialUiIcons.components as Icon 19 | import typings.materialUiStyles.components.ThemeProvider 20 | import typings.react.components.Fragment 21 | import typings.reactRouter.mod.RouteProps 22 | import typings.reactRouterDom.components.{BrowserRouter, Link, Route} 23 | 24 | val theme: Theme = createMuiTheme( 25 | ThemeOptions() 26 | .setTypography( 27 | TypographyOptions().setUseNextVariants(true) 28 | ) // https://v3.material-ui.com/style/typography/#migration-to-typography-v2 29 | ) 30 | 31 | /* the production build is deployed at github pages under /material-ui , while dev build is server from root of webpack-dev-server */ 32 | val basename = if scala.scalajs.runtime.linkingInfo.productionMode then "/ScalajsReactDemos/material-ui/" else "" 33 | 34 | val Main = ScalaFnComponent[Unit] { case () => 35 | ThemeProvider(theme)( 36 | BrowserRouter.basename(basename)( 37 | Route( 38 | RouteProps() 39 | .setExact(true) 40 | .setPath("/") 41 | .setRender(_ => 42 | List( 43 | ListSubheader.inset(true)(""), 44 | Link[String](to = "/dashboard")( 45 | ListItem.button(true)(ListItemIcon(Icon.Assignment()), ListItemText.primary("Dashboard")) 46 | ), 47 | Link[String](to = "/album")( 48 | ListItem.button(true)(ListItemIcon(Icon.Assignment()), ListItemText.primary("Album")) 49 | ), 50 | Link[String](to = "/signin")( 51 | ListItem.button(true)(ListItemIcon(Icon.Assignment()), ListItemText.primary("Sign In")) 52 | ), 53 | Link[String](to = "/login")( 54 | ListItem.button(true)(ListItemIcon(Icon.Assignment()), ListItemText.primary("Login")) 55 | ), 56 | Link[String](to = "/button")( 57 | ListItem.button(true)(ListItemIcon(Icon.Assignment()), ListItemText.primary("Buttons")) 58 | ), 59 | Link[String](to = "/select")( 60 | ListItem.button(true)(ListItemIcon(Icon.Assignment()), ListItemText.primary("Select")) 61 | ), 62 | Link[String](to = "/customization")( 63 | ListItem.button(true)(ListItemIcon(Icon.Assignment()), ListItemText.primary("Customization")) 64 | ) 65 | ).rawElement 66 | ) 67 | ), 68 | Route( 69 | RouteProps() 70 | .setPath("/dashboard") 71 | .setRender(_ => 72 | Fragment( 73 | AppTheme.component( 74 | AppTheme.Props( 75 | title = "Dashboard page layout example - Material-UI", 76 | description = "An example layout for creating an albumn.", 77 | hideCredit = true 78 | ) 79 | )(Dashboard.component()) 80 | ).rawElement 81 | ) 82 | ), 83 | Route( 84 | RouteProps() 85 | .setPath("/album") 86 | .setRender(_ => 87 | Fragment( 88 | AppTheme.component( 89 | AppTheme.Props( 90 | title = "Album page layout - Material-UI", 91 | description = "An example layout for creating an album or gallery." 92 | ) 93 | )(Album.component()) 94 | ).rawElement 95 | ) 96 | ), 97 | Route( 98 | RouteProps() 99 | .setPath("/signin") 100 | .setRender(_ => 101 | Fragment( 102 | AppTheme.component( 103 | AppTheme.Props( 104 | title = "Sign-in page layout example - Material-UI", 105 | description = "An example layout for creating a sign-in page." 106 | ) 107 | )(SignIn.component()) 108 | ).rawElement 109 | ) 110 | ), 111 | Route( 112 | RouteProps() 113 | .setPath("/login") 114 | .setRender(_ => Login.component().rawElement) 115 | ), 116 | Route( 117 | RouteProps() 118 | .setPath("/button") 119 | .setRender(_ => 120 | Fragment( 121 | Typography.variant(Style.h4).gutterBottom(true).component("h2")("Buttons"), 122 | ButtonTest.component("dear user"), 123 | StyledButtonDemo.component(), 124 | StyledButtonHooksDemo.component() 125 | ).rawElement 126 | ) 127 | ), 128 | Route( 129 | RouteProps() 130 | .setPath("/select") 131 | .setRender(_ => 132 | Fragment( 133 | Typography.variant(Style.h4).gutterBottom(true).component("h2")("Select"), 134 | SelectDemo.component(scala.List("one", "two", "three")) 135 | ).rawElement 136 | ) 137 | ), 138 | Route( 139 | RouteProps() 140 | .setPath("/customization") 141 | .setRender(_ => 142 | Fragment( 143 | Typography.variant(Style.h4).gutterBottom(true).component("h2")("Customization"), 144 | DarkTheme(), 145 | Palette.component() 146 | ).rawElement 147 | ) 148 | ) 149 | ) 150 | ) 151 | } 152 | 153 | @main 154 | def main: Unit = 155 | println("starting") 156 | Main().renderIntoDOM(dom.document.getElementById("container")) 157 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/StyleBuilder.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import org.scalablytyped.runtime.StringDictionary 4 | import typings.materialUiStyles.makeStylesMod.StylesHook 5 | import typings.materialUiStyles.mod.makeStyles 6 | import typings.materialUiStyles.withStylesMod.* 7 | 8 | import scala.scalajs.js 9 | 10 | /* This is an example of a scala facade on top of the generated code. 11 | * Note that you can do all this without casting, but type inference is not perfect. 12 | */ 13 | object StyleBuilder: 14 | @inline def apply[Theme, Props <: js.Object]: StyleBuilder[Theme, Props] = 15 | new StyleBuilder[Theme, Props](_ => StringDictionary.empty) 16 | 17 | @inline final class StyleBuilder[T, P] private (val f: StyleRulesCallback[T, P, String]) extends AnyVal: 18 | @inline def add(key: String, value: CSSProperties): StyleBuilder[T, P] = 19 | new StyleBuilder[T, P]({ theme => 20 | val ret = f(theme) 21 | ret.update(key, value) 22 | ret 23 | }) 24 | 25 | @inline def add(key: String, withTheme: T => CSSProperties): StyleBuilder[T, P] = 26 | new StyleBuilder[T, P]({ theme => 27 | val ret = this.f(theme) 28 | ret.update(key, withTheme(theme)) 29 | ret 30 | }) 31 | 32 | @inline def add(key: String, withThemeProps: (T, P) => CSSProperties): StyleBuilder[T, P] = 33 | new StyleBuilder[T, P]({ theme => 34 | val ret: StyleRules[P, String] = this.f(theme) 35 | val x: js.Function1[P, CSSProperties] = (props: P) => withThemeProps(theme, props) 36 | ret.update(key, x) 37 | ret 38 | }) 39 | 40 | @inline def hook: StylesHook[Styles[T, P, String]] = 41 | makeStyles[Styles[T, P, String]](f) 42 | 43 | @inline def hook(opts: WithStylesOptions): StylesHook[Styles[T, P, String]] = 44 | makeStyles[Styles[T, P, String]](f, opts) 45 | end StyleBuilder 46 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/album/Album.scala: -------------------------------------------------------------------------------- 1 | package demo.album 2 | 3 | import demo.StyleBuilder 4 | import japgolly.scalajs.react.ScalaFnComponent 5 | import japgolly.scalajs.react.vdom.html_<^.* 6 | import org.scalablytyped.runtime.StringDictionary 7 | import typings.classnames.mod as classNames 8 | import typings.csstype.csstypeStrings.{auto, column, flex, relative} 9 | import typings.materialUiCore.components.* 10 | import typings.materialUiCore.stylesCreateMuiThemeMod.Theme 11 | import typings.materialUiCore.materialUiCoreInts.* 12 | import typings.materialUiCore.materialUiCoreStrings as strings 13 | import typings.materialUiCore.mod.PropTypes.Color 14 | import typings.materialUiCore.typographyTypographyMod.Style 15 | import typings.materialUiIcons.components as Icons 16 | import typings.materialUiStyles.makeStylesMod.StylesHook 17 | import typings.materialUiStyles.withStylesMod.{CSSProperties, Styles} 18 | import typings.react.components.Fragment 19 | 20 | import scala.scalajs.js 21 | 22 | // https://v3.material-ui.com/getting-started/page-layout-examples/album/ 23 | // https://github.com/mui-org/material-ui/blob/v3.x/docs/src/pages/getting-started/page-layout-examples/album/Album.js 24 | object Album: 25 | lazy val styles: StylesHook[Styles[Theme, js.Object, String]] = 26 | StyleBuilder[Theme, js.Object] 27 | .add("appBar", CSSProperties().setPosition(relative)) 28 | .add("icon", theme => CSSProperties().setMarginRight(theme.spacing.unit * 2)) 29 | .add("heroUnit", theme => CSSProperties().setBackgroundColor(theme.palette.background.paper)) 30 | .add( 31 | "heroContent", 32 | theme => 33 | CSSProperties() 34 | .setMaxWidth(600) 35 | .setMargin("0 auto") 36 | .setPadding(s"${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px") 37 | ) 38 | .add("heroButtons", theme => CSSProperties().setMarginTop(theme.spacing.unit * 4)) 39 | .add( 40 | "layout", 41 | theme => 42 | CSSProperties() 43 | .setWidth("auto") 44 | .setMarginLeft(theme.spacing.unit * 3) 45 | .setMarginRight(theme.spacing.unit * 3) 46 | .set( 47 | theme.breakpoints.up(1100 + theme.spacing.unit * 3 * 2), 48 | CSSProperties() 49 | .setWidth(1100) 50 | .setMarginLeft(auto.asInstanceOf[String]) 51 | .setMarginRight(auto.asInstanceOf[String]) 52 | ) 53 | ) 54 | .add("cardGrid", theme => CSSProperties().setPadding(s"${theme.spacing.unit * 8}px 0")) 55 | .add("card", CSSProperties().setHeight("100%").setDisplay(flex).setFlexDirection(column)) 56 | .add("cardMedia", CSSProperties().setPaddingTop("56.25%")) // 16:9 57 | .add("cardContent", CSSProperties().setFlexGrow(1)) 58 | .add( 59 | "footer", 60 | theme => CSSProperties().setBackgroundColor(theme.palette.background.paper).setPadding(theme.spacing.unit * 6) 61 | ) 62 | .hook 63 | 64 | val cards: Seq[Int] = 1 to 12 65 | 66 | type Props = Unit 67 | 68 | val component = ScalaFnComponent[Unit] { _ => 69 | val classes = styles(js.undefined) 70 | 71 | Fragment( 72 | CssBaseline(), 73 | Stepper.connectorVdomElement(<.div("foo")), 74 | AppBar 75 | .position(strings.static) 76 | .className(classes("appBar"))( 77 | Toolbar( 78 | Icons.PhotoCamera.className(classes("icon"))(), 79 | Typography.variant(Style.h6).color(Color.inherit)("Album layout") 80 | ) 81 | ), 82 | <.main( 83 | <.div(^.className := classes("heroUnit"))( 84 | <.div(^.className := classes("heroContent"))( 85 | Typography 86 | .variant(Style.h2) 87 | .align(strings.center) 88 | .color(strings.textSecondary) 89 | .gutterBottom(true) 90 | .component("h1")( 91 | "Album Layout" 92 | ), 93 | Typography 94 | .variant(Style.h6) 95 | .align(strings.center) 96 | .color(strings.textSecondary) 97 | .paragraph(true)( 98 | """Something short and leading about the collection below—its 99 | |contents, the creator, etc. Make it short and sweet, but not too 100 | |short so folks don't simply skip over it entirely.""".stripMargin 101 | ), 102 | <.div(^.className := classes("heroButtons"))( 103 | Grid 104 | .container(true) 105 | .spacing(`16`) 106 | .justify(strings.center)( 107 | Grid.item(true)(Button.variant(strings.contained).color(Color.primary)("Main call to action")), 108 | Grid.item(true)(Button.variant(strings.outlined).color(Color.primary)("Secondary action")) 109 | ) 110 | ) 111 | ) 112 | ), 113 | <.div( 114 | ^.className := classNames(StringDictionary[js.Any](classes("layout") -> true, classes("cardGrid") -> true)) 115 | )( 116 | Grid 117 | .container(true) 118 | .spacing(`40`)(cards.map { card => 119 | Grid 120 | .item(true) 121 | .withKey(card.toString) 122 | .sm(`6`) 123 | .md(`4`) 124 | .lg(`3`)( 125 | Card.className(classes("card"))( 126 | CardMedia 127 | .className(classes("cardMedia")) 128 | .image( 129 | "data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22288%22%20height%3D%22225%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20288%20225%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_164edaf95ee%20text%20%7B%20fill%3A%23eceeef%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_164edaf95ee%22%3E%3Crect%20width%3D%22288%22%20height%3D%22225%22%20fill%3D%22%2355595c%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2296.32500076293945%22%20y%3D%22118.8%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E" 130 | ) /* eslint-disable-line max-len*/ 131 | .title("Image title"), 132 | CardContent 133 | .className("cardContent")( 134 | Typography 135 | .gutterBottom(true) 136 | .variant(Style.h5) 137 | .component("h2")("Heading"), 138 | Typography( 139 | "This is a media card. You can use this section to describe the content." 140 | ) 141 | ), 142 | CardActions( 143 | Button.size(strings.small).color(Color.primary)("View"), 144 | Button.size(strings.small).color(Color.primary)("Edit") 145 | ) 146 | ) 147 | ) 148 | .build 149 | }.toVdomArray) 150 | ) 151 | ), 152 | <.footer(^.className := classes("footer"))( 153 | Typography 154 | .variant(Style.h6) 155 | .align(strings.center) 156 | .gutterBottom(true)( 157 | "Footer" 158 | ), 159 | Typography 160 | .variant(Style.subtitle1) 161 | .align(strings.center) 162 | .color(strings.textSecondary) 163 | .component("p")( 164 | "Something here to give the footer a purpose!" 165 | ) 166 | ) 167 | ) 168 | } 169 | end Album 170 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/button/Button.scala: -------------------------------------------------------------------------------- 1 | package demo.button 2 | 3 | import japgolly.scalajs.react.vdom.html_<^.* 4 | import japgolly.scalajs.react.{Callback, ScalaFnComponent} 5 | import org.scalablytyped.runtime.StringDictionary 6 | import org.scalajs.dom 7 | import typings.csstype.mod.{ColorProperty, NamedColor} 8 | import typings.materialUiCore.stylesCreateMuiThemeMod.{Theme, ThemeOptions} 9 | import typings.materialUiCore.stylesSpacingMod.SpacingOptions 10 | import typings.materialUiCore.{stylesMod, components as Mui} 11 | import typings.materialUiStyles.components.ThemeProvider 12 | import typings.react.mod.useState 13 | import typings.std.global.window 14 | 15 | import scala.scalajs.js 16 | 17 | object Button: 18 | 19 | val theme: Theme = stylesMod.createMuiTheme(ThemeOptions().setSpacing(SpacingOptions().setUnit(2))) 20 | 21 | // theme passed through react context and used in StyledButtonHooksDemo 22 | ThemeProvider(theme)( 23 | <.div( 24 | ButtonTest.component("dear user"), 25 | SelectDemo.component(List("one", "two", "three")), 26 | StyledButtonDemo.component(), 27 | StyledButtonHooksDemo.component() 28 | ) 29 | ).renderIntoDOM(dom.document.getElementById("container")) 30 | end Button 31 | 32 | object ButtonTest: 33 | 34 | val component = ScalaFnComponent[String] { name => 35 | /* use a hook to keep state */ 36 | val js.Tuple2(state, setState) = useState(1) 37 | 38 | val incrementButton = Mui.Button.onClick(_ => Callback(setState(state + 1)))( 39 | s"Increment it, $name" 40 | ) 41 | 42 | <.div( 43 | /* text field controlled by the value of the state hook above*/ 44 | Mui.TextField.StandardTextFieldProps().value(state).disabled(true), 45 | incrementButton 46 | ) 47 | } 48 | end ButtonTest 49 | 50 | object SelectDemo: 51 | 52 | val component = ScalaFnComponent[List[String]] { case values => 53 | val js.Tuple2(chosen, setChosen) = useState[String](values.head) 54 | 55 | val items = values.zipWithIndex.map { case (value, idx) => 56 | Mui.MenuItem.value(value).withKey(idx.toString)(value).build 57 | }.toVdomArray 58 | 59 | <.div( 60 | Mui.Select 61 | .value(chosen) 62 | .onChange((e, _) => Callback(setChosen(e.target.value)))(items), 63 | Mui.TextField 64 | .StandardTextFieldProps() 65 | .value(chosen) 66 | .disabled(true) 67 | ) 68 | } 69 | end SelectDemo 70 | 71 | object StyledButtonDemo: 72 | 73 | val component = ScalaFnComponent[Unit] { _ => 74 | val usingWithStyles = 75 | import typings.materialUiCore.stylesWithStylesMod.{CSSProperties, WithStylesOptions} 76 | 77 | val styleInjector = 78 | stylesMod.withStyles( 79 | StringDictionary("root" -> CSSProperties().setBackgroundColor(NamedColor.blue)), 80 | WithStylesOptions[String]() 81 | ) 82 | 83 | Mui.Button 84 | .withComponent(c => styleInjector(c).asInstanceOf[js.Any]) 85 | .onClick(_ => Callback(window.alert("clicked")))("using withStyles") 86 | end usingWithStyles 87 | 88 | val usingReactCss = 89 | import typings.react.mod.CSSProperties 90 | Mui.Button 91 | .style(CSSProperties().setBackgroundColor(NamedColor.darkred)) 92 | .onClick(_ => Callback(window.alert("clicked")))("direct css") 93 | 94 | <.div(usingWithStyles, usingReactCss) 95 | } 96 | end StyledButtonDemo 97 | 98 | // https://v3.material-ui.com/css-in-js/basics/ 99 | object StyledButtonHooksDemo: 100 | 101 | import typings.materialUiStyles.makeStylesMod.StylesHook 102 | import typings.materialUiStyles.mod.makeStyles 103 | import typings.materialUiStyles.withStylesMod.{CSSProperties, StyleRulesCallback, Styles, WithStylesOptions} 104 | 105 | class StyleProps(val favouriteColor: ColorProperty) extends js.Object 106 | 107 | val useStyles: StylesHook[Styles[Theme, StyleProps, String]] = 108 | val root: js.Function1[StyleProps, CSSProperties] = props => 109 | CSSProperties() 110 | .setBackground("linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)") 111 | .setBorder(0) 112 | .setBorderRadius(3) 113 | .setBoxShadow("0 3px 5px 2px rgba(255, 105, 135, .3)") 114 | .setColor(props.favouriteColor) 115 | .setHeight(48) 116 | .setPadding("0 30px") 117 | 118 | /* If you don't need direct access to theme, this could be `StyleRules[Props, String]` */ 119 | val stylesCallback: StyleRulesCallback[Theme, StyleProps, String] = theme => 120 | StringDictionary( 121 | "root" -> root, 122 | "outer" -> CSSProperties().setPadding(s"${theme.spacing.unit * 3}px") 123 | ) 124 | 125 | makeStyles(stylesCallback, WithStylesOptions()) 126 | end useStyles 127 | 128 | val component = ScalaFnComponent[Unit] { _ => 129 | val classes = useStyles(new StyleProps(NamedColor.green)) 130 | <.div( 131 | ^.className := classes("outer"), 132 | Mui.Button 133 | .className(classes("root")) 134 | .onClick(_ => Callback.alert("clicked"))("styles module with hook") 135 | ) 136 | } 137 | end StyledButtonHooksDemo 138 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/components/AppTheme.scala: -------------------------------------------------------------------------------- 1 | package demo.components 2 | 3 | import demo.StyleBuilder 4 | import japgolly.scalajs.react.ScalaFnComponent 5 | import japgolly.scalajs.react.vdom.html_<^.* 6 | import org.scalablytyped.runtime.StringDictionary 7 | import typings.classnames.mod as classNames 8 | import typings.csstype.csstypeStrings.absolute 9 | import typings.materialUiCore.stylesColorManipulatorMod.darken 10 | import typings.materialUiCore.colorsMod.{blue, pink} 11 | import typings.materialUiCore.components.* 12 | import typings.materialUiCore.stylesCreateMuiThemeMod.{Direction, Theme, ThemeOptions} 13 | import typings.materialUiCore.stylesCreatePaletteMod.{ColorPartial, PaletteColorOptions, PaletteOptions} 14 | import typings.materialUiCore.stylesCreateTypographyMod.TypographyOptions 15 | import typings.materialUiCore.materialUiCoreStrings.{center, textSecondary} 16 | import typings.materialUiCore.mod.PropTypes.Color 17 | import typings.materialUiCore.stylesMod.createMuiTheme 18 | import typings.materialUiStyles.makeStylesMod.StylesHook 19 | import typings.materialUiStyles.withStylesMod.{CSSProperties, Styles} 20 | 21 | import scala.scalajs.js 22 | 23 | // https://github.com/mui-org/material-ui/blob/v3.x/docs/src/modules/components/AppTheme.js 24 | object AppTheme: 25 | 26 | // https://github.com/mui-org/material-ui/blob/v3.x/docs/src/modules/styles/themeInitialState.js 27 | val theme: Theme = createMuiTheme( 28 | ThemeOptions() 29 | .setDirection(Direction.ltr) 30 | .setTypography(TypographyOptions().setUseNextVariants(true)) 31 | .setPalette( 32 | PaletteOptions() 33 | .setPrimary(ColorPartial().combineWith(blue)) 34 | .setSecondary( 35 | PaletteColorOptions.SimplePaletteColorOptions(darken(pink.A400, 0.08)) 36 | ) // Darken so we reach the AA contrast ratio level. 37 | ) 38 | ) 39 | 40 | lazy val styles: StylesHook[Styles[Theme, js.Object, String]] = 41 | StyleBuilder[Theme, js.Object] 42 | .add( 43 | "credit", 44 | theme => CSSProperties().setMarginTop(theme.spacing.unit * 6).setMarginBottom(theme.spacing.unit * 4) 45 | ) 46 | .add("hideCredit", CSSProperties().setPosition(absolute).set("top", 0)) 47 | .hook 48 | 49 | case class Props(description: String, hideCredit: Boolean = false, title: String) 50 | 51 | val component = ScalaFnComponent.withChildren[Props] { case (props, children) => 52 | val classes = styles(js.undefined) 53 | MuiThemeProvider(theme)( 54 | children, 55 | Typography 56 | .color(textSecondary) 57 | .align(center) 58 | .className( 59 | classNames(StringDictionary[js.Any](classes("credit") -> true, classes("hideCredit") -> props.hideCredit)) 60 | )( 61 | "Built with ", 62 | <.span(^.role := "img", ^.aria.label := "Love")("❤️"), 63 | " by the ", 64 | Link.color(Color.inherit).href("www.scalablytyped.org")("ScalablyTyped Material-UI"), 65 | " team." 66 | ) 67 | ) 68 | } 69 | end AppTheme 70 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/customization/DarkTheme.scala: -------------------------------------------------------------------------------- 1 | package demo.customization 2 | 3 | import typings.materialUiCore.stylesCreateMuiThemeMod.{Theme, ThemeOptions} 4 | import typings.materialUiCore.stylesCreatePaletteMod.PaletteOptions 5 | import typings.materialUiCore.stylesCreateTypographyMod.TypographyOptions 6 | import typings.materialUiCore.mod.PaletteType 7 | import typings.materialUiCore.stylesMod.createMuiTheme 8 | import typings.materialUiStyles.components.ThemeProvider 9 | 10 | // https://github.com/mui-org/material-ui/blob/v3.x/docs/src/pages/customization/themes/DarkTheme.js 11 | object DarkTheme: 12 | val theme: Theme = createMuiTheme( 13 | ThemeOptions() 14 | .setTypography(TypographyOptions().setUseNextVariants(true)) 15 | .setPalette( 16 | PaletteOptions().setType(PaletteType.dark) /* Switching the dark mode on is a single property value change.*/ 17 | ) 18 | ) 19 | 20 | def apply() = 21 | ThemeProvider(theme)(WithTheme.component(theme)) 22 | end DarkTheme 23 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/customization/Palette.scala: -------------------------------------------------------------------------------- 1 | package demo.customization 2 | 3 | import japgolly.scalajs.react.ScalaFnComponent 4 | import japgolly.scalajs.react.vdom.html_<^.* 5 | import typings.materialUiCore.colorsMod.purple 6 | import typings.materialUiCore.components.{Button, MuiThemeProvider} 7 | import typings.materialUiCore.stylesCreateMuiThemeMod.{Theme, ThemeOptions} 8 | import typings.materialUiCore.stylesCreatePaletteMod.{PaletteColorOptions, PaletteOptions} 9 | import typings.materialUiCore.stylesCreateTypographyMod.TypographyOptions 10 | import typings.materialUiCore.mod.PropTypes.Color 11 | import typings.materialUiCore.stylesMod 12 | 13 | // https://github.com/mui-org/material-ui/blob/v3.x/docs/src/pages/customization/themes/Palette.js 14 | object Palette: 15 | 16 | val theme: Theme = stylesMod 17 | .createMuiTheme( 18 | ThemeOptions() 19 | .setTypography(TypographyOptions().setUseNextVariants(true)) 20 | .setPalette( 21 | PaletteOptions() 22 | .setPrimary( 23 | PaletteColorOptions.SimplePaletteColorOptions(purple.`500`) 24 | ) // Purple and green play nicely together. 25 | .setSecondary(PaletteColorOptions.SimplePaletteColorOptions("#11cb5f")) // This is just green.A700 as hex. 26 | ) 27 | ) 28 | 29 | val component = ScalaFnComponent[Unit] { _ => 30 | MuiThemeProvider(theme)( 31 | Button.color(Color.primary)(<.span("Primary")), 32 | Button.color(Color.secondary)(<.span("Secondary")) 33 | ) 34 | } 35 | end Palette 36 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/customization/WithTheme.scala: -------------------------------------------------------------------------------- 1 | package demo.customization 2 | 3 | import japgolly.scalajs.react.ScalaFnComponent 4 | import japgolly.scalajs.react.vdom.html_<^.* 5 | import typings.materialUiCore.components.Typography 6 | import typings.materialUiCore.stylesCreateMuiThemeMod.Theme 7 | import typings.react.mod.CSSProperties 8 | 9 | // https://github.com/mui-org/material-ui/blob/v3.x/docs/src/pages/customization/themes/WithTheme.js 10 | object WithTheme: 11 | val component = ScalaFnComponent[Theme] { theme => 12 | val primaryText = theme.palette.text.primary; 13 | val primaryColor = theme.palette.primary.main; 14 | 15 | <.div(^.style := CSSProperties().setWidth(300))( 16 | Typography.style( 17 | CSSProperties() 18 | .setBackgroundColor(primaryColor) 19 | .setPadding(s"${theme.spacing.unit}px ${theme.spacing.unit * 2}px") 20 | .setColor(theme.palette.common.white) 21 | )(s"Primary color $primaryColor"), 22 | Typography.style( 23 | CSSProperties() 24 | .setBackgroundColor(theme.palette.background.default) 25 | .setPadding(s"${theme.spacing.unit}px ${theme.spacing.unit * 2}px") 26 | .setColor(primaryText) 27 | )(s"Primary color $primaryText") 28 | ) 29 | } 30 | end WithTheme 31 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/dashboard/Dashboard.scala: -------------------------------------------------------------------------------- 1 | package demo.dashboard 2 | 3 | import demo.StyleBuilder 4 | import japgolly.scalajs.react.vdom.html_<^.* 5 | import japgolly.scalajs.react.{Callback, ScalaFnComponent} 6 | import org.scalablytyped.runtime.StringDictionary 7 | import typings.classnames.mod as classNames 8 | import typings.csstype.csstypeStrings.* 9 | import typings.csstype.mod.OverflowXProperty 10 | import typings.materialUiCore.anon.{PartialClassNameMapDrawer, Partialdurationnumberstri} 11 | import typings.materialUiCore.components.* 12 | import typings.materialUiCore.stylesCreateBreakpointsMod.Breakpoint 13 | import typings.materialUiCore.stylesCreateMuiThemeMod.Theme 14 | import typings.materialUiCore.materialUiCoreStrings.{absolute, permanent} 15 | import typings.materialUiCore.mod.PropTypes 16 | import typings.materialUiCore.typographyTypographyMod.Style 17 | import typings.materialUiIcons.components as Icons 18 | import typings.materialUiStyles.makeStylesMod.StylesHook 19 | import typings.materialUiStyles.withStylesMod.{CSSProperties, Styles} 20 | import typings.react.mod.useState 21 | 22 | import scala.scalajs.js 23 | 24 | // https://v3.material-ui.com/getting-started/page-layout-examples/dashboard/ 25 | // https://github.com/mui-org/material-ui/blob/v3.x/docs/src/pages/getting-started/page-layout-examples/dashboard/Dashboard.js 26 | object Dashboard: 27 | 28 | val drawerWidth = 240 29 | 30 | lazy val styles: StylesHook[Styles[Theme, js.Object, String]] = 31 | StyleBuilder[Theme, js.Object] 32 | .add("root", CSSProperties().setDisplay(flex)) 33 | .add("toolbar", CSSProperties().setPaddingRight(24)) // keep right padding when drawer closed 34 | .add( 35 | "toolbarIcon", 36 | theme => 37 | CSSProperties() 38 | .setDisplay(flex) 39 | .setAlignItems(center) 40 | .setJustifyContent(`flex-end`) 41 | .setPadding("0 8px") 42 | .combineWith(theme.mixins.toolbar) 43 | ) 44 | .add( 45 | "appBar", 46 | theme => 47 | CSSProperties() 48 | .setZIndex(theme.zIndex.drawer + 1) 49 | .setTransition( 50 | theme.transitions.create( 51 | js.Array("width", "margin"), 52 | Partialdurationnumberstri() 53 | .setEasing(theme.transitions.easing.sharp) 54 | .setDuration(theme.transitions.duration.enteringScreen) 55 | ) 56 | ) 57 | ) 58 | .add( 59 | "appBarShift", 60 | theme => 61 | CSSProperties() 62 | .setMarginLeft(drawerWidth) 63 | .setWidth(s"calc(100% - ${drawerWidth}px)") 64 | .setTransition( 65 | theme.transitions.create( 66 | js.Array("width", "margin"), 67 | Partialdurationnumberstri() 68 | .setEasing(theme.transitions.easing.sharp) 69 | .setDuration(theme.transitions.duration.enteringScreen) 70 | ) 71 | ) 72 | ) 73 | .add("menuButton", CSSProperties().setMarginLeft(12).setMarginRight(36)) 74 | .add("menuButtonHidden", CSSProperties().setDisplay(none)) 75 | .add("title", CSSProperties().setFlexGrow(1)) 76 | .add( 77 | "drawerPaper", 78 | theme => 79 | CSSProperties() 80 | .setPosition(relative) 81 | .setWhiteSpace(nowrap) 82 | .setWidth(drawerWidth) 83 | .setTransition( 84 | theme.transitions.create( 85 | "width", 86 | Partialdurationnumberstri() 87 | .setEasing(theme.transitions.easing.sharp) 88 | .setDuration(theme.transitions.duration.enteringScreen) 89 | ) 90 | ) 91 | ) 92 | .add( 93 | "drawerPaperClose", 94 | theme => 95 | CSSProperties() 96 | .setOverflowX(OverflowXProperty.hidden) 97 | .setTransition( 98 | theme.transitions.create( 99 | "width", 100 | Partialdurationnumberstri() 101 | .setEasing(theme.transitions.easing.sharp) 102 | .setDuration(theme.transitions.duration.leavingScreen) 103 | ) 104 | ) 105 | .setWidth(theme.spacing.unit * 7) 106 | .set(theme.breakpoints.up(Breakpoint.sm), CSSProperties().setWidth(theme.spacing.unit * 9)) 107 | ) 108 | .add("appBarSpacer", theme => CSSProperties().combineWith(theme.mixins.toolbar)) 109 | .add( 110 | "content", 111 | theme => 112 | CSSProperties() 113 | .setFlexGrow(1) 114 | .setPadding(theme.spacing.unit * 3) 115 | .setHeight("100vh") 116 | .setOverflow(auto) 117 | ) 118 | .add("chartContainer", CSSProperties().setMarginLeft(-22)) 119 | .add("tableContainer", CSSProperties().setHeight(320)) 120 | .add("h5", theme => CSSProperties().setMarginBottom(theme.spacing.unit * 2)) 121 | .hook 122 | 123 | val component = ScalaFnComponent[Unit] { case () => 124 | val classes = styles(js.undefined) 125 | val js.Tuple2(isOpen, setIsOpen) = useState(true) 126 | val handleDrawerOpen = Callback(setIsOpen(true)) 127 | val handleDrawerClose = Callback(setIsOpen(false)) 128 | 129 | <.div( 130 | ^.className := classes("root"), 131 | CssBaseline(), 132 | AppBar 133 | .position(absolute) 134 | .className(classNames(StringDictionary[js.Any](classes("appBar") -> true, classes("appBarShift") -> isOpen)))( 135 | Toolbar 136 | .disableGutters(!isOpen) 137 | .className(classes("toolbar"))( 138 | IconButton 139 | .color(PropTypes.Color.inherit) 140 | .`aria-label`("Open drawer") 141 | .onClick(_ => handleDrawerOpen) 142 | .className( 143 | classNames( 144 | StringDictionary[js.Any](classes("menuButton") -> true, classes("menuButtonHidden") -> isOpen) 145 | ) 146 | )(Icons.Menu()), 147 | Typography() 148 | .component("h1") 149 | .variant(Style.h6) 150 | .color(PropTypes.Color.inherit) 151 | .noWrap(true) 152 | .className(classes("title"))( 153 | "Dashboard" 154 | ), 155 | IconButton.color(PropTypes.Color.inherit)( 156 | Badge.badgeContent(4).color(PropTypes.Color.secondary)(Icons.Notifications()) 157 | ) 158 | ) 159 | ), 160 | Drawer 161 | .variant(permanent) 162 | .classes( 163 | PartialClassNameMapDrawer().setPaper( 164 | classNames( 165 | StringDictionary[js.Any](classes("drawerPaper") -> true, classes("drawerPaperClose") -> !isOpen) 166 | ) 167 | ) 168 | ) 169 | .open(isOpen)( 170 | <.div(^.className := classes("toolbarIcon"))( 171 | IconButton.onClick(_ => handleDrawerClose)(Icons.ChevronLeft()) 172 | ), 173 | Divider(), 174 | List(ListItems.mainListItems), 175 | Divider(), 176 | List(ListItems.secondaryListItems) 177 | ), 178 | <.main(^.className := classes("content"))( 179 | <.div(^.className := classes("appBarSpacer")), 180 | Typography 181 | .variant(Style.h4) 182 | .gutterBottom(true) 183 | .component("h2")( 184 | "Orders" 185 | ), 186 | Typography() 187 | .component("div") 188 | .className(classes("chartContainer"))( 189 | SimpleLineChart.component() 190 | ), 191 | Typography() 192 | .variant(Style.h4) 193 | .gutterBottom(true) 194 | .component("h2")( 195 | "Products" 196 | ), 197 | Typography() 198 | .component("div") 199 | .className(classes("tableContainer"))(SimpleTable.component()) 200 | ) 201 | ) 202 | } 203 | end Dashboard 204 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/dashboard/ListItems.scala: -------------------------------------------------------------------------------- 1 | package demo.dashboard 2 | 3 | import japgolly.scalajs.react.vdom.html_<^.* 4 | import typings.materialUiCore.components.{ListItem, ListItemIcon, ListItemText, ListSubheader} 5 | import typings.materialUiIcons.components as Icon 6 | 7 | // https://github.com/mui-org/material-ui/blob/v3.x/docs/src/pages/getting-started/page-layout-examples/dashboard/listItems.js 8 | object ListItems: 9 | 10 | val mainListItems: VdomElement = 11 | <.div( 12 | ListItem.button(true)( 13 | ListItemIcon(Icon.Dashboard()), 14 | ListItemText.primary("Dashboard") 15 | ), 16 | ListItem.button(true)( 17 | ListItemIcon(Icon.ShoppingCart()), 18 | ListItemText.primary("Orders") 19 | ), 20 | ListItem.button(true)( 21 | ListItemIcon(Icon.People()), 22 | ListItemText.primary("Customers") 23 | ), 24 | ListItem.button(true)( 25 | ListItemIcon(Icon.BarChart()), 26 | ListItemText.primary("Reports") 27 | ), 28 | ListItem.button(true)( 29 | ListItemIcon(Icon.Layers()), 30 | ListItemText.primary("Integrations") 31 | ) 32 | ) 33 | 34 | val secondaryListItems: VdomElement = 35 | <.div( 36 | ListSubheader.inset(true)("Saved reports"), 37 | ListItem.button(true)( 38 | ListItemIcon(Icon.Assignment()), 39 | ListItemText.primary("Current month") 40 | ), 41 | ListItem.button(true)( 42 | ListItemIcon(Icon.Assignment()), 43 | ListItemText.primary("Last quarter") 44 | ), 45 | ListItem.button(true)( 46 | ListItemIcon(Icon.Assignment()), 47 | ListItemText.primary("Year-end sale") 48 | ) 49 | ) 50 | end ListItems 51 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/dashboard/SimpleLineChart.scala: -------------------------------------------------------------------------------- 1 | package demo.dashboard 2 | 3 | import japgolly.scalajs.react.ScalaFnComponent 4 | import japgolly.scalajs.react.vdom.html_<^.* 5 | import org.scalablytyped.runtime.StringDictionary 6 | import typings.recharts.components.* 7 | import typings.recharts.rechartsStrings.monotone 8 | 9 | import scala.scalajs.js 10 | 11 | // https://github.com/mui-org/material-ui/blob/v3.x/docs/src/pages/getting-started/page-layout-examples/dashboard/SimpleLineChart.js 12 | object SimpleLineChart: 13 | 14 | class Data(val Name: String, val Visits: Int, val Orders: Int) extends js.Object 15 | 16 | val data: js.Array[js.Object] = js.Array( 17 | new Data("Mon", 200, 3400), 18 | new Data("Tue", 128, 2398), 19 | new Data("Wed", 5000, 4300), 20 | new Data("Thu", 4780, 2908), 21 | new Data("Fri", 5890, 4800), 22 | new Data("Sat", 4390, 3800), 23 | new Data("Sun", 4490, 4300) 24 | ) 25 | 26 | val component = ScalaFnComponent[Unit] { case () => 27 | ResponsiveContainer 28 | .width("99%") 29 | .height(320)( 30 | LineChart.data(data)( 31 | XAxis.dataKey("Name"), 32 | YAxis(), 33 | CartesianGrid.vertical(false).strokeDasharray("3 3"), 34 | Tooltip(), 35 | Legend(), 36 | Line("Visits").`type`(monotone).stroke("#82ca9d"), 37 | Line("Orders").`type`(monotone).stroke("#8884d8").activeDot(StringDictionary("r" -> 8)) 38 | ) 39 | ) 40 | } 41 | end SimpleLineChart 42 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/dashboard/SimpleTable.scala: -------------------------------------------------------------------------------- 1 | package demo.dashboard 2 | 3 | import demo.StyleBuilder 4 | import japgolly.scalajs.react.ScalaFnComponent 5 | import japgolly.scalajs.react.vdom.html_<^.* 6 | import typings.csstype.csstypeStrings.auto 7 | import typings.materialUiCore.components.* 8 | import typings.materialUiCore.stylesCreateMuiThemeMod.Theme 9 | import typings.materialUiCore.materialUiCoreStrings.right 10 | import typings.materialUiStyles.withStylesMod.CSSProperties 11 | 12 | import scala.scalajs.js 13 | 14 | // https://github.com/mui-org/material-ui/blob/v3.x/docs/src/pages/getting-started/page-layout-examples/dashboard/SimpleTable.js 15 | object SimpleTable: 16 | 17 | lazy val styles = 18 | StyleBuilder[Theme, js.Object] 19 | .add("root", CSSProperties().setWidth("100%").setOverflowX(auto)) 20 | .add("table", CSSProperties().setMinWidth(700)) 21 | .hook 22 | 23 | case class Data(id: Long, name: String, calories: Double, fat: Double, carbs: Double, protein: Double) 24 | 25 | val data = Seq( 26 | Data(1, "Frozen yoghurt", 159, 6.0, 24, 4.0), 27 | Data(2, "Ice cream sandwich", 237, 9.0, 37, 4.3), 28 | Data(3, "Eclair", 262, 16.0, 24, 6.0), 29 | Data(4, "Cupcake", 305, 3.7, 67, 4.3), 30 | Data(5, "Gingerbread", 356, 16.0, 49, 3.9) 31 | ) 32 | 33 | val component = ScalaFnComponent[Unit] { case () => 34 | val classes = styles(js.undefined) 35 | Paper.className(classes("root"))( 36 | Table.className(classes("table"))( 37 | TableHead( 38 | TableRow( 39 | TableCell("Dessert (100g serving)"), 40 | TableCell.align(right)("Calories"), 41 | TableCell.align(right)("Fat (g)"), 42 | TableCell.align(right)("Carbs (g)"), 43 | TableCell.align(right)("Protein (g)") 44 | ) 45 | ), 46 | TableBody(data.map { n => 47 | TableRow 48 | .withKey(n.id.toString)( 49 | TableCell.set("component", "th").scope("row")(n.name), 50 | TableCell.align(right)(n.calories), 51 | TableCell.align(right)(n.fat), 52 | TableCell.align(right)(n.carbs), 53 | TableCell.align(right)(n.protein) 54 | ) 55 | .build 56 | }.toVdomArray) 57 | ) 58 | ) 59 | } 60 | end SimpleTable 61 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/login/Login.scala: -------------------------------------------------------------------------------- 1 | package demo.login 2 | 3 | import demo.login.Styles.styles 4 | import japgolly.scalajs.react.vdom.html_<^.* 5 | import japgolly.scalajs.react.{Callback, ScalaFnComponent} 6 | import org.scalablytyped.runtime.StringDictionary 7 | import typings.classnames.mod as classNames 8 | import typings.materialUiCore.anon.{PartialClassNameMapInputC, PartialInputProps} 9 | import typings.materialUiCore.components.* 10 | import typings.materialUiCore.materialUiCoreStrings.* 11 | import typings.materialUiCore.typographyTypographyMod.Style 12 | import typings.react.components.Fragment 13 | import typings.react.mod.useState 14 | import typings.std.HTMLInputElement 15 | 16 | import scala.scalajs.js 17 | import scala.scalajs.js.annotation.JSImport 18 | 19 | @JSImport("./logo.svg", JSImport.Default) 20 | @js.native 21 | object Logo extends js.Object 22 | 23 | @JSImport("./google.svg", JSImport.Default) 24 | @js.native 25 | object GoogleLogo extends js.Object 26 | 27 | // https://github.com/flatlogic/react-material-admin/blob/master/src/pages/login/Login.js 28 | object Login: 29 | 30 | val component = ScalaFnComponent[Unit] { _ => 31 | val classes = styles(js.undefined) 32 | 33 | val js.Tuple2(isLoading, setIsLoading) = useState(false); 34 | val js.Tuple2(error, setError) = useState(false); 35 | val js.Tuple2(activeTabId, setActiveTabId) = useState(0); 36 | val js.Tuple2(nameValue, setNameValue) = useState(""); 37 | val js.Tuple2(loginValue, setLoginValue) = useState(""); 38 | val js.Tuple2(passwordValue, setPasswordValue) = useState(""); 39 | 40 | Grid 41 | .container(true) 42 | .className(classes("container"))( 43 | <.div(^.className := classes("logotypeContainer"))( 44 | <.img(^.src := Logo.asInstanceOf[String], ^.alt := "logo", ^.className := classes("logotypeImage")), 45 | Typography.className(classes("logotypeText"))("Material Admin") 46 | ), 47 | <.div(^.className := classes("formContainer"))( 48 | <.div(^.className := classes("form"))( 49 | Tabs(activeTabId) 50 | .onChange((_, id) => Callback(setActiveTabId(id.asInstanceOf[Int]))) 51 | .indicatorColor(primary) 52 | .textColor(primary) 53 | .centered(true)( 54 | Tab.label("Login").className(classNames(StringDictionary[js.Any]("root" -> classes("tab")))), 55 | Tab.label("New User").className(classNames(StringDictionary[js.Any]("root" -> classes("tab")))) 56 | ), 57 | activeTabId match 58 | case 0 => 59 | Fragment( 60 | Typography 61 | .variant(Style.h1) 62 | .className(classes("greeting"))("Good Morning, User"), 63 | Button 64 | .size(large) 65 | .className(classes("googleButton"))( 66 | <.img( 67 | ^.src := GoogleLogo.asInstanceOf[String], 68 | ^.alt := "google", 69 | ^.className := classes("googleIcon") 70 | ), 71 | "Sign in with Google" 72 | ), 73 | <.div(^.className := classes("formDividerContainer"))( 74 | <.div(^.className := classes("formDivider")), 75 | Typography.className(classes("formDividerWord"))("or"), 76 | <.div(^.className := classes("formDivider")) 77 | ), 78 | Fade.in(error)( 79 | Typography 80 | .color(secondary) 81 | .className(classes("errorMessage"))("Something is wrong with your login or password :(") 82 | ), 83 | TextField.StandardTextFieldProps 84 | .id("email") 85 | .InputProps( 86 | PartialInputProps() 87 | .setClasses( 88 | PartialClassNameMapInputC() 89 | .setUnderline(classes("textFieldUnderline")) 90 | .setInput(classes("textField")) 91 | ) 92 | ) 93 | .value(loginValue) 94 | .onChange(e => Callback(setLoginValue(e.currentTarget.asInstanceOf[HTMLInputElement].value))) 95 | .margin(normal) 96 | .placeholder("Email Address") 97 | .`type`("email") 98 | .fullWidth(true), 99 | TextField.StandardTextFieldProps 100 | .id("password") 101 | .InputProps( 102 | PartialInputProps() 103 | .setClasses( 104 | PartialClassNameMapInputC() 105 | .setUnderline(classes("textFieldUnderline")) 106 | .setInput(classes("textField")) 107 | ) 108 | ) 109 | .value(passwordValue) 110 | .onChange(e => Callback(setPasswordValue(e.currentTarget.asInstanceOf[HTMLInputElement].value))) 111 | .margin(normal) 112 | .placeholder("Password") 113 | .`type`("password") 114 | .fullWidth(true), 115 | <.div(^.className := classes("formButtons"))( 116 | if isLoading then CircularProgress.size(26).className(classes("loginLoader")) 117 | else 118 | Button 119 | .disabled(loginValue.length == 0 || passwordValue.length == 0) 120 | .variant(contained) 121 | .color(primary) 122 | .size(large)("Login") 123 | , 124 | Button 125 | .color(primary) 126 | .size(large) 127 | .className(classes("forgetButton"))("Forget Password") 128 | ) 129 | ) 130 | case 1 => 131 | Fragment( 132 | Typography 133 | .variant(Style.h1) 134 | .className(classes("greeting"))("Welcome"), 135 | Typography.variant(Style.h2).className(classes("subGreeting"))("Create your account"), 136 | Fade.in(error)( 137 | Typography 138 | .color(secondary) 139 | .className(classes("errorMessage"))("Something is wrong with your login or password :(") 140 | ), 141 | TextField.StandardTextFieldProps 142 | .id("name") 143 | .InputProps( 144 | PartialInputProps() 145 | .setClasses( 146 | PartialClassNameMapInputC() 147 | .setUnderline(classes("textFieldUnderline")) 148 | .setInput(classes("textField")) 149 | ) 150 | ) 151 | .value(nameValue) 152 | .onChange(e => Callback(setNameValue(e.currentTarget.asInstanceOf[HTMLInputElement].value))) 153 | .margin(normal) 154 | .placeholder("Full Name") 155 | .`type`("text") 156 | .fullWidth(true), 157 | TextField.StandardTextFieldProps 158 | .id("email") 159 | .InputProps( 160 | PartialInputProps() 161 | .setClasses( 162 | PartialClassNameMapInputC() 163 | .setUnderline(classes("textFieldUnderline")) 164 | .setInput(classes("textField")) 165 | ) 166 | ) 167 | .value(loginValue) 168 | .onChange(e => Callback(setLoginValue(e.currentTarget.asInstanceOf[HTMLInputElement].value))) 169 | .margin(normal) 170 | .placeholder("Email Address") 171 | .`type`("email") 172 | .fullWidth(true), 173 | TextField.StandardTextFieldProps 174 | .id("password") 175 | .InputProps( 176 | PartialInputProps() 177 | .setClasses( 178 | PartialClassNameMapInputC() 179 | .setUnderline(classes("textFieldUnderline")) 180 | .setInput(classes("textField")) 181 | ) 182 | ) 183 | .value(passwordValue) 184 | .onChange(e => Callback(setPasswordValue(e.currentTarget.asInstanceOf[HTMLInputElement].value))) 185 | .margin(normal) 186 | .placeholder("Password") 187 | .`type`("password") 188 | .fullWidth(true), 189 | <.div(^.className := classes("formButtons"))( 190 | if isLoading then CircularProgress.size(26).className(classes("loginLoader")) 191 | else 192 | Button 193 | .disabled(loginValue.length == 0 || passwordValue.length == 0) 194 | .size(large) 195 | .variant(contained) 196 | .color(primary) 197 | .fullWidth(true) 198 | .className(classes("createAccountButton"))("Create your account") 199 | ), 200 | <.div(^.className := classes("formDividerContainer"))( 201 | <.div(^.className := classes("formDivider")), 202 | Typography.className(classes("formDividerWord"))("or"), 203 | <.div(^.className := classes("formDivider")) 204 | ), 205 | Button 206 | .color(primary) 207 | .size(large) 208 | .className( 209 | classNames( 210 | StringDictionary[js.Any]( 211 | classes("googleButton") -> true, 212 | classes("googleButtonCreating") -> true 213 | ) 214 | ) 215 | )( 216 | <.img( 217 | ^.src := GoogleLogo.asInstanceOf[String], 218 | ^.alt := "google", 219 | ^.className := classes("googleIcon") 220 | ), 221 | "Sign in with Google" 222 | ) 223 | ) 224 | ), 225 | Typography 226 | .color(primary) 227 | .className(classes("copyright"))("© 2014-2019 Flatlogic, LLC. All rights reserved.") 228 | ) 229 | ) 230 | } 231 | end Login 232 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/login/Styles.scala: -------------------------------------------------------------------------------- 1 | package demo.login 2 | 3 | import demo.StyleBuilder 4 | import typings.csstype.csstypeStrings.* 5 | import typings.materialUiCore.stylesCreateMuiThemeMod.Theme 6 | import typings.materialUiCore.stylesSpacingMod.Spacing 7 | import typings.materialUiStyles.makeStylesMod.StylesHook 8 | import typings.materialUiStyles.withStylesMod.{CSSProperties, Styles} 9 | 10 | import scala.scalajs.js 11 | 12 | // https://github.com/flatlogic/react-material-admin/blob/master/src/pages/login/styles.js 13 | object Styles: 14 | 15 | lazy val styles: StylesHook[Styles[Theme, js.Object, String]] = 16 | StyleBuilder[Theme, js.Object] 17 | .add( 18 | "container", 19 | theme => 20 | CSSProperties() 21 | .setHeight("100vh") 22 | .setWidth("100vw") 23 | .setDisplay(flex) 24 | .setJustifyContent(center) 25 | .setAlignItems(center) 26 | .setPosition(typings.csstype.csstypeStrings.absolute) 27 | .setTop(0) 28 | .setLeft(0) 29 | ) 30 | .add( 31 | "logotypeContainer", 32 | theme => 33 | CSSProperties() 34 | .setBackgroundColor(theme.palette.primary.main) 35 | .setWidth("60%") 36 | .setHeight("100%") 37 | .setDisplay(flex) 38 | .setFlexDirection(typings.csstype.csstypeStrings.column) 39 | .setJustifyContent(center) 40 | .setAlignItems(center) 41 | .set( 42 | theme.breakpoints.down(typings.materialUiCore.materialUiCoreStrings.md), 43 | CSSProperties() 44 | .setWidth("50%") 45 | ) 46 | .set( 47 | theme.breakpoints.down(typings.materialUiCore.materialUiCoreStrings.md), 48 | CSSProperties() 49 | .setDisplay(none) 50 | ) 51 | ) 52 | .add( 53 | "logotypeImage", 54 | theme => 55 | CSSProperties() 56 | .setWidth(165) 57 | .setMarginBottom(theme.setSpacing(Spacing(4)).spacing.unit) 58 | ) 59 | .add( 60 | "logotypeText", 61 | theme => 62 | CSSProperties() 63 | .setColor("white") 64 | .setFontWeight(500) 65 | .setFontSize(84) 66 | .set( 67 | theme.breakpoints.down(typings.materialUiCore.materialUiCoreStrings.md), 68 | CSSProperties() 69 | .setFontSize(48) 70 | ) 71 | ) 72 | .add( 73 | "formContainer", 74 | theme => 75 | CSSProperties() 76 | .setWidth("40%") 77 | .setHeight("100%") 78 | .setDisplay(flex) 79 | .setFlexDirection(column) 80 | .setJustifyContent(center) 81 | .setAlignItems(center) 82 | .set( 83 | theme.breakpoints.down(typings.materialUiCore.materialUiCoreStrings.md), 84 | CSSProperties() 85 | .setWidth("50%") 86 | ) 87 | ) 88 | .add( 89 | "form", 90 | theme => 91 | CSSProperties() 92 | .setWidth(320) 93 | ) 94 | .add( 95 | "tab", 96 | theme => 97 | CSSProperties() 98 | .setFontWeight(400) 99 | .setFontSize(18) 100 | ) 101 | .add( 102 | "greeting", 103 | theme => 104 | CSSProperties() 105 | .setFontWeight(500) 106 | .setTextAlign(center) 107 | .setMarginTop(theme.setSpacing(Spacing(4)).spacing.unit) 108 | ) 109 | .add( 110 | "subGreeting", 111 | theme => 112 | CSSProperties() 113 | .setFontWeight(500) 114 | .setTextAlign(center) 115 | .setMarginTop(theme.setSpacing(Spacing(2)).spacing.unit) 116 | ) 117 | .add( 118 | "googleButton", 119 | theme => 120 | CSSProperties() 121 | .setMarginTop(theme.setSpacing(Spacing(6)).spacing.unit) 122 | //.setBoxShadow( theme.customShadows.widget) 123 | .setBackgroundColor("white") 124 | .setWidth("100%") 125 | .setTextTransform(none) 126 | ) 127 | .add( 128 | "googleButtonCreating", 129 | theme => 130 | CSSProperties() 131 | .setMarginTop(0) 132 | ) 133 | .add( 134 | "googleIcon", 135 | theme => 136 | CSSProperties() 137 | .setWidth(30) 138 | .setMarginRight(theme.setSpacing(Spacing(2)).spacing.unit) 139 | ) 140 | .add( 141 | "creatingButtonContainer", 142 | theme => 143 | CSSProperties() 144 | .setMarginTop(theme.setSpacing(Spacing(2.5)).spacing.unit) 145 | .setHeight(46) 146 | .setDisplay(flex) 147 | .setJustifyContent(center) 148 | .setAlignItems(center) 149 | ) 150 | .add( 151 | "createAccountButton", 152 | theme => 153 | CSSProperties() 154 | .setHeight(46) 155 | .setTextTransform(none) 156 | ) 157 | .add( 158 | "formDividerContainer", 159 | theme => 160 | CSSProperties() 161 | .setMarginTop(theme.setSpacing(Spacing(4)).spacing.unit) 162 | .setMarginBottom(theme.setSpacing(Spacing(4)).spacing.unit) 163 | .setDisplay(flex) 164 | .setAlignItems(center) 165 | ) 166 | .add( 167 | "formDividerWord", 168 | theme => 169 | CSSProperties() 170 | .setPaddingLeft(theme.setSpacing(Spacing(2)).spacing.unit) 171 | .setPaddingRight(theme.setSpacing(Spacing(2)).spacing.unit) 172 | ) 173 | .add( 174 | "formDivider", 175 | theme => 176 | CSSProperties() 177 | .setFlexGrow(1) 178 | .setHeight(1) 179 | .setBackgroundColor(theme.palette.text.hint) 180 | ) 181 | .add( 182 | "errorMessage", 183 | theme => 184 | CSSProperties() 185 | .setTextAlign(center) 186 | ) 187 | .add( 188 | "textFieldUnderline", 189 | theme => 190 | CSSProperties() 191 | .set( 192 | "&:before", 193 | CSSProperties() 194 | .setBorderBottomColor(theme.palette.primary.light) 195 | ) 196 | .set( 197 | "&:after", 198 | CSSProperties() 199 | .setBorderBottomColor(theme.palette.primary.main) 200 | ) 201 | .set( 202 | "&:hover:before", 203 | CSSProperties() 204 | .setBorderBottomColor(s"${theme.palette.primary.light} !important") 205 | ) 206 | ) 207 | .add( 208 | "textField", 209 | theme => 210 | CSSProperties() 211 | .setBorderBottomColor(theme.palette.background.default) 212 | ) 213 | .add( 214 | "formButtons", 215 | theme => 216 | CSSProperties() 217 | .setWidth("100%") 218 | .setMarginTop(theme.setSpacing(Spacing(4)).spacing.unit) 219 | .setDisplay(flex) 220 | .setJustifyContent("space-between") 221 | .setAlignItems(center) 222 | ) 223 | .add( 224 | "forgetButton", 225 | theme => 226 | CSSProperties() 227 | .setTextTransform(none) 228 | .setFontWeight(400) 229 | ) 230 | .add( 231 | "loginLoader", 232 | theme => 233 | CSSProperties() 234 | .setMarginLeft(theme.setSpacing(Spacing(4)).spacing.unit) 235 | ) 236 | .add( 237 | "copyright", 238 | theme => 239 | CSSProperties() 240 | .setMarginTop(theme.setSpacing(Spacing(4)).spacing.unit) 241 | .setWhiteSpace(nowrap) 242 | .set( 243 | theme.breakpoints.up(typings.materialUiCore.materialUiCoreStrings.md), 244 | CSSProperties() 245 | .setPosition(absolute) 246 | .setBottom(theme.setSpacing(Spacing(2)).spacing.unit) 247 | ) 248 | ) 249 | .hook 250 | end Styles 251 | -------------------------------------------------------------------------------- /material-ui/src/main/scala/demo/signin/SignIn.scala: -------------------------------------------------------------------------------- 1 | package demo.signin 2 | 3 | import demo.StyleBuilder 4 | import japgolly.scalajs.react.ScalaFnComponent 5 | import japgolly.scalajs.react.vdom.html_<^.* 6 | import typings.csstype.csstypeStrings.* 7 | import typings.materialUiCore.components.* 8 | import typings.materialUiCore.stylesCreateMuiThemeMod.Theme 9 | import typings.materialUiCore.materialUiCoreStrings.{contained, normal, primary, submit} 10 | import typings.materialUiCore.typographyTypographyMod.Style 11 | import typings.materialUiIcons.components as Icons 12 | import typings.materialUiStyles.makeStylesMod.StylesHook 13 | import typings.materialUiStyles.withStylesMod.{CSSProperties, Styles} 14 | 15 | import scala.scalajs.js 16 | 17 | // https://v3.material-ui.com/getting-started/page-layout-examples/sign-in/ 18 | // https://github.com/mui-org/material-ui/blob/v3.x/docs/src/pages/getting-started/page-layout-examples/sign-in/SignIn.js 19 | object SignIn: 20 | 21 | lazy val styles: StylesHook[Styles[Theme, js.Object, String]] = 22 | StyleBuilder[Theme, js.Object] 23 | .add( 24 | "main", 25 | theme => 26 | CSSProperties() 27 | .setWidth("auto") 28 | .setDisplay(block) 29 | .setMarginLeft(theme.spacing.unit * 3) 30 | .setMarginRight(theme.spacing.unit * 3) 31 | .set( 32 | theme.breakpoints.up(400 + theme.spacing.unit * 2 * 2), 33 | CSSProperties() 34 | .setWidth(400) 35 | .setMarginLeft("auto") 36 | .setMarginRight("auto") 37 | ) 38 | ) 39 | .add( 40 | "paper", 41 | theme => 42 | CSSProperties() 43 | .setMarginTop(theme.spacing.unit * 8) 44 | .setDisplay(flex) 45 | .setFlexDirection(column) 46 | .setAlignItems(center) 47 | .setPadding(s"${theme.spacing.unit * 2}px ${theme.spacing.unit * 3}px ${theme.spacing.unit * 3}px") 48 | ) 49 | .add( 50 | "avatar", 51 | theme => 52 | CSSProperties() 53 | .setMargin(theme.spacing.unit) 54 | .setBackgroundColor(theme.palette.secondary.main) 55 | ) 56 | .add("form", theme => CSSProperties().setWidth("100%").setMarginTop(theme.spacing.unit)) 57 | .add("submit", theme => CSSProperties().setMarginTop(theme.spacing.unit * 3)) 58 | .hook 59 | 60 | val component = ScalaFnComponent[Unit] { case () => 61 | val classes = styles(js.undefined) 62 | 63 | <.main(^.className := classes("main"))( 64 | CssBaseline(), 65 | Paper.className(classes("paper"))( 66 | Avatar.className(classes("avatar"))(Icons.LockOutlined()), 67 | Typography.variant(Style.h5).component("h1")("Sign in"), 68 | <.form(^.className := classes("form"))( 69 | FormControl 70 | .margin(normal) 71 | .required(true) 72 | .fullWidth(true)( 73 | InputLabel.htmlFor("email")("Email Address"), 74 | Input.id("email").name("email").autoComplete("email").autoFocus(true) 75 | ), 76 | FormControl 77 | .margin(normal) 78 | .required(true) 79 | .fullWidth(true)( 80 | InputLabel.htmlFor("password")("Password"), 81 | Input.id("password").name("password").autoComplete("current-password") 82 | ), 83 | FormControlLabel(Checkbox.value("remember").color(primary)).label("Remember Me"), 84 | Button 85 | .`type`(submit) 86 | .fullWidth(true) 87 | .variant(contained) 88 | .color(primary) 89 | .className(classes("submit"))("Sign in") 90 | ) 91 | ) 92 | ) 93 | } 94 | end SignIn 95 | -------------------------------------------------------------------------------- /nivo/src/main/js/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "japan", 4 | "color": "hsl(71, 70%, 50%)", 5 | "data": [ 6 | { 7 | "x": "plane", 8 | "y": 110 9 | }, 10 | { 11 | "x": "helicopter", 12 | "y": 224 13 | }, 14 | { 15 | "x": "boat", 16 | "y": 190 17 | }, 18 | { 19 | "x": "train", 20 | "y": 100 21 | }, 22 | { 23 | "x": "subway", 24 | "y": 116 25 | }, 26 | { 27 | "x": "bus", 28 | "y": 204 29 | }, 30 | { 31 | "x": "car", 32 | "y": 60 33 | }, 34 | { 35 | "x": "moto", 36 | "y": 192 37 | }, 38 | { 39 | "x": "bicycle", 40 | "y": 137 41 | }, 42 | { 43 | "x": "horse", 44 | "y": 265 45 | }, 46 | { 47 | "x": "skateboard", 48 | "y": 94 49 | }, 50 | { 51 | "x": "others", 52 | "y": 175 53 | } 54 | ] 55 | }, 56 | { 57 | "id": "france", 58 | "color": "hsl(203, 70%, 50%)", 59 | "data": [ 60 | { 61 | "x": "plane", 62 | "y": 184 63 | }, 64 | { 65 | "x": "helicopter", 66 | "y": 60 67 | }, 68 | { 69 | "x": "boat", 70 | "y": 295 71 | }, 72 | { 73 | "x": "train", 74 | "y": 155 75 | }, 76 | { 77 | "x": "subway", 78 | "y": 237 79 | }, 80 | { 81 | "x": "bus", 82 | "y": 210 83 | }, 84 | { 85 | "x": "car", 86 | "y": 242 87 | }, 88 | { 89 | "x": "moto", 90 | "y": 288 91 | }, 92 | { 93 | "x": "bicycle", 94 | "y": 178 95 | }, 96 | { 97 | "x": "horse", 98 | "y": 95 99 | }, 100 | { 101 | "x": "skateboard", 102 | "y": 253 103 | }, 104 | { 105 | "x": "others", 106 | "y": 91 107 | } 108 | ] 109 | }, 110 | { 111 | "id": "us", 112 | "color": "hsl(33, 70%, 50%)", 113 | "data": [ 114 | { 115 | "x": "plane", 116 | "y": 215 117 | }, 118 | { 119 | "x": "helicopter", 120 | "y": 188 121 | }, 122 | { 123 | "x": "boat", 124 | "y": 45 125 | }, 126 | { 127 | "x": "train", 128 | "y": 299 129 | }, 130 | { 131 | "x": "subway", 132 | "y": 108 133 | }, 134 | { 135 | "x": "bus", 136 | "y": 221 137 | }, 138 | { 139 | "x": "car", 140 | "y": 276 141 | }, 142 | { 143 | "x": "moto", 144 | "y": 25 145 | }, 146 | { 147 | "x": "bicycle", 148 | "y": 5 149 | }, 150 | { 151 | "x": "horse", 152 | "y": 212 153 | }, 154 | { 155 | "x": "skateboard", 156 | "y": 244 157 | }, 158 | { 159 | "x": "others", 160 | "y": 191 161 | } 162 | ] 163 | }, 164 | { 165 | "id": "germany", 166 | "color": "hsl(53, 70%, 50%)", 167 | "data": [ 168 | { 169 | "x": "plane", 170 | "y": 70 171 | }, 172 | { 173 | "x": "helicopter", 174 | "y": 246 175 | }, 176 | { 177 | "x": "boat", 178 | "y": 15 179 | }, 180 | { 181 | "x": "train", 182 | "y": 36 183 | }, 184 | { 185 | "x": "subway", 186 | "y": 182 187 | }, 188 | { 189 | "x": "bus", 190 | "y": 234 191 | }, 192 | { 193 | "x": "car", 194 | "y": 246 195 | }, 196 | { 197 | "x": "moto", 198 | "y": 186 199 | }, 200 | { 201 | "x": "bicycle", 202 | "y": 11 203 | }, 204 | { 205 | "x": "horse", 206 | "y": 34 207 | }, 208 | { 209 | "x": "skateboard", 210 | "y": 262 211 | }, 212 | { 213 | "x": "others", 214 | "y": 209 215 | } 216 | ] 217 | }, 218 | { 219 | "id": "norway", 220 | "color": "hsl(28, 70%, 50%)", 221 | "data": [ 222 | { 223 | "x": "plane", 224 | "y": 166 225 | }, 226 | { 227 | "x": "helicopter", 228 | "y": 70 229 | }, 230 | { 231 | "x": "boat", 232 | "y": 14 233 | }, 234 | { 235 | "x": "train", 236 | "y": 90 237 | }, 238 | { 239 | "x": "subway", 240 | "y": 25 241 | }, 242 | { 243 | "x": "bus", 244 | "y": 280 245 | }, 246 | { 247 | "x": "car", 248 | "y": 276 249 | }, 250 | { 251 | "x": "moto", 252 | "y": 24 253 | }, 254 | { 255 | "x": "bicycle", 256 | "y": 263 257 | }, 258 | { 259 | "x": "horse", 260 | "y": 1 261 | }, 262 | { 263 | "x": "skateboard", 264 | "y": 210 265 | }, 266 | { 267 | "x": "others", 268 | "y": 113 269 | } 270 | ] 271 | } 272 | ] 273 | -------------------------------------------------------------------------------- /nivo/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Nivo demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /nivo/src/main/scala/demo/App.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.ScalaFnComponent 4 | import japgolly.scalajs.react.vdom.html_<^.* 5 | import org.scalablytyped.runtime.StringDictionary 6 | import typings.nivoAxes.mod.{AxisProps, Orient} 7 | import typings.nivoAxes.nivoAxesStrings.middle 8 | import typings.nivoColors.mod.{ColorSchemeId, OrdinalColorsInstruction, SchemeColorInstruction} 9 | import typings.nivoCore.mod.Box 10 | import typings.nivoLegends.anon.PartialitemTextColorstrin 11 | import typings.nivoLegends.mod.* 12 | import typings.nivoLine.components.Line 13 | import typings.nivoLine.mod.Serie 14 | import typings.nivoScales.mod.{LinearScale, Scale} 15 | import typings.nivoScales.nivoScalesStrings.auto 16 | 17 | import scala.scalajs.js 18 | 19 | // ported from https://nivo.rocks/line 20 | object App: 21 | //// make sure parent container have a defined height when using 22 | //// responsive component, otherwise height will be 0 and 23 | //// no chart will be rendered. 24 | //// website examples showcase many properties, 25 | //// you'll often use just a few of them. 26 | val component = ScalaFnComponent[js.Array[Serie]] { data => 27 | Line(data, height = 1000, width = 1500) 28 | .margin(Box().setTop(50).setRight(110).setBottom(50).setLeft(60)) 29 | .xScale(Scale.PointScale()) 30 | .yScale(LinearScale().setMin(auto).setMax(auto).setStacked(true).setReverse(false)) 31 | .axisTopNull 32 | .axisRightNull 33 | .axisBottom( 34 | AxisProps() 35 | .setOrient(Orient.bottom) 36 | .setTickSize(5) 37 | .setTickPadding(5) 38 | .setTickRotation(0) 39 | .setLegend("transportation") 40 | .setLegendOffset(36) 41 | .setLegendPosition(middle) 42 | ) 43 | .axisLeft( 44 | AxisProps() 45 | .setOrient(Orient.left) 46 | .setTickSize(5) 47 | .setTickPadding(5) 48 | .setTickRotation(0) 49 | .setLegend("count") 50 | .setLegendOffset(-40) 51 | .setLegendPosition(middle) 52 | ) 53 | .colors(SchemeColorInstruction(ColorSchemeId.nivo): OrdinalColorsInstruction[Any]) 54 | .pointSize(10) 55 | .pointColor(StringDictionary("theme" -> "background")) 56 | .pointBorderWidth(2) 57 | .pointBorderColor(StringDictionary("from" -> "serieColor")) 58 | .pointLabel("y") 59 | .pointLabelYOffset(-12) 60 | .useMesh(true) 61 | .legendsVarargs( 62 | LegendProps( 63 | anchor = LegendAnchor.`bottom-right`, 64 | direction = LegendDirection.column, 65 | itemHeight = 20, 66 | itemWidth = 80 67 | ).setJustify(false) 68 | .setTranslateX(100) 69 | .setTranslateY(0) 70 | .setItemsSpacing(0) 71 | .setItemDirection(LegendItemDirection.`left-to-right`) 72 | .setItemOpacity(0.75) 73 | .setSymbolSize(12) 74 | .setSymbolShape(LegendSymbolShape.circle) 75 | .setSymbolBorderColor("rgba(0, 0, 0, .5)") 76 | .setEffectsVarargs( 77 | LegendEffect(PartialitemTextColorstrin().setItemBackground("rgba(0, 0, 0, .03)").setItemOpacity(1)) 78 | ) 79 | ) 80 | } 81 | end App 82 | -------------------------------------------------------------------------------- /nivo/src/main/scala/demo/Main.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import org.scalajs.dom.document 4 | import typings.nivoLine.mod.Serie 5 | import typings.std.global.console 6 | 7 | import scala.scalajs.js 8 | import scala.scalajs.js.annotation.JSImport 9 | 10 | @js.native @JSImport("./data.json", JSImport.Namespace) 11 | val Data: js.Array[Serie] = js.native 12 | 13 | @main 14 | def main: Unit = 15 | console.warn(Data) 16 | App.component(Data).renderIntoDOM(document.getElementById("container")) 17 | -------------------------------------------------------------------------------- /office-ui-fabric-react/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | office-ui-fabric-react demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /office-ui-fabric-react/src/main/scala/demo/Demo.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.vdom.html_<^.* 4 | import japgolly.scalajs.react.{Callback, ScalaFnComponent} 5 | import org.scalajs.dom 6 | import typings.officeUiFabricReact.components as Fabric 7 | import typings.react.mod.useState 8 | 9 | import scala.scalajs.js 10 | 11 | val App = ScalaFnComponent[String] { name => 12 | /* use a hook to keep state */ 13 | val js.Tuple2(state, setState) = useState(1) 14 | 15 | val incrementButton = Fabric.Button.onClick(_ => Callback(setState(state + 1)))(s"Increment it, $name") 16 | val text = Fabric.TextField.value(state.toString).disabled(true) 17 | <.div(text, incrementButton) 18 | } 19 | 20 | @main 21 | def main: Unit = 22 | App("Dear user").renderIntoDOM(dom.document.getElementById("container")) 23 | -------------------------------------------------------------------------------- /project/ScalacOptions.scala: -------------------------------------------------------------------------------- 1 | object ScalacOptions { 2 | val flags = Seq( 3 | "-deprecation", // Emit warning and location for usages of deprecated APIs. 4 | "-feature", // Emit warning and location for usages of features that should be imported explicitly. 5 | "-unchecked" // Enable additional warnings where generated code depends on assumptions. 6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.9.0 -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.1") 2 | addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % "0.21.1") 3 | addSbtPlugin("org.scalablytyped.converter" % "sbt-converter" % "1.0.0-beta44") -------------------------------------------------------------------------------- /project/scalafmt.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.2.1") 2 | -------------------------------------------------------------------------------- /react-big-calendar/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-big-calendar demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /react-big-calendar/src/main/scala/demo/Main.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import org.scalajs.dom.document 4 | import typings.moment.{momentStrings, mod as Moment} 5 | import typings.reactBigCalendar.components.Calendar 6 | import typings.reactBigCalendar.mod.{momentLocalizer, View} 7 | 8 | import scala.scalajs.js 9 | import scala.scalajs.js.annotation.JSImport 10 | 11 | @JSImport("react-big-calendar/lib/css/react-big-calendar.css", JSImport.Namespace) 12 | @js.native 13 | object BigCalendarCss extends js.Object 14 | 15 | class Event(val start: js.Date, val end: js.Date, val title: js.UndefOr[String]) extends js.Object 16 | 17 | @main 18 | def main: Unit = 19 | BigCalendarCss // touch to load css 20 | 21 | val Localizer = momentLocalizer(Moment.^) 22 | 23 | val someEvent = new Event( 24 | start = new js.Date, 25 | end = Moment.apply(new js.Date).add(1, momentStrings.day).toDate(), 26 | title = "My amazing event" 27 | ) 28 | Calendar[Event, js.Object](Localizer) 29 | .eventsVarargs(someEvent) 30 | .defaultDate(new js.Date) 31 | .defaultView(View.week) 32 | .viewsVarargs(View.agenda, View.day, View.week) 33 | .renderIntoDOM(document.getElementById("container")) 34 | end main 35 | -------------------------------------------------------------------------------- /react-dnd/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-dnd demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /react-dnd/src/main/scala/demo/App.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.facade.React.{Element, RefFn, RefHandle} 4 | import japgolly.scalajs.react.vdom.Attr.ValueType 5 | import japgolly.scalajs.react.vdom.TopNode 6 | import japgolly.scalajs.react.vdom.html_<^.* 7 | import japgolly.scalajs.react.{Callback, CallbackTo, ScalaFnComponent} 8 | import org.scalajs.dom.HTMLElement 9 | import typings.csstype.mod.{ClearProperty, FloatProperty, TextAlignProperty} 10 | import typings.dndCore.libInterfacesMod.SourceType 11 | import typings.react.mod.CSSProperties 12 | import typings.reactDnd.components.DndProvider 13 | import typings.reactDnd.libInterfacesConnectorsMod.{ConnectDropTarget, ConnectableElement} 14 | import typings.reactDnd.libInterfacesHooksApiMod.{DragSourceHookSpec, DropTargetHookSpec} 15 | import typings.reactDnd.mod.{useDrag, useDrop} 16 | import typings.reactDndHtml5Backend.mod.HTML5Backend 17 | import typings.std.global.alert 18 | 19 | import scala.language.implicitConversions 20 | import scala.scalajs.js 21 | import scala.scalajs.js.| 22 | 23 | object components: 24 | object ItemTypes: 25 | val BOX = "box" 26 | 27 | implicit def whyyyDoesItHaveToBeSoComplicated: ValueType[HTMLElement => Callback, RefFn[HTMLElement]] = 28 | ValueType[HTMLElement => Callback, RefFn[HTMLElement]] { (consume, f) => 29 | val massaged: RefFn[HTMLElement] = 30 | (e: HTMLElement | Null) => Option(e.asInstanceOf[HTMLElement]).foreach(e => f(e).runNow()) 31 | 32 | consume(massaged) 33 | } 34 | 35 | case class Collected(isOver: Boolean, canDrop: Boolean) 36 | 37 | class DropResult(val name: String) extends js.Object 38 | 39 | val dustbinStyles = CSSProperties() 40 | .setHeight("12rem") 41 | .setWidth("12rem") 42 | .setMarginRight("1.5rem") 43 | .setMarginBottom("1.5rem") 44 | .setColor("white") 45 | .setPadding("1rem") 46 | .setTextAlign(TextAlignProperty.center) 47 | .setFontSize("1rem") 48 | .setLineHeight("normal") 49 | .setFloat(FloatProperty.left) 50 | 51 | val Dustbin = ScalaFnComponent[Unit] { case () => 52 | val js.Tuple2(Collected(canDrop, isOver), drop) = 53 | useDrop( 54 | DropTargetHookSpec[js.Object, DropResult, Collected](ItemTypes.BOX) 55 | .setDrop((_, _) => new DropResult("Dustbin")) 56 | .setCollect(monitor => Collected(monitor.isOver(), monitor.canDrop())) 57 | ) 58 | 59 | val isActive = canDrop && isOver 60 | 61 | val backgroundColor: String = 62 | if isActive then "darkgreen" 63 | else if canDrop then "darkkhaki" 64 | else "#222" 65 | 66 | <.div( 67 | ^.untypedRef(elem => drop(elem.asInstanceOf[ConnectableElement], js.undefined)), 68 | ^.style := dustbinStyles.duplicate.setBackgroundColor(backgroundColor), 69 | if isActive then "Release to drop" else "Drag a box here" 70 | ) 71 | } 72 | 73 | class Dragged(val name: String, val `type`: SourceType) extends js.Object 74 | 75 | val boxStyles = CSSProperties() 76 | .setBorder("1px dashed gray") 77 | .setBackgroundColor("white") 78 | .setPadding("0.5rem 1rem") 79 | .setMarginRight("1.5rem") 80 | .setMarginBottom("1.5rem") 81 | .setCursor("move") 82 | .setFloat(FloatProperty.left) 83 | 84 | val Box = ScalaFnComponent[String] { name => 85 | val js.Tuple3(isDragging, drag, _) = 86 | useDrag( 87 | DragSourceHookSpec[Dragged, DropResult, Boolean](item = new Dragged(name, ItemTypes.BOX)) 88 | .setEnd { (itemU, monitor) => 89 | Callback(itemU.foreach { item => 90 | val dropResult = monitor.getDropResult() 91 | alert(s"You dropped ${item.name} into ${dropResult.asInstanceOf[DropResult].name}!") 92 | }) 93 | } 94 | .setCollect(monitor => monitor.isDragging()) 95 | ) 96 | 97 | val opacity = if isDragging then "0.4" else "1" 98 | <.div( 99 | ^.untypedRef(elem => drag(elem.asInstanceOf[ConnectableElement], js.undefined)), 100 | ^.style := boxStyles.duplicate.setOpacity(opacity), 101 | name 102 | ) 103 | } 104 | val Container = ScalaFnComponent[Unit] { case () => 105 | <.div( 106 | <.div(^.style := CSSProperties().setOverflow("hidden").setClear(ClearProperty.both), Dustbin()), 107 | <.div( 108 | ^.style := CSSProperties().setOverflow("hidden").setClear(ClearProperty.both), 109 | Box("Glass"), 110 | Box("Banana"), 111 | Box("Paper") 112 | ) 113 | ) 114 | } 115 | 116 | val App = ScalaFnComponent[Unit] { case () => 117 | <.div(^.className := "App")( 118 | DndProvider.Backend(HTML5Backend)(Container()) 119 | ) 120 | } 121 | end components 122 | -------------------------------------------------------------------------------- /react-dnd/src/main/scala/demo/Main.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import org.scalajs.dom 4 | 5 | @main 6 | def main: Unit = 7 | components.App().renderIntoDOM(dom.document.getElementById("container")) 8 | -------------------------------------------------------------------------------- /react-i18n/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React i18n demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /react-i18n/src/main/scala/demo/App.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.vdom.html_<^.* 4 | import japgolly.scalajs.react.{Callback, ScalaFnComponent} 5 | import org.scalajs.dom.document 6 | import org.scalajs.dom.html.Element 7 | import typings.react.mod.CSSProperties 8 | import typings.reactI18next.components.Trans 9 | import typings.reactI18next.mod.useTranslation 10 | 11 | import scala.scalajs.js 12 | 13 | val App = ScalaFnComponent[Unit] { _ => 14 | val js.Tuple3(t, i18n, _) = useTranslation() 15 | val index = 11 16 | 17 | // note, explicit type parameters seem to be necessary below. didn't investigate why 18 | 19 | <.div(^.className := "App")( 20 | <.div(^.className := "App-header")( 21 | // note: `t` on the line under needs type parameters in order to not run into a `ClassCastException`. 22 | // Better write a small facade around it to constrain the interface a bit if you want to use it 23 | <.h2(t[String, String, js.Object]("Welcome to React")), 24 | <.button(^.onClick --> Callback(i18n.changeLanguage(I18n.de)), "de"), 25 | <.button(^.onClick --> Callback(i18n.changeLanguage(I18n.en)), "en") 26 | ), 27 | <.div(^.className := "App-intro")( 28 | Trans[Element]()("To get started, edit ", <.code("src/App.js"), " and save to reload."), 29 | Trans[Element]().i18nKey("welcome")("trans"), 30 | Trans[Element]()(index + 1, <.a("xxx")) 31 | ), 32 | <.div(^.style := CSSProperties().setMarginTop(40))( 33 | "Learn more ", 34 | <.a(^.href := "https://react.i18next.com")("https://react.i18next.com") 35 | ) 36 | ) 37 | } 38 | 39 | @main 40 | def main: Unit = 41 | I18n.initialize() 42 | App().renderIntoDOM(document.getElementsByTagName("body")(0)) 43 | -------------------------------------------------------------------------------- /react-i18n/src/main/scala/demo/I18n.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import org.scalablytyped.runtime.StringDictionary 4 | import typings.i18next.i18nextBooleans.`false` 5 | import typings.i18next.mod.{InitOptions, InterpolationOptions, default as i18n} 6 | import typings.i18nextBrowserLanguagedetector.mod.default as LanguageDetector 7 | import typings.reactI18next.mod.initReactI18next 8 | 9 | import scala.scalajs.js 10 | 11 | object I18n: 12 | 13 | val namespace = "translations" 14 | 15 | val en = "en" 16 | private val enTexts = StringDictionary[js.Any]( 17 | "To get started, edit <1>src/App.js and save to reload." -> "To get started, edit <1>src/App.js and save to reload.", 18 | "Welcome to React" -> "Welcome to React and react-i18next", 19 | "welcome" -> "Hello
World" 20 | ) 21 | 22 | val de = "de" 23 | private val deTexts = StringDictionary[js.Any]( 24 | "To get started, edit <1>src/App.js and save to reload." -> "Starte in dem du, <1>src/App.js editierst und speicherst.", 25 | "Welcome to React" -> "Willkommen bei React und react-i18next", 26 | "welcome" -> "Hello
World" 27 | ) 28 | 29 | def initialize() = 30 | i18n 31 | .use(new LanguageDetector) 32 | .use(initReactI18next) 33 | .init( 34 | InitOptions() 35 | .setResources( 36 | StringDictionary( 37 | en -> StringDictionary(namespace -> enTexts), 38 | de -> StringDictionary(namespace -> deTexts) 39 | ) 40 | ) 41 | .setFallbackLng(en) 42 | .setDebug(true) 43 | .setDefaultNS(namespace) 44 | .setKeySeparator(`false`) 45 | .setInterpolation(InterpolationOptions().setEscapeValue(false)) 46 | ) 47 | end I18n 48 | -------------------------------------------------------------------------------- /react-leaflet/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-leaflet demo 6 | 7 | 8 |
9 | 10 | 11 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /react-leaflet/src/main/scala/demo/App.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.ScalaFnComponent 4 | import japgolly.scalajs.react.vdom.html_<^.* 5 | import typings.leaflet.mod.LatLngExpression 6 | import typings.reactLeaflet.components.{Map, Marker, Popup, TileLayer} 7 | import typings.reactLeaflet.mod.{MapProps, MarkerProps, PopupProps, TileLayerProps} 8 | 9 | import scala.language.implicitConversions 10 | import scala.scalajs.js 11 | 12 | val App = ScalaFnComponent[Unit] { _ => 13 | val position: LatLngExpression = js.Tuple2(51.505, -0.09) 14 | 15 | Map(MapProps().setCenter(position).setZoom(13))( 16 | TileLayer( 17 | TileLayerProps(url = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png") 18 | .setAttribution("© OpenStreetMap contributors") 19 | ), 20 | Marker(MarkerProps(position = position))( 21 | Popup(PopupProps())("A pretty CSS3 popup.\nEasily customizable.").build 22 | ) 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /react-leaflet/src/main/scala/demo/Main.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import org.scalajs.dom 4 | 5 | import scala.scalajs.js 6 | import scala.scalajs.js.annotation.JSImport 7 | 8 | @JSImport("./node_modules/leaflet/dist/leaflet.css", JSImport.Namespace) 9 | @js.native 10 | object Css extends js.Object 11 | 12 | @main 13 | def main: Unit = 14 | /* touch to load */ 15 | typings.leaflet.leafletRequire 16 | Css 17 | 18 | App().renderIntoDOM(dom.document.getElementById("container")) 19 | -------------------------------------------------------------------------------- /react-markdown/src/main/js/docs/README.md: -------------------------------------------------------------------------------- 1 | # Working with objects 2 | 3 | Javascript is remarkably flexible. When we integrate with arbitrary Javascript code in Scala.js, we need a very flexible 4 | encoding to tag along. The encoding chosen for ScalablyTyped is the result of years of experimentation, and has 5 | a much more dynamic feeling than what you may be used to. 6 | 7 | Let's start with an example of a type definition we want to use: 8 | 9 | ```scala 10 | @js.native 11 | trait Point extends StObject { 12 | 13 | var x: Double = js.native 14 | 15 | var y: Double = js.native 16 | } 17 | object Point { 18 | 19 | @scala.inline 20 | def apply(x: Double, y: Double): Point = { 21 | val __obj = js.Dynamic.literal(x = x.asInstanceOf[js.Any], y = y.asInstanceOf[js.Any]) 22 | __obj.asInstanceOf[Point] 23 | } 24 | 25 | @scala.inline 26 | implicit class PointMutableBuilder[Self <: Point] (val x: Self) extends AnyVal { 27 | 28 | @scala.inline 29 | def setX(value: Double): Self = StObject.set(x, "x", value.asInstanceOf[js.Any]) 30 | 31 | @scala.inline 32 | def setY(value: Double): Self = StObject.set(x, "y", value.asInstanceOf[js.Any]) 33 | } 34 | } 35 | ``` 36 | 37 | We notice several things: 38 | - it's a `@js.native` trait, so we cannot `new` it ourselves. This can be [`changed`](conversion-options.md#stenablescalajsdefined), but it's not recommended. 39 | - it has two required members (`x` and `y`). Optional members would typically be wrapped in `js.UndefOr` 40 | - it has an `object` with syntax to help us work with it 41 | - the entire syntax is built on mutability. It's Javascript, after all. more on that further down 42 | 43 | ### Basic usage 44 | 45 | ```scala 46 | // At construction time we need to supply all required parameters 47 | val p = Point(x = 1,y = 2) 48 | 49 | // we can mutate what we have 50 | // this is equivalent to typescript `p.x = 3 51 | val p2 = p.setX(3) 52 | 53 | // or we can duplicate and then mutate. 54 | // this is equivalent to typescript `const p2 = {...p, x: 3} 55 | val p3 = p.duplicate.setX(3) 56 | 57 | // we can combine with other javascript objects. 58 | // this is equivalent to javascript `const p3 = {...p, {}}` 59 | val p4: Point with TickOptions = p.combineWith(TickOptions()) 60 | 61 | // fallback, if the type definitions are wrong or for any other reason you can break the contract 62 | val p5: p.duplicate.set("x", "foo") 63 | 64 | // you can also set any other property 65 | val p6: p.duplicate.set("x2", "foo") 66 | ``` 67 | -------------------------------------------------------------------------------- /react-markdown/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-markdown demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /react-markdown/src/main/scala/demo/DocPage.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.component.ScalaFn.Component 4 | import japgolly.scalajs.react.facade.React.{ElementType, Node} 5 | import japgolly.scalajs.react.vdom.html_<^.* 6 | import japgolly.scalajs.react.{CtorType, ScalaFnComponent} 7 | import org.scalablytyped.runtime.StringDictionary 8 | import org.scalajs.dom.XMLHttpRequest 9 | import typings.react.mod.{useEffect, useState, EffectCallback} 10 | import typings.reactMarkdown.components.ReactMarkdown 11 | import typings.reactMarkdown.mod.{ReactMarkdownProps, ReactMarkdownPropsBase} 12 | import typings.reactSyntaxHighlighter.components.Light as SyntaxHighligther 13 | import typings.reactSyntaxHighlighter.mod.Light 14 | import typings.reactSyntaxHighlighter.distEsmStylesHljsMod.darcula 15 | import scala.scalajs.js 16 | 17 | val docFile = "./docs/README.md" 18 | 19 | class LanguageValue(val language: String, val value: String) extends js.Object 20 | 21 | val codeRender: js.Function1[LanguageValue, Node] = 22 | rp => SyntaxHighligther.style(darcula).language(rp.language)(rp.value).build.rawElement 23 | 24 | val DocPage: Component[Unit, CtorType.Nullary] = ScalaFnComponent { case () => 25 | val js.Tuple2(document, setDocument) = useState[Option[String]](None) 26 | 27 | useEffect( 28 | (() => 29 | val xhr = new XMLHttpRequest 30 | xhr.onload = _ => setDocument(Some(xhr.responseText)) 31 | xhr.open("GET", docFile) 32 | xhr.send() 33 | ): EffectCallback, 34 | js.Array(docFile) 35 | ) 36 | 37 | val props = ReactMarkdownPropsBase() 38 | .setRenderers(StringDictionary("code" -> codeRender).asInstanceOf[StringDictionary[ElementType]]) 39 | .asInstanceOf[ReactMarkdownProps] 40 | 41 | ReactMarkdown(props)(document) 42 | } 43 | -------------------------------------------------------------------------------- /react-markdown/src/main/scala/demo/Main.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import org.scalajs.dom 4 | import typings.reactSyntaxHighlighter.mod.Light 5 | import typings.reactSyntaxHighlighter.distEsmLanguagesHljsScalaMod.default as scalaLanguage 6 | 7 | // https://stackblitz.com/edit/react-syntax-highlighter-issue-js 8 | // https://github.com/remarkjs/react-markdown#use-custom-renderers-syntax-highlight 9 | @main 10 | def main: Unit = 11 | Light.registerLanguage("scala", scalaLanguage) 12 | DocPage().renderIntoDOM(dom.document.getElementById("container")) 13 | -------------------------------------------------------------------------------- /react-mobx/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /react-mobx/src/main/scala/demo/Main.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import org.scalajs.dom.document 4 | 5 | @main 6 | def main: Unit = 7 | TodoList(TodoListProps(new TodoStore, new PeopleStore)) 8 | .renderIntoDOM(document.getElementsByTagName("body")(0)) 9 | -------------------------------------------------------------------------------- /react-mobx/src/main/scala/demo/ObserverComponent.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.component.JsFn 4 | import japgolly.scalajs.react.component.ScalaFn.Component 5 | import japgolly.scalajs.react.internal.Box 6 | import japgolly.scalajs.react.vdom.VdomElement 7 | import japgolly.scalajs.react.{facade, Children, CtorType, PropsChildren} 8 | import typings.mobxReact.mod.observer 9 | 10 | import scala.scalajs.js 11 | 12 | object ObserverComponent: 13 | private def create[P, C <: Children, CT[-p, +u] <: CtorType[p, u]]( 14 | render: Box[P] with facade.PropsWithChildren => VdomElement 15 | )(implicit s: CtorType.Summoner.Aux[Box[P], C, CT]): Component[P, CT] = 16 | 17 | val jsRender = 18 | render.andThen(_.rawElement): js.Function1[Box[P] with facade.PropsWithChildren, facade.React.Element] 19 | val rawComponent = observer(jsRender).asInstanceOf[facade.React.StatelessFunctionalComponent[Box[P]]] 20 | 21 | JsFn 22 | .force[Box[P], C](rawComponent)(s) 23 | .cmapCtorProps[P](Box(_)) 24 | .mapUnmounted(_.mapUnmountedProps(_.unbox)) 25 | end create 26 | 27 | def apply[P](render: P => VdomElement)(implicit s: CtorType.Summoner[Box[P], Children.None]): Component[P, s.CT] = 28 | create[P, Children.None, s.CT](b => render(b.unbox))(s) 29 | 30 | def withChildren[P]( 31 | render: (P, PropsChildren) => VdomElement 32 | )(implicit s: CtorType.Summoner[Box[P], Children.Varargs]): Component[P, s.CT] = 33 | create[P, Children.Varargs, s.CT](b => render(b.unbox, PropsChildren(b.children)))(s) 34 | 35 | def justChildren(render: PropsChildren => VdomElement): Component[Unit, CtorType.Children] = 36 | create(b => render(PropsChildren(b.children))) 37 | end ObserverComponent 38 | -------------------------------------------------------------------------------- /react-mobx/src/main/scala/demo/PeopleStore.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import typings.mobx.mod as MobX 4 | import typings.mobx.libTypesObservablevalueMod.IObservableValue 5 | 6 | import scala.scalajs.js 7 | 8 | case class Person(name: String): 9 | def changeName(n: String): Person = Person(n) 10 | 11 | class PeopleStore: 12 | 13 | val people: IObservableValue[List[Person]] = 14 | MobX.observable.box(List(Person("Michel"), Person("Me"))) 15 | 16 | def changePeople(f: List[Person] => List[Person]): Unit = people.set(f(people.get())) 17 | 18 | def updatePerson(index: Int, f: Person => Person): Unit = changePeople(_.updated(index, f(people.get()(index)))) 19 | 20 | val renamePerson: js.Function2[Int, String, Unit] = 21 | MobX.action("renamePerson", (index: Int, name: String) => updatePerson(index, _.changeName(name))) 22 | end PeopleStore 23 | -------------------------------------------------------------------------------- /react-mobx/src/main/scala/demo/TodoList.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.vdom.html_<^.* 4 | import japgolly.scalajs.react.{Callback, CallbackTo} 5 | import org.scalajs.dom.window 6 | import typings.react.mod.useEffect 7 | 8 | import scala.scalajs.js 9 | 10 | case class TodoListProps(store: TodoStore, peopleStore: PeopleStore) 11 | 12 | val TodoList = ObserverComponent { case TodoListProps(store, peopleStore) => 13 | useEffect( 14 | () => 15 | store.assignTodo(0, Some(peopleStore.people.get()(0))) 16 | store.assignTodo(1, Some(peopleStore.people.get()(1))) 17 | peopleStore.renamePerson(0, "Michel Weststrate") 18 | , 19 | js.Array() // run an effect and clean it up only once (on mount and unmount) 20 | ) 21 | 22 | val onNewTodo: Callback = CallbackTo(window.prompt("Task name", "coffee plz")) flatMap { 23 | case e if e.isEmpty => Callback.empty 24 | case task => Callback(store.addTodo(task)) 25 | } 26 | 27 | val onLoadTodo = Callback { 28 | store.increasePending() 29 | window.setTimeout( 30 | () => 31 | store.addTodo("Random Todo " + Math.random()) 32 | store.decreasePending() 33 | , 34 | 2000 35 | ) 36 | } 37 | 38 | val todoViews = TagMod.fromTraversableOnce { 39 | val ts = store.todos.get().todos 40 | ts.indices.map(index => 41 | TodoView.withKey("td" + index)( 42 | TodoViewProps( 43 | todo = ts(index), 44 | toggle = Callback(store.toggleTodo(index)), 45 | rename = task => Callback(store.renameTodo(index, task)) 46 | ) 47 | ) 48 | ) 49 | } 50 | 51 | <.div( 52 | store.report.get(), 53 | <.ul(todoViews), 54 | <.ul( 55 | store.pendingRequests.get().c match 56 | case 0 => <.div() 57 | case _ => <.li("Loading...") 58 | ), 59 | <.button(^.onClick --> onNewTodo)("New Todo"), 60 | <.small("double-click a todo to edit"), 61 | <.div( 62 | <.button(^.onClick --> onLoadTodo.void)("Load async Todo") 63 | ) 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /react-mobx/src/main/scala/demo/TodoStore.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import typings.mobx.libCoreComputedvalueMod.IComputedValue 4 | import typings.mobx.mod as MobX 5 | import typings.mobx.libTypesObservablevalueMod.IObservableValue 6 | 7 | import scala.scalajs.js 8 | 9 | case class Todo(task: String, completed: Boolean = false, assignee: Option[Person] = None): 10 | 11 | def renameTask(t: String): Todo = Todo(t, completed, assignee) 12 | 13 | def toggleCompleted: Todo = Todo(task, !completed, assignee) 14 | 15 | def changeAssignee(a: Option[Person]): Todo = Todo(task, completed, a) 16 | 17 | object Todo: 18 | 19 | def fromTask(task: String): Todo = Todo(task) 20 | 21 | case class Todos(todos: List[Todo]): 22 | 23 | def completedCount(): Int = todos.count(_.completed == true) 24 | 25 | def findUncompleted(): Option[Todo] = todos.find(_.completed == false) 26 | 27 | def addNewTask(task: String): Todos = Todos(todos :+ Todo.fromTask(task)) 28 | 29 | case class PendingRequests(c: Int): 30 | 31 | def increase: PendingRequests = PendingRequests(c + 1) 32 | 33 | def decrease: PendingRequests = PendingRequests(c - 1) 34 | 35 | class TodoStore: 36 | 37 | val todos: IObservableValue[Todos] = 38 | MobX.observable.box(Todos(List(Todo("read MobX tutorial"), Todo("try MobX")))) 39 | 40 | val pendingRequests: IObservableValue[PendingRequests] = 41 | MobX.observable.box(PendingRequests(0)) 42 | 43 | val report: IComputedValue[String] = 44 | MobX.computed { () => 45 | val ts = todos.get() 46 | "Next todo: " + (ts.findUncompleted() match 47 | case Some(nextTodo) => nextTodo.task 48 | case None => "" 49 | ) + ". Progress: " + ts.completedCount() + "/" + ts.todos.length 50 | } 51 | 52 | def changeTodos(f: Todos => Todos): Unit = todos.set(f(todos.get())) 53 | 54 | def updateTodo(index: Int, f: Todo => Todo): Unit = 55 | changeTodos(t => Todos(t.todos.updated(index, f(todos.get().todos(index))))) 56 | 57 | val addTodo: js.Function1[String, Unit] = 58 | MobX.action("addTodo", (task: String) => changeTodos(_.addNewTask(task))) 59 | 60 | val toggleTodo: js.Function1[Int, Unit] = 61 | MobX.action("toggleTodo", (index: Int) => updateTodo(index, _.toggleCompleted)) 62 | 63 | val renameTodo: js.Function2[Int, String, Unit] = 64 | MobX.action("renameTodo", (index: Int, task: String) => updateTodo(index, _.renameTask(task))) 65 | 66 | val assignTodo: js.Function2[Int, Option[Person], Unit] = 67 | MobX.action("assignTodo", (index: Int, assignee: Option[Person]) => updateTodo(index, _.changeAssignee(assignee))) 68 | 69 | def changeRequests(f: PendingRequests => PendingRequests): Unit = pendingRequests.set(f(pendingRequests.get())) 70 | 71 | val increasePending: js.Function0[Unit] = 72 | MobX.action("increasePending", () => changeRequests(_.increase)) 73 | 74 | val decreasePending: js.Function0[Unit] = 75 | MobX.action("decreasePending", () => changeRequests(_.decrease)) 76 | end TodoStore 77 | -------------------------------------------------------------------------------- /react-mobx/src/main/scala/demo/TodoView.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.vdom.html_<^.* 4 | import japgolly.scalajs.react.{Callback, CallbackTo} 5 | import org.scalajs.dom.window 6 | 7 | case class TodoViewProps(todo: Todo, toggle: Callback, rename: String => Callback) 8 | 9 | val TodoView = ObserverComponent { case TodoViewProps(todo, toggle, rename) => 10 | val onRename = CallbackTo(window.prompt("Task name", todo.task)) flatMap { 11 | case e if e.isEmpty => Callback.empty 12 | case task => rename(task) 13 | } 14 | 15 | <.li(^.onDoubleClick --> onRename)( 16 | <.input( 17 | ^.`type` := "checkbox", 18 | ^.checked := todo.completed, 19 | ^.onChange --> toggle 20 | ), 21 | todo.task, 22 | todo.assignee match 23 | case Some(person) => <.small(person.name) 24 | case None => <.span() 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /react-native/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Text, View} from 'react-native'; 3 | 4 | if (__DEV__) { 5 | module.exports = require("./target/scala-2.13/react-native-fastopt.js").app; 6 | } else { 7 | module.exports = () => { 8 | return 9 | 10 | Scala.js opt mode has not been enabled in App.js, please uncomment the code for it. 11 | 12 | ; 13 | } 14 | 15 | // uncomment the following line to enable opt building 16 | // module.exports = require("./target/scala-2.13/react-native-opt.js").app; 17 | } 18 | -------------------------------------------------------------------------------- /react-native/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "React Native basic example", 4 | "description": "React Native example with react-router and antd", 5 | "slug": "react-native", 6 | "privacy": "public", 7 | "sdkVersion": "37.0.0", 8 | "platforms": [ 9 | "ios", 10 | "android" 11 | ], 12 | "packagerOpts": { 13 | "config": "metro.config.js" 14 | }, 15 | "version": "1.0.0", 16 | "orientation": "portrait", 17 | "icon": "./assets/icon.png", 18 | "splash": { 19 | "image": "./assets/splash.png", 20 | "resizeMode": "contain", 21 | "backgroundColor": "#ffffff" 22 | }, 23 | "updates": { 24 | "fallbackToCacheTimeout": 0 25 | }, 26 | "assetBundlePatterns": [ 27 | "**/*" 28 | ], 29 | "ios": { 30 | "supportsTablet": true 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /react-native/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScalablyTyped/ScalaJsReactDemos/ee2c2dfad18ffe29f7ad092a20ee5aa4c0eca841/react-native/assets/icon.png -------------------------------------------------------------------------------- /react-native/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScalablyTyped/ScalaJsReactDemos/ee2c2dfad18ffe29f7ad092a20ee5aa4c0eca841/react-native/assets/splash.png -------------------------------------------------------------------------------- /react-native/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /react-native/fastopt-noparse.js: -------------------------------------------------------------------------------- 1 | var transformer = require('metro-react-native-babel-transformer') 2 | 3 | module.exports.transform = function ({ src, filename, options }) { 4 | options = options || {}; 5 | if (filename.indexOf('fastopt') > -1) { 6 | return { 7 | code: src, 8 | filename 9 | } 10 | } 11 | else { 12 | return transformer.transform({ src, filename, options }); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /react-native/metro.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transformer: { 3 | babelTransformerPath: require("path").resolve("./fastopt-noparse.js"), 4 | getTransformOptions: async () => ({ 5 | transform: { 6 | experimentalImportSupport: false, 7 | inlineRequires: false, 8 | }, 9 | }), 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /react-native/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject" 9 | }, 10 | "dependencies": { 11 | "@ant-design/icons-react-native": "2.0.0", 12 | "@ant-design/react-native": "3.3.0", 13 | "@types/node": "^13.13.2", 14 | "@types/react": "16.9.34", 15 | "@types/react-native": "^0.62.4", 16 | "@types/react-router-native": "^5.1.0", 17 | "expo": "^37.0.7", 18 | "expo-font": "8.1.1", 19 | "react": "16.9.0", 20 | "react-native": "https://github.com/expo/react-native/archive/sdk-37.0.1.tar.gz", 21 | "react-router-native": "5.1.2", 22 | "typescript": "3.8" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "^7.9.0", 26 | "babel-preset-expo": "^8.1.0", 27 | "react-proxy": "^1.1.8" 28 | }, 29 | "private": true 30 | } 31 | -------------------------------------------------------------------------------- /react-native/src/main/scala/hello/world/Antd.scala: -------------------------------------------------------------------------------- 1 | package hello.world 2 | 3 | import japgolly.scalajs.react.vdom.html_<^.* 4 | import japgolly.scalajs.react.{Callback, CallbackTo, ScalaFnComponent} 5 | import typings.antDesignIconsReactNative.antDesignIconsReactNativeStrings.{flag, gift} 6 | import typings.antDesignIconsReactNative.components.{IconFill, IconOutline} 7 | import typings.antDesignReactNative.antDesignReactNativeStrings as antdStrings 8 | import typings.antDesignReactNative.components.{List as AntdList, *} 9 | import typings.antDesignReactNative.mod.Toast 10 | import typings.antDesignReactNative.libModalPropsTypeMod.Action 11 | import typings.react.mod.useState 12 | import typings.reactNative.components.ScrollView 13 | import typings.reactNative.mod.{FlexAlignType, ViewStyle} 14 | import typings.reactNative.reactNativeStrings 15 | 16 | import scala.scalajs.js 17 | 18 | val Antd = ScalaFnComponent[Unit] { case () => 19 | val js.Tuple2(isModalVisible, updateIsModalVisible) = useState(false) 20 | 21 | View( 22 | Text.style(Styles.title)("Antd components"), 23 | ScrollView 24 | .automaticallyAdjustContentInsets(false) 25 | .showsHorizontalScrollIndicator(false) 26 | .showsVerticalScrollIndicator(false)( 27 | AntdList.renderHeaderVdomElement(Text("List header"))( 28 | ListItem 29 | .arrow(antdStrings.horizontal) 30 | .onPress(e => Callback(updateIsModalVisible(true)))("Open modal"), 31 | ListItem 32 | .arrow(antdStrings.horizontal) 33 | .onPress(e => Callback(Toast.success("Successful!")))( 34 | "Launch success toast" 35 | ) 36 | ), 37 | View.style( 38 | ViewStyle() 39 | .setBackgroundColor("white") 40 | .setFlex(1) 41 | .setFlexDirection(reactNativeStrings.column) 42 | .setJustifyContent(reactNativeStrings.center) 43 | .setAlignItems(FlexAlignType.center) 44 | )( 45 | InputItem.placeholder("input text"), 46 | InputItem 47 | .placeholder("password") 48 | .`type`(antdStrings.password) 49 | .error(true) 50 | .onErrorClick(_ => Callback(Toast.fail("Always wrong!"))) 51 | .last(true), 52 | WingBlank.size(antdStrings.lg)( 53 | Button 54 | .onPress(_ => Callback(Toast.fail("Failure!"))) 55 | .`type`(antdStrings.primary)("Launch fail toast") 56 | ), 57 | WhiteSpace.size(antdStrings.xl), 58 | IconFill(flag).size(40), 59 | IconOutline(name = gift).size(80), 60 | Icon(name = "experiment").size(antdStrings.lg).color("#A82") 61 | ) 62 | ), 63 | Modal(visible = isModalVisible) 64 | .transparent(true) 65 | .maskClosable(true) 66 | .closable(false) 67 | .title("Basic modal") 68 | .onClose(Callback(updateIsModalVisible(false))) 69 | .footer( 70 | js.Array( 71 | Action("Cancel").setOnPress(CallbackTo(updateIsModalVisible(false))), 72 | Action("OK").setOnPress(CallbackTo(updateIsModalVisible(false))) 73 | ) 74 | ) 75 | )(Text("Some contents...")) 76 | } 77 | -------------------------------------------------------------------------------- /react-native/src/main/scala/hello/world/App.scala: -------------------------------------------------------------------------------- 1 | package hello.world 2 | 3 | import japgolly.scalajs.react.component.ScalaFn.Component 4 | import japgolly.scalajs.react.facade.React 5 | import japgolly.scalajs.react.vdom.html_<^.* 6 | import japgolly.scalajs.react.{Callback, CtorType, ReactEventFrom, ScalaFnComponent} 7 | import org.scalajs.dom.Element 8 | import typings.antDesignReactNative.anon.PartialLocale 9 | import typings.antDesignReactNative.antDesignReactNativeStrings.xl 10 | import typings.antDesignReactNative.components.{List as AntdList, *} 11 | import typings.bang88ReactNativeDrawerLayout.mod.DrawerLayout 12 | import typings.react.mod.useState 13 | import typings.reactNative.components.ScrollView 14 | import typings.reactNative.mod.NodeHandle 15 | import typings.reactRouter.components.Route 16 | import typings.reactRouter.mod.{ExtractRouteParams, RouteProps} 17 | import typings.reactRouterNative.components.{NativeRouter, Redirect} 18 | 19 | import scala.scalajs.js 20 | import scala.scalajs.js.| 21 | 22 | sealed abstract class RoutePath(val path: String, val title: String) 23 | 24 | object RoutePath: 25 | object Home extends RoutePath("/", "Home") 26 | object Antd extends RoutePath("/antd", "Antd") 27 | object ReactRouter extends RoutePath("/react_router", "React Router") 28 | 29 | val allOrdered: List[RoutePath] = List(Home, Antd, ReactRouter) 30 | 31 | def toOption[T](ot: T | Null): Option[T] = 32 | Option(ot.asInstanceOf[T]) 33 | 34 | val App: Component[Unit, CtorType.Nullary] = ScalaFnComponent { case () => 35 | var ref: Option[DrawerLayout] = None 36 | 37 | val js.Tuple2(redirPath, updateRedirPath) = useState(RoutePath.Home.path) 38 | 39 | def navigateTo(route: RoutePath)(e: ReactEventFrom[NodeHandle with Element]) = 40 | Callback { 41 | updateRedirPath(route.path) 42 | ref.foreach(_.closeDrawer()) 43 | } 44 | 45 | def checkRedirection(stayPath: String, elem: VdomElement): VdomNode = 46 | if redirPath != stayPath then Redirect(to = redirPath) else elem 47 | 48 | val routeLinks: Seq[VdomElement] = RoutePath.allOrdered.zipWithIndex.map { case (route, index) => 49 | ListItem.onPress(navigateTo(route))(Text(route.title)).withKey(index.toString) 50 | } 51 | 52 | Provider.locale(PartialLocale().setLocale("enUS"))( 53 | NativeRouter( 54 | Drawer 55 | .drawerRef(nullableRef => Callback { ref = toOption(nullableRef) }) 56 | .sidebar(ScrollView(WhiteSpace.size(xl), AntdList(routeLinks*)))( 57 | AntdList.renderHeaderVdomElement(WhiteSpace.size(xl))( 58 | ListItem 59 | .extra(Icon(name = "menu")) 60 | .onPress(_ => Callback(ref.foreach(_.openDrawer())))("React Native demo") 61 | ) 62 | ), 63 | Route( 64 | RouteProps[String, ExtractRouteParams[String, String]]() 65 | .setPath(RoutePath.Home.path) 66 | .setRender(props => checkRedirection(props.`match`.path, Home()).rawNode) 67 | .setExact(true) 68 | ), 69 | Route( 70 | RouteProps() 71 | .setPath(RoutePath.Antd.path) 72 | .setRender(props => checkRedirection(props.`match`.path, Antd()).rawNode) 73 | ), 74 | Route( 75 | RouteProps[String, ExtractRouteParams[String, String]]() 76 | .setPath(RoutePath.ReactRouter.path) 77 | .setRender(props => checkRedirection(props.`match`.path, ReactRouter(props.`match`)).rawNode) 78 | .setExact(true) 79 | ) 80 | ) 81 | ) 82 | } 83 | -------------------------------------------------------------------------------- /react-native/src/main/scala/hello/world/Home.scala: -------------------------------------------------------------------------------- 1 | package hello.world 2 | 3 | import japgolly.scalajs.react.component.ScalaFn.Component 4 | import japgolly.scalajs.react.vdom.html_<^.* 5 | import japgolly.scalajs.react.{CtorType, ScalaFnComponent} 6 | import typings.reactNative.components.{Text, View} 7 | import typings.reactNative.mod.{TextStyle, ViewStyle} 8 | 9 | val Home: Component[Unit, CtorType.Nullary] = ScalaFnComponent { case () => 10 | View.style(ViewStyle().setMargin(20))( 11 | Text.style(TextStyle().setFontSize(16))( 12 | "This is a demo written in Scala through Scala.js, Slinky, ScalablyTyped and Expo.\n\n" + 13 | "It uses components from Antd Native and React Router Native." 14 | ) 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /react-native/src/main/scala/hello/world/LoadFonts.scala: -------------------------------------------------------------------------------- 1 | package hello.world 2 | 3 | import japgolly.scalajs.react.ScalaFnComponent 4 | import japgolly.scalajs.react.vdom.Implicits.* 5 | import org.scalablytyped.runtime.StringDictionary 6 | import typings.expo.components.AppLoading 7 | import typings.expoFont.buildFontDottypesMod.FontSource 8 | import typings.expoFont.mod as Font 9 | import typings.react.mod.{useEffect, useState} 10 | import typings.reactNative.components.Text 11 | 12 | import scala.concurrent.ExecutionContext.Implicits.global 13 | import scala.scalajs.js 14 | import scala.scalajs.js.annotation.JSImport 15 | import scala.util.{Failure, Success} 16 | 17 | /* we must load these fonts manually to use antd design */ 18 | object LoadFonts: 19 | object Fonts: 20 | @JSImport("../../node_modules/@ant-design/icons-react-native/fonts/antoutline.ttf", JSImport.Namespace) 21 | @js.native 22 | val AntdIconOutline: FontSource = js.native 23 | 24 | @JSImport("../../node_modules/@ant-design/icons-react-native/fonts/antfill.ttf", JSImport.Namespace) 25 | @js.native 26 | val AntdIconFill: FontSource = js.native 27 | 28 | val All: StringDictionary[FontSource] = StringDictionary( 29 | "antoutline" -> AntdIconOutline, 30 | "antfill" -> AntdIconFill 31 | ) 32 | end Fonts 33 | 34 | sealed trait State 35 | object State: 36 | case object Loading extends State 37 | case class Error(msg: String) extends State 38 | case object Success extends State 39 | 40 | val component = ScalaFnComponent[Unit] { case () => 41 | val js.Tuple2(state, setState) = useState(State.Loading: State) 42 | 43 | useEffect( 44 | () => 45 | Font 46 | .loadAsync(Fonts.All) 47 | .toFuture 48 | .onComplete { 49 | case Failure(exception) => setState(State.Error(exception.getMessage)) 50 | case Success(_) => setState(State.Success) 51 | }, 52 | js.Array() 53 | ) 54 | 55 | state match 56 | case State.Loading => AppLoading.AutoHideSplash() 57 | case State.Error(msg) => Text(s"Could not load fonts: $msg") 58 | case State.Success => App() 59 | } 60 | end LoadFonts 61 | -------------------------------------------------------------------------------- /react-native/src/main/scala/hello/world/Main.scala: -------------------------------------------------------------------------------- 1 | package hello.world 2 | 3 | import scala.scalajs.js.annotation.JSExportTopLevel 4 | 5 | @JSExportTopLevel("app") 6 | val app = LoadFonts.component.toJsComponent.raw 7 | -------------------------------------------------------------------------------- /react-native/src/main/scala/hello/world/ReactRouter.scala: -------------------------------------------------------------------------------- 1 | package hello.world 2 | 3 | import japgolly.scalajs.react.ScalaFnComponent 4 | import japgolly.scalajs.react.vdom.html_<^.* 5 | import typings.reactNative.components.{Text, View} 6 | import typings.reactRouter.mod.{Route as _, *} 7 | import typings.reactRouterNative.components.* 8 | 9 | val ReactRouter = ScalaFnComponent[`match`[?]] { m => 10 | def link(title: String, path: String): VdomElement = 11 | Link(to = m.url + path).style(Styles.subNavItemStyle)(Text.style(Styles.topicStyle)(title)) 12 | 13 | View( 14 | Text.style(Styles.title)("React Router demo"), 15 | Text.style(Styles.headerStyle)("Topics"), 16 | View( 17 | link("Rendering with React", "/rendering"), 18 | link("Components", "/components"), 19 | link("Props v. State", "/props-v-state") 20 | ), 21 | Route( 22 | RouteProps() 23 | .setPath(m.path + "/:topicId") 24 | .setRender(props => Topic(props.`match`.asInstanceOf[`match`[Param]]).rawNode) 25 | ), 26 | Route(RouteProps().setPath(m.path).setRender(_ => Text("Please select a topic").rawNode)) 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /react-native/src/main/scala/hello/world/Styles.scala: -------------------------------------------------------------------------------- 1 | package hello.world 2 | 3 | import typings.reactNative.mod.TextStyle 4 | import typings.reactNative.reactNativeStrings 5 | 6 | object Styles: 7 | val headerStyle = TextStyle() 8 | .setPadding(10) 9 | .setFontSize(20) 10 | .setColor("black") 11 | 12 | val topicStyle = TextStyle() 13 | .setPaddingTop(30) 14 | .setTextAlign(reactNativeStrings.center) 15 | .setFontSize(15) 16 | 17 | val subNavItemStyle = TextStyle().setPadding(5) 18 | 19 | val title = TextStyle() 20 | .setPadding(10) 21 | .setFontSize(20) 22 | .setFontWeight(reactNativeStrings.bold) 23 | .setTextAlign(reactNativeStrings.center) 24 | .setColor("red") 25 | end Styles 26 | -------------------------------------------------------------------------------- /react-native/src/main/scala/hello/world/Topic.scala: -------------------------------------------------------------------------------- 1 | package hello.world 2 | 3 | import japgolly.scalajs.react.ScalaFnComponent 4 | import japgolly.scalajs.react.vdom.Implicits.* 5 | import typings.reactNative.components.Text 6 | import typings.reactRouter.mod.* 7 | 8 | import scala.scalajs.js 9 | 10 | trait Param extends js.Object: 11 | val topicId: String 12 | 13 | val Topic = ScalaFnComponent[`match`[Param]] { m => 14 | Text.style(Styles.topicStyle)("Topic: " + m.params.topicId) 15 | } 16 | -------------------------------------------------------------------------------- /react-redux/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-redux demo 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /react-redux/src/main/scala/demo/Main.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import demo.advanced.{ExpenseAction, ExpenseContainer, ExpenseReducer, ExpenseState} 4 | import demo.basic.CakeAction.CakeAction 5 | import demo.basic.{CakeContainer, CakeReducer} 6 | import japgolly.scalajs.react.{Children, JsComponent} 7 | import org.scalajs.dom 8 | import typings.reactRedux.components.Provider 9 | import typings.reactRedux.mod.connect 10 | import typings.redux.mod.* 11 | import typings.reduxDevtoolsExtension.developmentOnlyMod.composeWithDevTools 12 | 13 | import scala.scalajs.js 14 | import scala.scalajs.js.| 15 | 16 | object Main: 17 | type AppAction = ExpenseAction | CakeAction 18 | 19 | trait AppState extends js.Object: 20 | val expenses: ExpenseState 21 | val cake: CakeReducer.State 22 | 23 | trait Reducers extends js.Object: 24 | val expenses: Reducer[ExpenseState, ExpenseAction] 25 | val cake: Reducer[CakeReducer.State, CakeAction] 26 | 27 | val rootReducer: Reducer[AppState, AppAction] = 28 | combineReducers(new Reducers: 29 | override val expenses: Reducer[ExpenseState, ExpenseAction] = ExpenseReducer.Reducer 30 | override val cake: Reducer[CakeReducer.State, CakeAction] = CakeReducer.Reducer 31 | ).asInstanceOf[Reducer[AppState, AppAction]] 32 | 33 | def main(args: Array[String]): Unit = 34 | val Empty = js.Object 35 | 36 | val store = createStore(rootReducer, composeWithDevTools(applyMiddleware())) 37 | val keepDispatch: js.Function1[Dispatch[AppAction], js.Dynamic] = d => js.Dynamic.literal(dispatch = d) 38 | 39 | val ConnectedExpenses = JsComponent[js.Object, Children.None, js.Object]( 40 | connect 41 | .asInstanceOf[js.Dynamic]((appState: AppState) => js.Dynamic.literal(state = appState.expenses), keepDispatch)( 42 | ExpenseContainer.component.toJsComponent.raw 43 | ) 44 | ) 45 | 46 | val ConnectedCakes = JsComponent[js.Object, Children.None, js.Object]( 47 | connect.asInstanceOf[js.Dynamic]((appState: AppState) => js.Dynamic.literal(state = appState.cake), keepDispatch)( 48 | CakeContainer.component.toJsComponent.raw 49 | ) 50 | ) 51 | 52 | Provider(store.unsafeCast2[Any, AppAction])( 53 | ConnectedExpenses(Empty), 54 | ConnectedCakes(Empty) 55 | ).renderIntoDOM(dom.document.getElementById("container")) 56 | end main 57 | end Main 58 | -------------------------------------------------------------------------------- /react-redux/src/main/scala/demo/advanced/Expense.scala: -------------------------------------------------------------------------------- 1 | package demo.advanced 2 | 3 | import java.util.UUID 4 | import scala.scalajs.js 5 | 6 | class Expense(val id: String, val description: String, val note: String, val amount: String, val createAt: String) 7 | extends js.Object 8 | 9 | object Expense: 10 | 11 | val default = new Expense("", "", "", "", "") 12 | 13 | def apply(): Expense = new Expense(UUID.randomUUID().toString, "description", "note", "1.0", "createAt") 14 | -------------------------------------------------------------------------------- /react-redux/src/main/scala/demo/advanced/ExpenseAction.scala: -------------------------------------------------------------------------------- 1 | package demo.advanced 2 | 3 | import typings.redux.mod.Action 4 | 5 | import scala.scalajs.js 6 | 7 | @js.native 8 | sealed trait ExpenseAction extends Action[String] 9 | 10 | object ExpenseAction: 11 | 12 | @js.native 13 | trait SetExpenseAction extends ExpenseAction: 14 | val expenses: js.Array[Expense] = js.native 15 | 16 | object SetExpenseAction: 17 | def _type = "SET_EXPENSE" 18 | 19 | @scala.inline 20 | def apply(expenses: js.Array[Expense]): SetExpenseAction = 21 | val __obj = js.Dynamic.literal() 22 | __obj.updateDynamic("type")(_type) 23 | __obj.asInstanceOf[SetExpenseAction].set("expenses", expenses) 24 | def unapply(a: Action[String]): Option[js.Array[Expense]] = 25 | if a.`type` == _type then Some(a.asInstanceOf[SetExpenseAction].expenses) else None 26 | end SetExpenseAction 27 | 28 | @js.native 29 | trait EditExpenseAction extends ExpenseAction: 30 | val expense: Expense = js.native 31 | 32 | object EditExpenseAction: 33 | def _type = "EDIT_EXPENSE" 34 | 35 | @scala.inline 36 | def apply(expense: Expense): EditExpenseAction = 37 | val __obj = js.Dynamic.literal() 38 | __obj.updateDynamic("type")(_type) 39 | __obj.asInstanceOf[EditExpenseAction].set("expense", expense) 40 | def unapply(a: Action[String]): Option[Expense] = 41 | if a.`type` == _type then Some(a.asInstanceOf[EditExpenseAction].expense) else None 42 | end EditExpenseAction 43 | 44 | @js.native 45 | trait RemoveExpenseAction extends ExpenseAction: 46 | val id: String = js.native 47 | 48 | object RemoveExpenseAction: 49 | def _type = "REMOVE_EXPENSE" 50 | 51 | @scala.inline 52 | def apply(id: String): RemoveExpenseAction = 53 | val __obj = js.Dynamic.literal("id" -> id) 54 | __obj.updateDynamic("type")(_type) 55 | __obj.asInstanceOf[RemoveExpenseAction] 56 | 57 | def unapply(a: Action[String]): Option[String] = 58 | if a.`type` == _type then Some(a.asInstanceOf[RemoveExpenseAction].id) else None 59 | end RemoveExpenseAction 60 | 61 | @js.native 62 | trait AddExpenseAction extends ExpenseAction: 63 | val expense: Expense = js.native 64 | 65 | object AddExpenseAction: 66 | def _type = "ADD_EXPENSE" 67 | @scala.inline 68 | def apply(expense: Expense): AddExpenseAction = 69 | val __obj = js.Dynamic.literal("expense" -> expense) 70 | __obj.updateDynamic("type")(_type) 71 | __obj.asInstanceOf[AddExpenseAction] 72 | 73 | def unapply(a: Action[String]): Option[Expense] = 74 | if a.`type` == _type then Some(a.asInstanceOf[AddExpenseAction].expense) else None 75 | end AddExpenseAction 76 | end ExpenseAction 77 | -------------------------------------------------------------------------------- /react-redux/src/main/scala/demo/advanced/ExpenseContainer.scala: -------------------------------------------------------------------------------- 1 | package demo.advanced 2 | 3 | import japgolly.scalajs.react.vdom.html_<^.* 4 | import japgolly.scalajs.react.{Callback, ScalaFnComponent} 5 | import typings.redux.mod.Dispatch 6 | 7 | import scala.scalajs.js 8 | 9 | // https://www.youtube.com/watch?v=OXxul6AvXNs 10 | // https://github.com/cmcaboy/redux-typed/tree/typed 11 | object ExpenseContainer: 12 | 13 | @js.native 14 | trait Props extends js.Object: 15 | val state: ExpenseState 16 | val dispatch: Dispatch[ExpenseAction] 17 | 18 | val component = ScalaFnComponent[Props] { (props: Props) => 19 | <.div( 20 | <.h1(s"Expense Page"), 21 | <.div( 22 | props.state.expenses 23 | .map(expense => 24 | <.div(^.key := expense.id)( 25 | <.p(expense.description), 26 | <.p(expense.amount), 27 | <.p(expense.note), 28 | <.button(^.onClick --> Callback(props.dispatch(ExpenseAction.RemoveExpenseAction(expense.id))))( 29 | "Remove Expense" 30 | ), 31 | <.button(^.onClick --> Callback(props.dispatch(ExpenseAction.EditExpenseAction(expense))))("Edit Expense") 32 | ) 33 | ) 34 | .toVdomArray 35 | ), 36 | <.button(^.onClick --> Callback(props.dispatch(ExpenseAction.SetExpenseAction(js.Array(Expense())))))( 37 | "Set Expense" 38 | ), 39 | <.button(^.onClick --> Callback(props.dispatch(ExpenseAction.AddExpenseAction(Expense()))))("Add Expense") 40 | ) 41 | } 42 | end ExpenseContainer 43 | -------------------------------------------------------------------------------- /react-redux/src/main/scala/demo/advanced/ExpenseReducer.scala: -------------------------------------------------------------------------------- 1 | package demo.advanced 2 | 3 | import typings.redux.mod.* 4 | 5 | object ExpenseReducer: 6 | val Reducer: Reducer[ExpenseState, ExpenseAction] = (stateOpt, action) => 7 | val state = stateOpt.getOrElse(ExpenseState.initial) 8 | action match 9 | case ExpenseAction.SetExpenseAction(expenses) => 10 | ExpenseState(expenses) 11 | 12 | case ExpenseAction.EditExpenseAction(editedExpense) => 13 | ExpenseState(state.expenses.map { expense => 14 | if expense.id.equals(editedExpense.id) then editedExpense 15 | else expense 16 | }) 17 | 18 | case ExpenseAction.RemoveExpenseAction(expenseId) => 19 | ExpenseState(state.expenses.filterNot(_.id.equals(expenseId))) 20 | 21 | case ExpenseAction.AddExpenseAction(expense) => 22 | ExpenseState(state.expenses :+ expense) 23 | case _ => 24 | state 25 | end match 26 | end ExpenseReducer 27 | -------------------------------------------------------------------------------- /react-redux/src/main/scala/demo/advanced/ExpenseState.scala: -------------------------------------------------------------------------------- 1 | package demo.advanced 2 | 3 | import scala.scalajs.js 4 | 5 | class ExpenseState(val expenses: js.Array[Expense]) extends js.Object 6 | 7 | object ExpenseState: 8 | 9 | val initial: ExpenseState = ExpenseState(js.Array[Expense]()) 10 | 11 | def apply(expenses: js.Array[Expense]): ExpenseState = 12 | new ExpenseState(expenses) 13 | -------------------------------------------------------------------------------- /react-redux/src/main/scala/demo/basic/CakeAction.scala: -------------------------------------------------------------------------------- 1 | package demo.basic 2 | 3 | import typings.redux.mod.Action 4 | 5 | import scala.scalajs.js 6 | 7 | object CakeAction: 8 | 9 | @js.native 10 | sealed trait CakeAction extends Action[String] 11 | 12 | @js.native 13 | trait BuyCake extends CakeAction 14 | 15 | object BuyCake: 16 | val _type = "BUY_CAKE" 17 | 18 | @scala.inline 19 | def apply(): BuyCake = 20 | val __obj = js.Dynamic.literal() 21 | __obj.updateDynamic("type")(_type.asInstanceOf[js.Any]) 22 | __obj.asInstanceOf[BuyCake] 23 | 24 | def unapply(a: Action[String]): Boolean = 25 | a.`type` == _type 26 | end BuyCake 27 | end CakeAction 28 | -------------------------------------------------------------------------------- /react-redux/src/main/scala/demo/basic/CakeContainer.scala: -------------------------------------------------------------------------------- 1 | package demo.basic 2 | 3 | import demo.basic.CakeAction.{BuyCake, CakeAction} 4 | import demo.basic.CakeReducer.State 5 | import japgolly.scalajs.react.component.ScalaFn.Component 6 | import japgolly.scalajs.react.vdom.html_<^.* 7 | import japgolly.scalajs.react.{Callback, CtorType, ScalaFnComponent} 8 | import typings.redux.mod.Dispatch 9 | 10 | import scala.scalajs.js 11 | 12 | // https://www.youtube.com/watch?v=gFZiQnM3Is4 13 | object CakeContainer: 14 | @js.native 15 | trait Props extends js.Object: 16 | val state: State 17 | val dispatch: Dispatch[CakeAction] 18 | 19 | val component: Component[Props, CtorType.Props] = ScalaFnComponent[Props] { props => 20 | <.div( 21 | <.h2(s"Number of cakes ${props.state.numOfCakes}"), 22 | <.button(^.onClick --> Callback(props.dispatch(BuyCake())))("Buy Cake") 23 | ) 24 | } 25 | end CakeContainer 26 | -------------------------------------------------------------------------------- /react-redux/src/main/scala/demo/basic/CakeReducer.scala: -------------------------------------------------------------------------------- 1 | package demo.basic 2 | 3 | import demo.basic.CakeAction.CakeAction 4 | import typings.redux.mod.Reducer 5 | 6 | import scala.scalajs.js 7 | 8 | object CakeReducer: 9 | 10 | class State(val numOfCakes: Int) extends js.Object 11 | 12 | object State: 13 | 14 | val initial: State = State(10) 15 | 16 | def apply(_numOfCakes: Int): State = 17 | new State(_numOfCakes) 18 | 19 | val Reducer: Reducer[State, CakeAction] = (stateOpt, action) => 20 | val state = stateOpt.getOrElse(State.initial) 21 | action match 22 | case CakeAction.BuyCake() => State(state.numOfCakes - 1) 23 | case _ => state 24 | end CakeReducer 25 | -------------------------------------------------------------------------------- /react-router-dom/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-router-dom-slinky demo 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /react-router-dom/src/main/scala/demo/App.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.ScalaFnComponent 4 | import japgolly.scalajs.react.vdom.html_<^.* 5 | import org.scalajs.dom 6 | import typings.reactRouter.mod.{`match`, RouteProps} 7 | import typings.reactRouterDom.components.* 8 | 9 | import scala.scalajs.js 10 | 11 | def home: VdomElement = <.div(<.h2("Home")) 12 | 13 | def about: VdomElement = <.div(<.h2("About")) 14 | 15 | val App = ScalaFnComponent[Unit] { _ => 16 | val renderIntro = <.div( 17 | <.header(^.className := "App-header")(<.h1(^.className := "App-title")("Welcome to React (with Scala.js!)")), 18 | <.p(^.className := "App-intro")("To get started, edit ", <.code("App.scala"), " and save to reload.") 19 | ) 20 | 21 | <.div(^.className := "App")( 22 | renderIntro, 23 | BrowserRouter( 24 | <.div( 25 | <.ul( 26 | <.li(Link[js.Object](to = "/")("Home")), 27 | <.li(Link[js.Object](to = "/about")("About")), 28 | <.li(Link[js.Object](to = "/topics")("Topics")) 29 | ), 30 | <.hr(), 31 | Route(RouteProps().setExact(true).setPath("/").setRender(_ => home.rawElement)), 32 | Route(RouteProps().setPath("/about").setRender(_ => about.rawElement)), 33 | Route(RouteProps().setPath("/topics").setRender(props => Topics(props.`match`).rawElement)) 34 | ) 35 | ) 36 | ) 37 | } 38 | 39 | val Topics = ScalaFnComponent[`match`[?]] { m => 40 | <.div( 41 | <.h2("Topics"), 42 | <.ul( 43 | <.li(Link[js.Object](to = m.url + "/rendering")("Rendering with React")), 44 | <.li(Link[js.Object](to = m.url + "/components")("Components")), 45 | <.li(Link[js.Object](to = m.url + "/props-v-state")("Props v. State")) 46 | ), 47 | <.hr(), 48 | Route( 49 | RouteProps() 50 | .setPath(m.path + "/:topicId") 51 | .setRender(props => Topic(props.`match`.asInstanceOf[`match`[Param]]).rawElement) 52 | ), 53 | Route(RouteProps().setExact(true).setPath(m.path).setRender(_ => <.h3("Please select a topic").rawElement)) 54 | ) 55 | } 56 | 57 | @js.native 58 | trait Param extends js.Object: 59 | val topicId: String = js.native 60 | 61 | val Topic = ScalaFnComponent[`match`[Param]] { m => 62 | <.div( 63 | <.h3("Topic: " + m.params.topicId) 64 | ) 65 | } 66 | 67 | @main 68 | def main: Unit = 69 | val container = Option(dom.document.getElementById("root")).getOrElse { 70 | val elem = dom.document.createElement("div") 71 | elem.id = "root" 72 | dom.document.body.appendChild(elem) 73 | elem 74 | } 75 | 76 | App().renderIntoDOM(container) 77 | end main 78 | -------------------------------------------------------------------------------- /react-slick/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-slick demo 6 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /react-slick/src/main/scala/demo/Main.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.vdom.html_<^.* 4 | import japgolly.scalajs.react.{Callback, ScalaFnComponent} 5 | import org.scalajs.dom.document 6 | import typings.react.mod.useState 7 | import typings.reactSlick.components.ReactSlick 8 | 9 | import scala.scalajs.js 10 | 11 | case class State(selectedIdx: Option[Int]) 12 | 13 | val SlickTest = 14 | ScalaFnComponent[js.Array[String]] { images => 15 | val js.Tuple2(state, setState) = useState(State(None)) 16 | 17 | def myOnClick(idx: Int) = 18 | Callback { 19 | println(s"clicked image $idx") 20 | setState(State(Some(idx))) 21 | } 22 | 23 | val renderedImages: js.Array[VdomElement] = 24 | images.zipWithIndex.map { case (source, idx) => 25 | <.img(^.key := idx, ^.src := source, ^.onClick --> myOnClick(idx)) 26 | } 27 | 28 | <.div( 29 | ^.style := js.Dynamic.literal(position = "relative", left = "200px", width = 500, height = 500), 30 | <.label( 31 | ^.style := js.Dynamic.literal(color = "blue"), 32 | s"Selected image index: ${state.selectedIdx.fold("none")(_.toString)}" 33 | ), 34 | ReactSlick 35 | .onInit(Callback.info("slick init")) 36 | .dots(true) 37 | .autoplay(true) 38 | .autoplaySpeed(1000) 39 | .slidesToShow(2)(renderedImages.to(Seq)*) 40 | ) 41 | } 42 | 43 | @main 44 | def main: Unit = 45 | SlickTest( 46 | js.Array( 47 | "https://i.pinimg.com/474x/a8/30/69/a8306979f24cbf615e1cc0a635ceb384.jpg", 48 | "https://i.pinimg.com/474x/b0/15/4c/b0154cfc2fe3a664ac8f679df4debf56.jpg", 49 | "https://i.imgur.com/FqeTKrS.jpg", 50 | "https://static.boredpanda.com/blog/wp-content/uploads/2019/11/cat-fluffy-squirrel-tail-bell-7-5dca63b7b11a8__700.jpg", 51 | "https://i.chzbgr.com/full/9428254976/hD3DA6B8F/cat" 52 | ) 53 | ).renderIntoDOM(document.body) 54 | -------------------------------------------------------------------------------- /react-window/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React-window demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /react-window/src/main/scala/demo/Main.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.ScalaFnComponent 4 | import japgolly.scalajs.react.facade.React.StatelessFunctionalComponent 5 | import japgolly.scalajs.react.vdom.html_<^.* 6 | import org.scalajs.dom 7 | import typings.reactVirtualizedAutoSizer.components.* 8 | import typings.reactWindow.components.* 9 | import typings.reactWindow.mod.ListChildComponentProps 10 | 11 | final val Elements: Vector[String] = Vector.tabulate(1000)(i => s"Element($i)") 12 | 13 | final val Row = ScalaFnComponent { (p: ListChildComponentProps) => 14 | <.div(^.style := p.style, s"Row(index=${p.index}, data=${Elements(p.index.toInt)})") 15 | }.toJsComponent 16 | 17 | // see https://react-window.now.sh/#/examples/list/fixed-size 18 | @main 19 | def main: Unit = 20 | val rawRow: StatelessFunctionalComponent[ListChildComponentProps] = Row.raw 21 | val rawRowCasted = rawRow.asInstanceOf[typings.react.mod.FunctionComponent[ListChildComponentProps]] 22 | 23 | FixedSizeList( 24 | height = 800, 25 | width = 800, 26 | itemSize = 100, 27 | itemCount = Elements.size, 28 | children = rawRowCasted 29 | ).renderIntoDOM(dom.document.getElementById("container")) 30 | end main 31 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Demos for ScalablyTyped with Scalajs-React flavour 2 | 3 | This is a collection of tiny demo projects to show off how we can use react libraries with Scalajs-react with typings generated from ScalablyTyped 4 | 5 | ## Browser demos 6 | 7 | ### react-mobx 8 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/react-mobx/) 9 | 10 | `sbt> react-mobx/start` starts a webpack-dev-server at http://localhost:8001 . 11 | 12 | ### react-slick 13 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/react-slick/) 14 | 15 | `sbt> react-slick/start` starts a webpack-dev-server at http://localhost:8002 . 16 | 17 | 18 | ### react-big-calendar 19 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/react-big-calendar/) 20 | 21 | `sbt> react-big-calendar/start` starts a webpack-dev-server at http://localhost:8003 . 22 | 23 | 24 | ### semantic-ui-react-kitchensink 25 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/semantic-ui-react-kitchensink/) 26 | 27 | `sbt> semantic-ui-react-kitchensink/start` starts a webpack-dev-server at http://localhost:8004 . 28 | 29 | 30 | ### antd 31 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/antd/) 32 | 33 | `sbt> antd/start` starts a webpack-dev-server at http://localhost:8006 . 34 | 35 | 36 | ### react-router-dom 37 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/react-router-dom/) 38 | 39 | `sbt> react-router-dom/start` starts a webpack-dev-server at http://localhost:8007 . 40 | 41 | 42 | ### material-ui 43 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/material-ui/) 44 | 45 | `sbt> material-ui/start` starts a webpack-dev-server at http://localhost:8008 . 46 | 47 | 48 | ### react-leaflet 49 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/react-leaflet/) 50 | 51 | `sbt> react-leaflet/start` starts a webpack-dev-server at http://localhost:8009 . 52 | 53 | 54 | ### office-ui-fabric-react 55 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/office-ui-fabric-react/) 56 | 57 | `sbt> office-ui-fabric-react/start` starts a webpack-dev-server at http://localhost:8010 . 58 | 59 | 60 | ### react-dnd 61 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/react-dnd/) 62 | 63 | `sbt> react-dnd/start` starts a webpack-dev-server at http://localhost:8011 . 64 | 65 | ### react-i18next 66 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/react-i18n/) 67 | 68 | `sbt> react-i18n/start` starts a webpack-dev-server at http://localhost:8012 . 69 | 70 | ### @nivo/line 71 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/nivo/) 72 | 73 | `sbt> nivo/start` starts a webpack-dev-server at http://localhost:8013 . 74 | 75 | ### downshift 76 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/downshift/) 77 | 78 | `sbt> downshift/start` starts a webpack-dev-server at http://localhost:8014 . 79 | 80 | ### react-redux 81 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/react-redux/) 82 | 83 | `sbt> react-redux/start` starts a webpack-dev-server at http://localhost:8015 . 84 | 85 | ### react-window 86 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/react-window/) 87 | 88 | `sbt> react-window/start` starts a webpack-dev-server at http://localhost:8016 . 89 | 90 | ### react-markdown 91 | [Demo](https://scalablytyped.github.io/ScalaJsReactDemos/react-markdown/) 92 | 93 | `sbt> react-markdown/start` starts a webpack-dev-server at http://localhost:8017 . 94 | 95 | ## React-native 96 | [Expo demo you can run on your phone](https://expo.io/@scalablytyped/scalably-typed-react-native) (slinky version is deployed there) 97 | 98 | To run this you'll need to follow the 99 | [Setting up the development environment](https://reactnative.dev/docs/environment-setup) 100 | for react-native. 101 | 102 | When you have an emulator running, you can start the demo like this: 103 | 104 | ``` 105 | sbt>react-native/run 106 | ``` 107 | -------------------------------------------------------------------------------- /semantic-ui-react-kitchensink/readme.md: -------------------------------------------------------------------------------- 1 | ## Kitchen sink demo for semantic-ui-react 2 | 3 | This is a kitchen sink, a loosely organized collection of usages of [Semantic UI React](https://react.semantic-ui.com/), the official Semantic-UI-React integration. 4 | 5 | ### Contribute 6 | 7 | If you have successfully coded other components or subtle features than those shown here, consider adding them for the benefit of the community. 8 | -------------------------------------------------------------------------------- /semantic-ui-react-kitchensink/src/main/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Semantic-ui-react demo 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /semantic-ui-react-kitchensink/src/main/scala/demo/App.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.vdom.html_<^.* 4 | import japgolly.scalajs.react.{Callback, ScalaFnComponent} 5 | import org.scalajs.dom 6 | import typings.react.mod.useState 7 | import typings.react.reactStrings.submit 8 | import typings.semanticUiReact.components as Sui 9 | import typings.semanticUiReact.distCommonjsGenericMod.{SemanticICONS, SemanticSIZES, SemanticWIDTHSSTRING} 10 | import typings.semanticUiReact.semanticUiReactStrings.left 11 | 12 | import scala.language.implicitConversions 13 | import scala.scalajs.js 14 | 15 | val App = ScalaFnComponent { case () => 16 | val js.Tuple2(isModalVisible, updateIsModalVisible) = useState(false) 17 | 18 | <.div( 19 | Sui.Grid( 20 | Sui.GridColumn.width(SemanticWIDTHSSTRING.`1`), 21 | Sui.GridColumn.width(SemanticWIDTHSSTRING.`14`)( 22 | Sui.Divider.horizontal(true)( 23 | Sui.Header.as("h4")(Sui.Icon.name(SemanticICONS.tag), "Button and Icon") 24 | ), 25 | <.p(Sui.Button.primary(true)("Primary")), 26 | <.p(Sui.Icon.name(SemanticICONS.recycle)), 27 | <.p( 28 | Sui.Button.icon(true)( 29 | Sui.Icon.name(SemanticICONS.recycle) 30 | ) 31 | ), 32 | <.p( 33 | Sui.Button 34 | .labelPosition(left) 35 | .icon(true)( 36 | Sui.Icon.name(SemanticICONS.pause), 37 | "Pause" 38 | ) 39 | ), 40 | Sui.Divider.horizontal(true)( 41 | Sui.Header.as("h4")( 42 | Sui.Icon.name(SemanticICONS.tag), 43 | "Form and Checkbox" 44 | ) 45 | ), 46 | Sui.Form( 47 | Sui.FormField( 48 | <.label("First Name"), 49 | <.input(^.placeholder := "First Name") 50 | ), 51 | Sui.FormField( 52 | <.label("Last Name"), 53 | <.input(^.placeholder := "Last Name") 54 | ), 55 | Sui.FormField( 56 | Sui.Checkbox.label("I agree to the Terms and Conditions") 57 | ), 58 | Sui.FormField(Sui.Checkbox.label("I agree to the Cookie Policy").toggle(true)), 59 | Sui.Button.`type`(submit)("OK!") 60 | ), 61 | Sui.Divider.horizontal(true)( 62 | Sui.Header.as("h4")(Sui.Icon.name(SemanticICONS.tag), "Card and Image") 63 | ), 64 | Sui.Card( 65 | Sui.Image 66 | .size(SemanticSIZES.medium) 67 | .wrapped(true) 68 | .ui(false) 69 | .set("src", "https://react.semantic-ui.com/images/avatar/large/matthew.png"), 70 | Sui.CardContent( 71 | Sui.CardHeader("Matthew"), 72 | Sui.CardMeta(<.span(^.className := "date")("Joined in 2015")), 73 | Sui.CardDescription("Matthew is a musician living in Nashville.") 74 | ), 75 | Sui.CardContent.extra(true)( 76 | <.a(Sui.Icon.name(SemanticICONS.user), "22 Friends") 77 | ) 78 | ), 79 | Sui.Divider.horizontal(true)( 80 | Sui.Header.as("h4")(Sui.Icon.name(SemanticICONS.tag), "Modal") 81 | ), 82 | <.p(Sui.Button.primary(true).onClick((_, _) => Callback(updateIsModalVisible(true)))("Show modal")) 83 | ), 84 | Sui.GridColumn.width(SemanticWIDTHSSTRING.`1`) 85 | ), 86 | Sui.Modal 87 | .onClose((_, _) => Callback(updateIsModalVisible(false))) 88 | .open(isModalVisible)( 89 | Sui.ModalHeader("Select a Photo"), 90 | Sui.ModalContent.image(true)( 91 | Sui.Image 92 | .size(SemanticSIZES.medium) 93 | .fluid(true) 94 | .wrapped(true) 95 | .set("src", "https://react.semantic-ui.com/images/avatar/large/rachel.png"), 96 | Sui.ModalDescription( 97 | Sui.Header("Default Profile Image"), 98 | <.p("We've found the following gravatar image associated with your e-mail address."), 99 | <.p("Is it okay to use this photo?") 100 | ) 101 | ) 102 | ) 103 | ) 104 | } 105 | 106 | @main 107 | def main: Unit = 108 | App(()).renderIntoDOM(dom.document.getElementById("container")) 109 | -------------------------------------------------------------------------------- /storybook-react/.storybook-dist/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/react'; 2 | 3 | function loadStories() { 4 | require('../target/scala-3.1.0/storybook-react-opt.js'); 5 | } 6 | 7 | configure(loadStories, module); 8 | -------------------------------------------------------------------------------- /storybook-react/.storybook-dist/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../.storybook/webpack.config.js'); 2 | -------------------------------------------------------------------------------- /storybook-react/.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/react'; 2 | 3 | function loadStories() { 4 | require('../target/scala-3.1.0/storybook-react-fastopt.js'); 5 | } 6 | 7 | configure(loadStories, module); 8 | -------------------------------------------------------------------------------- /storybook-react/.storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * I have no idea. 3 | * Stolen from https://github.com/psychobolt/react-rollup-boilerplate/blob/d9cb9179cb7c00baab486646c504110bf7e2f50a/.storybook/webpack.config.js#L11 4 | */ 5 | 6 | module.exports = ({config}) => ({ 7 | ...config, 8 | module: { 9 | ...config.module, 10 | rules: [ 11 | // Temp fix for issue: https://github.com/storybooks/storybook/issues/3346 12 | ...config.module.rules.filter(rule => !( 13 | (rule.use && rule.use.length && rule.use.find(({loader}) => loader === 'babel-loader')) 14 | )), 15 | { 16 | test: /\.jsx?$/, 17 | include: require('path').resolve('./'), 18 | exclude: /(node_modules|dist)/, 19 | loader: 'babel-loader', 20 | }, 21 | { 22 | test: /\.jsx?$/, 23 | exclude: /node_modules/, 24 | loader: 'source-map-loader', 25 | enforce: 'pre', 26 | }, 27 | ] 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /storybook-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "storybook": "start-storybook -p 8005 -c .storybook", 4 | "dist": "build-storybook -c .storybook-dist -o ../docs/storybook-react" 5 | }, 6 | "dependencies": { 7 | "@storybook/react": "5.3.18", 8 | "@types/node": "13.13.2", 9 | "@types/react": "16.9.34", 10 | "react": "16.13.1", 11 | "typescript": "3.8" 12 | }, 13 | "devDependencies": { 14 | "@babel/core": "^7.11.0", 15 | "babel-loader": "^8.1.0", 16 | "es6-shim": "^0.35.5", 17 | "react-dom": "^16.13.1", 18 | "source-map-loader": "^1.0.1", 19 | "webpack": "4.43.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /storybook-react/src/main/scala/demo/Demo.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import japgolly.scalajs.react.Callback 4 | import japgolly.scalajs.react.vdom.html_<^.* 5 | import typings.node.global.module 6 | import typings.storybookReact.mod.storiesOf 7 | 8 | object Demo: 9 | def main(args: Array[String]): Unit = 10 | storiesOf("Button", module) 11 | .add("with text", ctx => <.button("Hello Button").rawElement) 12 | .add( 13 | "with some emoji", 14 | ctx => 15 | <.button( 16 | ^.onClick ==> (e => Callback.alert(s"x: ${e.pageX}, y: ${e.pageY}")), 17 | ^.aria.label := "so cool", 18 | ^.role := "img" 19 | )(<.span("😀😎")).rawElement 20 | ) 21 | end Demo 22 | --------------------------------------------------------------------------------