├── .gitignore ├── LICENSE ├── README.md ├── core └── src │ └── main │ └── scala │ └── com │ └── acework │ └── js │ ├── components │ └── bootstrap │ │ ├── Accordion.scala │ │ ├── Affix.scala │ │ ├── Alert.scala │ │ ├── Badge.scala │ │ ├── BootstrapComponent.scala │ │ ├── BootstrapJQuery.scala │ │ ├── BsProps.scala │ │ ├── Button.scala │ │ ├── ButtonGroup.scala │ │ ├── ButtonToolbar.scala │ │ ├── Carousel.scala │ │ ├── CarouselItem.scala │ │ ├── Classes.scala │ │ ├── Col.scala │ │ ├── CollapsableMixin.scala │ │ ├── CollapsableNav.scala │ │ ├── Directions.scala │ │ ├── DropdownButton.scala │ │ ├── DropdownMenu.scala │ │ ├── DropdownStateMixin.scala │ │ ├── FadeMixin.scala │ │ ├── Glyphicon.scala │ │ ├── Grid.scala │ │ ├── Icon.scala │ │ ├── Input.scala │ │ ├── Interpolate.scala │ │ ├── JQuery.scala │ │ ├── JsNumberOrString.scala │ │ ├── Jumbotron.scala │ │ ├── Label.scala │ │ ├── ListGroup.scala │ │ ├── ListGroupItem.scala │ │ ├── MenuItem.scala │ │ ├── MergeableProps.scala │ │ ├── Modal.scala │ │ ├── ModalTrigger.scala │ │ ├── Nav.scala │ │ ├── NavBar.scala │ │ ├── NavItem.scala │ │ ├── OverlayMixin.scala │ │ ├── OverlayTrigger.scala │ │ ├── PageHeader.scala │ │ ├── PageItem.scala │ │ ├── Pager.scala │ │ ├── Panel.scala │ │ ├── PanelGroup.scala │ │ ├── Placements.scala │ │ ├── Popover.scala │ │ ├── ProgressBar.scala │ │ ├── Row.scala │ │ ├── Sizes.scala │ │ ├── SplitButton.scala │ │ ├── Styles.scala │ │ ├── SubNav.scala │ │ ├── TabPane.scala │ │ ├── TabbedArea.scala │ │ ├── Table.scala │ │ ├── Tooltip.scala │ │ ├── Utils.scala │ │ ├── Well.scala │ │ └── package.scala │ ├── logger │ ├── Log4JavaScript.scala │ ├── LoggerFactory.scala │ └── package.scala │ └── utils │ ├── EventListener.scala │ └── TransitionEvent.scala ├── demo ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── index.html ├── res │ ├── app.css │ ├── bootstrap-docs.css │ ├── bootstrap.css │ ├── bootstrap.min.css │ ├── carousel.png │ ├── default.css │ ├── docs.css │ ├── highlight.pack.js │ └── logo.png └── src │ └── main │ └── scala │ └── demo │ ├── ReactApp.scala │ ├── examples │ ├── AlertAutoDismissable.scala │ ├── AlertDismissable.scala │ ├── ContainedModal.scala │ ├── ExamplePageFooter.scala │ ├── ExamplePageHeader.scala │ └── util │ │ └── CodeContent.scala │ └── pages │ ├── Alerts.scala │ ├── Badges.scala │ ├── ButtonGroups.scala │ ├── Buttons.scala │ ├── Carousels.scala │ ├── Components.scala │ ├── DropdownButtons.scala │ ├── Glyphicons.scala │ ├── Grids.scala │ ├── Inputs.scala │ ├── Jumbotrons.scala │ ├── Labels.scala │ ├── ListGroups.scala │ ├── Modals.scala │ ├── Navbars.scala │ ├── Navs.scala │ ├── PageHeaders.scala │ ├── Pagers.scala │ ├── Panels.scala │ ├── Popovers.scala │ ├── Progressbars.scala │ ├── Section.scala │ ├── SubSection.scala │ ├── Tables.scala │ ├── Tabs.scala │ ├── Tooltips.scala │ └── Wells.scala ├── macro ├── build.sbt └── src │ └── main │ └── scala │ └── com │ └── acework │ └── js │ └── utils │ ├── Mappable.scala │ └── Mergeable.scala ├── project ├── ScalajsReactBootstrap.scala └── plugins.sbt └── test └── src └── test └── scala └── test ├── CoreTest.scala └── TestUtil.scala /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | core/target 3 | demo/target 4 | project/project 5 | project/target 6 | target 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 wei 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scala-react-bootstrap 2 | Bootstrap components built with scalajs-react 3 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Accordion.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import japgolly.scalajs.react._ 4 | 5 | /** 6 | * Created by weiyin on 10/03/15. 7 | */ 8 | object Accordion { 9 | 10 | val Accordion = ReactComponentB[Unit]("Accordion") 11 | .stateless 12 | .render { (P, C, _) => 13 | // TODO spread props 14 | PanelGroup(PanelGroup.PanelGroup(accordion = true), C) 15 | }.buildU 16 | 17 | def apply(children: ReactNode*) = Accordion(children) 18 | } 19 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Affix.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.EventListener 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | import org.scalajs.dom.raw.HTMLElement 7 | import org.scalajs.dom.{Event, document, window} 8 | 9 | import scala.scalajs.js 10 | import scala.scalajs.js.{UndefOr, undefined} 11 | import Utils._ 12 | 13 | /** 14 | * Created by weiyin on 10/03/15. 15 | */ 16 | object Affix { 17 | 18 | case class State(affixClass: String = "affix-top", affixPositionTop: Double = 0) 19 | 20 | case class Affix(offset: UndefOr[Double] = undefined, 21 | offsetTop: UndefOr[Double] = undefined, 22 | offsetBottom: UndefOr[Double] = undefined, 23 | role: UndefOr[String] = undefined, 24 | affixed: UndefOr[String] = undefined, 25 | addClasses: String = "") { 26 | def apply(children: ReactNode*) = component(this, children) 27 | 28 | def apply() = component(this) 29 | } 30 | 31 | val affixRegexp = "(affix-top|affix-bottom|affix)".r 32 | 33 | class Backend(val scope: BackendScope[Affix, State]) { 34 | var _onWindowScrollListener: UndefOr[EventListener] = undefined 35 | var _onDocumentClickListener: UndefOr[EventListener] = undefined 36 | 37 | 38 | var pinnedOffset: UndefOr[Double] = undefined 39 | var unpin: UndefOr[Double] = undefined 40 | var affixed: UndefOr[String] = undefined 41 | 42 | 43 | def checkPositionWithEventLoop() = { 44 | js.timers.setTimeout(0)(checkPosition()) 45 | } 46 | 47 | def getPinnedOffset(node: TopNode) = { 48 | if (pinnedOffset.isDefined) 49 | pinnedOffset.get 50 | else { 51 | var className = affixRegexp.replaceAllIn(node.className, "") 52 | className = if (className.length > 0) className + " affix" else className + "affix" 53 | node.className = className 54 | 55 | pinnedOffset = getOffset(node).top - window.pageYOffset 56 | pinnedOffset.get 57 | } 58 | } 59 | 60 | def checkPosition() = { 61 | if (scope.isMounted()) { 62 | val domNode = scope.getDOMNode() 63 | // documentElement has no offsetHeight 64 | val scrollHeight = document.body.offsetHeight 65 | val scrollTop = window.pageYOffset 66 | var position = getOffset(domNode) 67 | 68 | if (scope.props.affixed.getOrElse("NA") == "top") 69 | position = position.copy(top = position.top + scrollTop) 70 | 71 | var offsetTop = if (scope.props.offsetTop.isDefined) 72 | scope.props.offsetTop 73 | else 74 | scope.props.offset 75 | 76 | var offsetBottom = if (scope.props.offsetBottom.isDefined) 77 | scope.props.offsetBottom 78 | else 79 | scope.props.offset 80 | 81 | if (offsetTop.isDefined || offsetBottom.isDefined) { 82 | if (!offsetTop.isDefined) 83 | offsetTop = 0.0 84 | if (!offsetBottom.isDefined) 85 | offsetBottom = 0.0 86 | } 87 | 88 | var affix: UndefOr[String] = undefined 89 | 90 | if (this.unpin.isDefined && (scrollTop + unpin.get < position.top)) { 91 | affix = undefined 92 | } else if (offsetBottom.isDefined && (position.top + domNode.offsetHeight >= scrollHeight - offsetBottom.get)) { 93 | affix = "bottom" 94 | } else if (offsetTop.isDefined && (scrollTop <= offsetTop.get)) { 95 | affix = "top" 96 | } 97 | 98 | if (affixed.getOrElse("NA1") != affix.getOrElse("NA2")) { 99 | if (unpin.isDefined) 100 | domNode.style.top = "" 101 | 102 | val affixType = if (affix.isDefined) s"affix-${affix.get}" else "affix" 103 | affixed = affix 104 | 105 | if (affix.getOrElse("NA") == "bottom") 106 | unpin = getPinnedOffset(domNode) 107 | else 108 | unpin = undefined 109 | 110 | var affixPositionTop = 0.0 111 | if (affix.getOrElse("NA") == "bottom") { 112 | domNode.className = affixRegexp.replaceAllIn(domNode.className, "affix-bottom") 113 | affixPositionTop = scrollHeight - offsetBottom.get - domNode.offsetHeight - getOffset(domNode).top 114 | } 115 | 116 | scope.modState(_.copy(affixClass = affixType, affixPositionTop = affixPositionTop)) 117 | } 118 | } 119 | } 120 | 121 | def onComponentDidMount() = { 122 | _onWindowScrollListener = EventListener.listen(window, "scroll", (_: Event) => checkPosition()) 123 | _onDocumentClickListener = EventListener.listen(document, "click", (_: Event) => checkPositionWithEventLoop()) 124 | } 125 | 126 | def onComponentWillUnmount() = { 127 | if (_onDocumentClickListener.isDefined) { 128 | _onDocumentClickListener.get.remove() 129 | _onDocumentClickListener = undefined 130 | } 131 | 132 | if (_onWindowScrollListener.isDefined) { 133 | _onWindowScrollListener.get.remove() 134 | _onWindowScrollListener = undefined 135 | } 136 | } 137 | 138 | def onComponentDidUpdate(prevProps: Affix, prevState: State) = { 139 | if (prevState.affixClass == scope.state.affixClass) 140 | checkPositionWithEventLoop() 141 | } 142 | } 143 | 144 | val component = ReactComponentB[Affix]("Affix") 145 | .initialState(State()) 146 | .backend(new Backend(_)) 147 | .render((P, C, S, B) => { 148 | <.div(^.classSet1(P.addClasses, S.affixClass -> true), ^.top := S.affixPositionTop, 149 | C 150 | ) 151 | 152 | }) 153 | .componentDidMount(scope => scope.backend.onComponentDidMount()) 154 | .componentDidUpdate((scope, prevProps, prevState) => scope.backend.onComponentDidUpdate(prevProps, prevState)) 155 | .componentWillUnmount(scope => scope.backend.onComponentWillUnmount()) 156 | .build 157 | 158 | def apply(props: Affix, children: ReactNode*) = component(props, children) 159 | 160 | } 161 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Alert.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | 7 | import scala.scalajs.js 8 | import scala.scalajs.js.{UndefOr, undefined} 9 | 10 | /** 11 | * Created by weiyin on 10/03/15. 12 | */ 13 | object Alert extends BootstrapComponent { 14 | override type P = Alert 15 | override type S = Unit 16 | override type B = Backend 17 | override type N = TopNode 18 | 19 | override def defaultProps = Alert() 20 | 21 | case class Alert(dismissAfter: UndefOr[Int] = undefined, 22 | onDismiss: UndefOr[() => Unit] = undefined, 23 | bsClass: UndefOr[Classes.Value] = Classes.alert, 24 | bsStyle: UndefOr[Styles.Value] = Styles.info, 25 | bsSize: UndefOr[Sizes.Value] = undefined, 26 | addClasses: String = "") extends BsProps with MergeableProps[Alert] { 27 | 28 | def merge(t: Map[String, Any]): Alert = implicitly[Mergeable[Alert]].merge(this, t) 29 | 30 | def asMap: Map[String, Any] = implicitly[Mappable[Alert]].toMap(this) 31 | 32 | def apply(children: ReactNode*) = component(this, children) 33 | 34 | def apply() = component(this) 35 | } 36 | 37 | class Backend($: BackendScope[Alert, Unit]) { 38 | var dismissTimer: js.UndefOr[js.timers.SetTimeoutHandle] = 39 | js.undefined 40 | 41 | def clearTimer() = { 42 | if (dismissTimer.isDefined) { 43 | js.timers.clearTimeout(dismissTimer.get) 44 | dismissTimer = undefined 45 | } 46 | } 47 | 48 | def start(dismissAfter: Int, onDismiss: () => Unit) = 49 | dismissTimer = js.timers.setTimeout(dismissAfter)(onDismiss()) 50 | } 51 | 52 | override val component = ReactComponentB[Alert]("Alert") 53 | .stateless 54 | .backend(new Backend(_)) 55 | .render { (P, C, S, B) => 56 | var classes = P.bsClassSet 57 | val isDismissable = P.onDismiss.isDefined 58 | classes += ("alert-dismissable" -> isDismissable) 59 | 60 | def renderDismissButton() = { 61 | <.button(^.tpe := "button", ^.className := "close", 62 | ^.onClick --> P.onDismiss.get(), ^.aria.hidden := true, ^.dangerouslySetInnerHtml("×")) 63 | } 64 | 65 | // TODO spread props 66 | <.div(^.classSet1M(P.addClasses, classes))( 67 | if (isDismissable) renderDismissButton() else EmptyTag, 68 | C 69 | ) 70 | } 71 | .componentDidMount($ => 72 | if ($.props.dismissAfter.isDefined && $.props.onDismiss.isDefined) 73 | $.backend.start($.props.dismissAfter.get, $.props.onDismiss.get) 74 | ) 75 | .componentWillUnmount($ => $.backend.clearTimer()) 76 | .build 77 | 78 | } 79 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Badge.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.components.bootstrap.Utils.ValidComponentChildren 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | 7 | import scala.scalajs.js 8 | import scala.scalajs.js._ 9 | 10 | /** 11 | * Created by weiyin on 10/03/15. 12 | */ 13 | object Badge extends BootstrapComponent { 14 | override type P = Badge 15 | override type S = Unit 16 | override type B = Unit 17 | override type N = TopNode 18 | 19 | def defaultProps = Badge() 20 | 21 | case class Badge(pullRight: UndefOr[Boolean] = undefined, addClasses: String = "") { 22 | def apply(children: ReactNode*) = component(this, children) 23 | 24 | def apply() = component(this) 25 | } 26 | 27 | override val component = ReactComponentB[Badge]("Badge") 28 | .render { (P, C) => 29 | 30 | def hasContent = { 31 | var res = ValidComponentChildren.hasValidComponents(C) 32 | if (!res) { 33 | C.forEach { c => 34 | if (js.typeOf(c) == "string" || js.typeOf(c) == "number") 35 | res = true 36 | } 37 | } 38 | res 39 | } 40 | 41 | // FIXME spread props 42 | <.span(^.className := P.addClasses, 43 | ^.classSet("pull-right" -> P.pullRight.getOrElse(false), "badge" -> hasContent))(C) 44 | 45 | }.build 46 | 47 | } 48 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/BootstrapComponent.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import japgolly.scalajs.react.ReactComponentC.ReqProps 4 | import japgolly.scalajs.react.{ReactNode, TopNode} 5 | 6 | import scala.scalajs.js 7 | 8 | /** 9 | * Created by weiyin on 18/03/15. 10 | */ 11 | trait BootstrapComponent { 12 | 13 | type P 14 | type S 15 | type B 16 | type N <: TopNode 17 | 18 | val component: ReqProps[P, S, B, N] 19 | 20 | def defaultProps: P 21 | 22 | def withKey(key: js.Any) = component.withKey(key) 23 | 24 | def withRef(ref: String) = component.withRef(ref) 25 | 26 | def apply(props: P, children: ReactNode*) = component(props, children) 27 | 28 | def apply(children: ReactNode*) = component(defaultProps, children) 29 | 30 | def apply() = component 31 | } 32 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/BootstrapJQuery.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import scala.scalajs.js 4 | 5 | /** 6 | * Created by weiyin on 11/03/15. 7 | */ 8 | trait BootstrapJQuery extends JQuery { 9 | def modal(action: String): BootstrapJQuery = js.native 10 | 11 | def modal(options: js.Any): BootstrapJQuery = js.native 12 | } 13 | 14 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/BsProps.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import scala.scalajs.js.UndefOr 4 | 5 | /** 6 | * Created by weiyin on 09/03/15. 7 | */ 8 | 9 | trait BsProps { 10 | val bsClass: UndefOr[Classes.Value] 11 | val bsStyle: UndefOr[Styles.Value] 12 | val bsSize: UndefOr[Sizes.Value] 13 | 14 | def bsClassSet: Map[String, Boolean] = { 15 | var classes = Map[String, Boolean]() 16 | 17 | if (bsClass.isDefined && Classes.values.contains(bsClass.get)) { 18 | val bsClassName = bsClass.get.toString.replaceAll("\\$minus", "-") 19 | classes += (bsClassName -> true) 20 | 21 | val prefix = bsClassName + "-" 22 | if (bsSize.isDefined && Sizes.values.contains(bsSize.get)) { 23 | val bsSizeName = bsSize.get.toString 24 | classes += (prefix + bsSizeName -> true) 25 | } 26 | if (bsStyle.isDefined && Styles.values.contains(bsStyle.get)) { 27 | val bsStyleName = bsStyle.get.toString 28 | classes += ((prefix + bsStyleName) -> true) 29 | } 30 | } 31 | classes 32 | } 33 | 34 | def prefixClass(subClass: String) = { 35 | val bsClassName = bsClass.get.toString.replaceAll("\\$minus", "-") 36 | s"$bsClassName-$subClass" 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Button.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react.ReactComponentC.ReqProps 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | import scala.scalajs.js.{UndefOr, undefined} 9 | 10 | /** 11 | * Created by weiyin on 09/03/15. 12 | */ 13 | 14 | object Button extends BootstrapComponent { 15 | override type P = Button 16 | override type S = Unit 17 | override type B = Unit 18 | override type N = TopNode 19 | 20 | override def defaultProps = Button() 21 | 22 | case class Button( 23 | /*== start react bootstraps ==*/ 24 | active: Boolean = false, 25 | disabled: Boolean = false, 26 | block: Boolean = false, 27 | navItem: Boolean = false, 28 | navDropdown: Boolean = false, 29 | componentClass: UndefOr[String] = undefined, 30 | href: UndefOr[String] = undefined, 31 | target: UndefOr[String] = undefined, 32 | `type`: String = "button", 33 | value: UndefOr[String] = undefined, 34 | /*== end react bootstraps ==*/ 35 | onClick: UndefOr[(ReactEvent) => Unit] = undefined, 36 | onMouseOver: UndefOr[(ReactEvent) => Unit] = undefined, 37 | onMouseOut: UndefOr[(ReactEvent) => Unit] = undefined, 38 | onBlur: UndefOr[(ReactEvent) => Unit] = undefined, 39 | onFocus: UndefOr[(ReactEvent) => Unit] = undefined, 40 | bsClass: UndefOr[Classes.Value] = Classes.btn, 41 | bsStyle: UndefOr[Styles.Value] = Styles.default, 42 | bsSize: UndefOr[Sizes.Value] = undefined, 43 | addClasses: String = "") extends BsProps with MergeableProps[Button] { 44 | 45 | def merge(t: Map[String, Any]): Button = implicitly[Mergeable[Button]].merge(this, t) 46 | 47 | def asMap: Map[String, Any] = implicitly[Mappable[Button]].toMap(this) 48 | 49 | def apply(children: ReactNode*) = component(this, children) 50 | 51 | def apply() = component(this) 52 | } 53 | 54 | override val component = ReactComponentB[Button]("Button") 55 | .render { (P, C) => 56 | 57 | def renderNavItem(classes: Map[String, Boolean]) = { 58 | var componentClass = <.li(^.classSet("active" -> P.active)) 59 | 60 | componentClass = spreadEventHandlers(componentClass) 61 | componentClass(renderAnchor(classes)) 62 | } 63 | 64 | def spreadEventHandlers(node: ReactTag): ReactTag = { 65 | var componentClass = node 66 | if (P.onClick.isDefined) 67 | componentClass = componentClass(^.onClick ==> P.onClick.get) 68 | 69 | if (P.onMouseOut.isDefined) 70 | componentClass = componentClass(^.onMouseOut ==> P.onMouseOut.get) 71 | 72 | if (P.onMouseOver.isDefined) 73 | componentClass = componentClass(^.onMouseOver ==> P.onMouseOver.get) 74 | 75 | if (P.onBlur.isDefined) 76 | componentClass = componentClass(^.onBlur ==> P.onBlur.get) 77 | 78 | if (P.onFocus.isDefined) 79 | componentClass = componentClass(^.onFocus ==> P.onFocus.get) 80 | 81 | componentClass 82 | } 83 | 84 | def renderAnchor(classes: Map[String, Boolean]) = { 85 | // TODO spread props 86 | var componentClass = P.componentClass.getOrElse("a").reactTag 87 | componentClass = componentClass(^.href := P.href.getOrElse("#"), 88 | ^.target := P.target, ^.tpe := P.`type`, 89 | ^.classSet1M(P.addClasses, classes + ("disabled" -> P.disabled)), 90 | ^.role := "button") 91 | 92 | componentClass = spreadEventHandlers(componentClass) 93 | componentClass(C) 94 | } 95 | 96 | def renderButton(classes: Map[String, Boolean]) = { 97 | var componentClass = P.componentClass.getOrElse("button").reactTag 98 | 99 | // TODO spread props 100 | componentClass = componentClass(^.classSet1M(P.addClasses, classes), ^.tpe := P.`type`, 101 | ^.value := P.value, ^.disabled := P.disabled) 102 | 103 | componentClass = spreadEventHandlers(componentClass) 104 | componentClass(C) 105 | } 106 | 107 | var classes = if (P.navDropdown) Map[String, Boolean]() else P.bsClassSet 108 | classes ++= Map("active" -> P.active, "btn-block" -> P.block) 109 | 110 | if (P.navItem) 111 | renderNavItem(classes) 112 | else if (P.href.isDefined || P.target.isDefined || P.navDropdown) 113 | renderAnchor(classes) 114 | else 115 | renderButton(classes) 116 | } 117 | .build 118 | 119 | } 120 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/ButtonGroup.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | 7 | import scala.scalajs.js.{Any => JAny, UndefOr, undefined} 8 | 9 | /** 10 | * Created by weiyin on 10/03/15. 11 | */ 12 | 13 | 14 | object ButtonGroup extends BootstrapComponent { 15 | override type P = ButtonGroup 16 | override type S = Unit 17 | override type B = Unit 18 | override type N = TopNode 19 | 20 | override def defaultProps: P = ButtonGroup() 21 | 22 | case class ButtonGroup(id: UndefOr[String] = undefined, 23 | vertical: UndefOr[Boolean] = undefined, 24 | justified: UndefOr[Boolean] = undefined, 25 | bsClass: UndefOr[Classes.Value] = Classes.`btn-group`, 26 | bsStyle: UndefOr[Styles.Value] = undefined, 27 | bsSize: UndefOr[Sizes.Value] = undefined, 28 | addClasses: String = "") extends BsProps with MergeableProps[ButtonGroup] { 29 | 30 | def merge(t: Map[String, Any]): ButtonGroup = implicitly[Mergeable[ButtonGroup]].merge(this, t) 31 | 32 | def asMap: Map[String, Any] = implicitly[Mappable[ButtonGroup]].toMap(this) 33 | 34 | def apply(children: ReactNode*) = component(this, children) 35 | 36 | def apply() = component(this) 37 | } 38 | 39 | override val component = ReactComponentB[ButtonGroup]("ButtonGroup") 40 | .render { (P, C) => 41 | val classes = P.bsClassSet ++ Map( 42 | "btn-group" -> !P.vertical.getOrElse(false), 43 | "btn-group-vertical" -> P.vertical.getOrElse(false), 44 | "btn-group-justifed" -> P.justified.getOrElse(false)) 45 | 46 | // TODO spread props 47 | <.div(^.classSet1M(P.addClasses, classes))(C) 48 | }.build 49 | 50 | } 51 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/ButtonToolbar.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | 7 | import scala.scalajs.js.{UndefOr, undefined} 8 | 9 | /** 10 | * Created by weiyin on 10/03/15. 11 | */ 12 | object ButtonToolbar extends BootstrapComponent { 13 | override type P = ButtonToolBar 14 | override type S = Unit 15 | override type B = Unit 16 | override type N = TopNode 17 | 18 | override def defaultProps = ButtonToolBar() 19 | 20 | case class ButtonToolBar(bsClass: UndefOr[Classes.Value] = Classes.`btn-toolbar`, 21 | bsStyle: UndefOr[Styles.Value] = undefined, 22 | bsSize: UndefOr[Sizes.Value] = undefined, 23 | addClasses: String = "") extends BsProps with MergeableProps[ButtonToolBar] { 24 | 25 | def merge(t: Map[String, Any]): ButtonToolBar = implicitly[Mergeable[ButtonToolBar]].merge(this, t) 26 | 27 | def asMap: Map[String, Any] = implicitly[Mappable[ButtonToolBar]].toMap(this) 28 | 29 | def apply(children: ReactNode*) = component(this, children) 30 | 31 | def apply() = component(this) 32 | } 33 | 34 | override val component = ReactComponentB[ButtonToolBar]("ButtonToolbar") 35 | .render { (P, C) => 36 | // TODO spread props 37 | <.div(^.role := "toolbar", ^.classSet1M(P.addClasses, P.bsClassSet))(C) 38 | }.build 39 | 40 | } 41 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/CarouselItem.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable, TransitionEvent} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | import org.scalajs.dom.raw.Event 7 | 8 | import scala.scalajs.js 9 | import scala.scalajs.js.{UndefOr, undefined} 10 | 11 | /** 12 | * Created by weiyin on 10/03/15. 13 | */ 14 | object CarouselItem extends BootstrapComponent { 15 | override type P = CarouselItem 16 | override type S = State 17 | override type B = Backend 18 | override type N = TopNode 19 | 20 | override def defaultProps = CarouselItem() 21 | 22 | case class CarouselItem(index: Int = 0, 23 | active: UndefOr[Boolean] = undefined, 24 | animation: UndefOr[Boolean] = true, 25 | animateIn: UndefOr[Boolean] = false, 26 | animateOut: UndefOr[Boolean] = false, 27 | direction: UndefOr[Directions.Value] = Directions.next, 28 | onAnimateOutEnd: UndefOr[(Int) => Unit] = undefined, 29 | caption: UndefOr[String] = undefined, 30 | addClasses: String = "") extends MergeableProps[CarouselItem] { 31 | 32 | def merge(t: Map[String, Any]): CarouselItem = implicitly[Mergeable[CarouselItem]].merge(this, t) 33 | 34 | def asMap: Map[String, Any] = implicitly[Mappable[CarouselItem]].toMap(this) 35 | 36 | def apply(children: ReactNode*) = component(this, children) 37 | 38 | def apply() = component(this) 39 | } 40 | 41 | case class State(direction: UndefOr[Directions.Value] = undefined) 42 | 43 | class Backend($: BackendScope[CarouselItem, State]) { 44 | 45 | def startAnimation() = { 46 | if ($.isMounted()) 47 | $.modState(s => s.copy(direction = if ($.props.direction.getOrElse(Directions.next) == Directions.prev) Directions.right else Directions.left)) 48 | } 49 | 50 | def onAnimateOutEnd(e: Any) = { 51 | if ($.isMounted() && $.props.onAnimateOutEnd.isDefined) 52 | $.props.onAnimateOutEnd.get($.props.index) 53 | } 54 | } 55 | 56 | val component = ReactComponentB[CarouselItem]("CarouselItem") 57 | .initialState(State()) 58 | .backend(new Backend(_)) 59 | .render((P, C, S, B) => { 60 | 61 | def renderCaption() = { 62 | if (P.caption.isDefined) { 63 | <.div(^.className := "carousel-caption")(P.caption.get) 64 | } 65 | else 66 | EmptyTag 67 | } 68 | 69 | var classes = Map("item" -> true, 70 | "active" -> (P.active.getOrElse(false) && !P.animateIn.getOrElse(false) || P.animateOut.getOrElse(false)), 71 | "next" -> (P.active.getOrElse(false) && P.animateIn.getOrElse(false) && P.direction.getOrElse(Directions.next) == Directions.next), 72 | "prev" -> (P.active.getOrElse(false) && P.animateIn.getOrElse(false) && P.direction.getOrElse(Directions.next) == Directions.prev) 73 | ) 74 | 75 | if (S.direction.isDefined && (P.animateIn.getOrElse(false) || P.animateOut.getOrElse(false))) 76 | classes += (S.direction.get.toString -> true) 77 | 78 | // TODO spread props 79 | <.div(^.classSet1M(P.addClasses, classes))(C, renderCaption()) 80 | }) 81 | .componentWillReceiveProps(($, newProps) => 82 | if ($.props.active.getOrElse(false) != newProps.active.getOrElse(false)) 83 | $.modState(_.copy(direction = undefined)) 84 | ) 85 | .componentDidUpdate(($, previousProps, S) => { 86 | if (!$.props.active.getOrElse(false) && previousProps.active.getOrElse(false)) { 87 | TransitionEvent.addEndEventListener($.getDOMNode(), (e: Event) => $.backend.onAnimateOutEnd(e)) 88 | } 89 | 90 | if ($.props.active.getOrElse(false) != previousProps.active.getOrElse(false)) { 91 | js.timers.setTimeout(20)($.backend.startAnimation()) 92 | } 93 | }) 94 | .build 95 | } 96 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Classes.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | /** 4 | * Created by weiyin on 09/03/15. 5 | */ 6 | object Classes extends Enumeration { 7 | val alert, btn, `btn-group`, `btn-toolbar`, column, `input-group`, 8 | form, plyphicon, label, `list-group-item`, panel, `panel-group`, `progress-bar`, 9 | nav, navbar, modal, row, well, glyphicon = Value 10 | } 11 | 12 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Col.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import japgolly.scalajs.react._ 4 | import japgolly.scalajs.react.vdom.prefix_<^._ 5 | 6 | import scala.scalajs.js._ 7 | 8 | /** 9 | * Created by weiyin on 10/03/15. 10 | */ 11 | object Col { 12 | 13 | case class Col(xs: UndefOr[Int] = undefined, 14 | sm: UndefOr[Int] = undefined, 15 | md: UndefOr[Int] = undefined, 16 | lg: UndefOr[Int] = undefined, 17 | xsOffset: UndefOr[Int] = undefined, 18 | smOffset: UndefOr[Int] = undefined, 19 | mdOffset: UndefOr[Int] = undefined, 20 | lgOffset: UndefOr[Int] = undefined, 21 | xsPush: UndefOr[Int] = undefined, 22 | smPush: UndefOr[Int] = undefined, 23 | mdPush: UndefOr[Int] = undefined, 24 | lgPush: UndefOr[Int] = undefined, 25 | xsPull: UndefOr[Int] = undefined, 26 | smPull: UndefOr[Int] = undefined, 27 | mdPull: UndefOr[Int] = undefined, 28 | lgPull: UndefOr[Int] = undefined, 29 | componentClass: String = "div", addClasses: String = "") { 30 | 31 | def apply(children: ReactNode*) = component(this, children) 32 | 33 | def apply() = component(this) 34 | } 35 | 36 | val component = ReactComponentB[Col]("Col") 37 | .render { (P, C) => 38 | var classes = Map[String, Boolean]() 39 | 40 | def extractClasses(sizeName: String, size: UndefOr[Int], offset: UndefOr[Int], push: UndefOr[Int], pull: UndefOr[Int]) { 41 | 42 | if (size.isDefined) 43 | classes += (s"col-$sizeName-${size.get}" -> true) 44 | 45 | if (offset.isDefined && offset.get >= 0) 46 | classes += (s"col-$sizeName-offset-${offset.get}" -> true) 47 | 48 | if (push.isDefined && push.get >= 0) 49 | classes += (s"col-$sizeName-push-${push.get}" -> true) 50 | 51 | if (pull.isDefined && pull.get >= 0) 52 | classes += (s"col-$sizeName-pull-${pull.get}" -> true) 53 | } 54 | 55 | extractClasses("xs", P.xs, P.xsOffset, P.xsPush, P.xsPull) 56 | extractClasses("sm", P.sm, P.smOffset, P.smPush, P.smPull) 57 | extractClasses("md", P.md, P.mdOffset, P.mdPush, P.mdPull) 58 | extractClasses("lg", P.lg, P.lgOffset, P.lgPush, P.lgPull) 59 | 60 | val componentClass = P.componentClass.reactTag 61 | componentClass(^.classSet1M(P.addClasses, classes))(C) 62 | }.build 63 | 64 | } 65 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/CollapsableMixin.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.TransitionEvent 4 | import japgolly.scalajs.react.{BackendScope, TopNode} 5 | import org.scalajs.dom.Event 6 | 7 | import scala.scalajs.js 8 | import scala.scalajs.js.UndefOr 9 | 10 | /** 11 | * Created by weiyin on 09/03/15. 12 | */ 13 | 14 | case class CollapsableState(collapsing: Boolean, expanded: Boolean) 15 | 16 | trait CollapsableProps { 17 | val collapsable: UndefOr[Boolean] 18 | val expanded: UndefOr[Boolean] 19 | } 20 | 21 | trait CollapsableMixin[P <: CollapsableProps] { 22 | 23 | val scope: BackendScope[P, CollapsableState] 24 | var _collapseEnd = false 25 | 26 | def isExpanded = { 27 | scope.props.expanded.getOrElse(scope.state.expanded) 28 | } 29 | 30 | def getCollapsableClassSet(classNames: String) = { 31 | var classes = Map[String, Boolean]() 32 | for (className <- classNames.split(" ")) 33 | classes += (className -> true) 34 | 35 | classes += ("collapsing" -> scope.state.collapsing) 36 | classes += ("collapse" -> !scope.state.collapsing) 37 | classes += ("in" -> (isExpanded && !scope.state.collapsing)) 38 | 39 | classes 40 | } 41 | 42 | def dimension: String = "height" 43 | 44 | def getCollapsableDimensionValue: Int 45 | 46 | def onComponentWillUpdate(nextProps: P, nextState: CollapsableState) = { 47 | val willExpanded = nextProps.expanded.getOrElse(nextState.expanded) 48 | if (willExpanded != isExpanded) { 49 | // if the expanded state is being toggled, ensure node has a dimension value 50 | // this is needed for the animation to work and needs to be set before 51 | // the collapsing class is applied (after collapsing is applied the in class 52 | // is removed and the node's dimension will be wrong) 53 | val dim = dimension 54 | val value = if (!willExpanded) getCollapsableDimensionValue else 0 55 | 56 | getCollapsableDOMNode.map(_.style.setProperty(dim, s"${value}px")) 57 | 58 | _afterWillUpdate() 59 | } 60 | } 61 | 62 | def onComponentDidUpdate(prevProps: P, prevState: CollapsableState) = { 63 | // check if expanded is being toggled, if so, set collapsing 64 | _checkToggleCollapsing(prevProps, prevState) 65 | 66 | // check if collapsing was turned on, if so, start animation 67 | _checkStartAnimation() 68 | } 69 | 70 | def _afterWillUpdate() = {} 71 | 72 | def _checkStartAnimation() = { 73 | if (scope.state.collapsing) { 74 | val dim = dimension 75 | val value = if (isExpanded) getCollapsableDimensionValue else 0 76 | getCollapsableDOMNode.map(_.style.setProperty(dim, s"${value}px")) 77 | } 78 | } 79 | 80 | def _checkToggleCollapsing(nextProps: P, nextState: CollapsableState) = { 81 | val wasExpanded = nextProps.expanded.getOrElse(nextState.expanded) 82 | if (wasExpanded != isExpanded) { 83 | if (wasExpanded) 84 | _handleCollapse() 85 | else 86 | _handleExpand() 87 | } 88 | } 89 | 90 | def _handleCollapse() = { 91 | getCollapsableDOMNode.map { node => 92 | var onHandleCollapseComplete: (Event) => Unit = null 93 | onHandleCollapseComplete = (e: Event) => { 94 | _removeEndEventListener(node, onHandleCollapseComplete) 95 | scope.modState(_.copy(collapsing = false)) 96 | } 97 | _addEndEventListener(node, onHandleCollapseComplete) 98 | scope.modState(_.copy(collapsing = true)) 99 | } 100 | } 101 | 102 | def _handleExpand() = { 103 | getCollapsableDOMNode.map { node => 104 | val dim = dimension 105 | var onHandleExpandComplete: (Event) => Unit = null 106 | onHandleExpandComplete = (e: Event) => { 107 | _removeEndEventListener(node, onHandleExpandComplete) 108 | // remove dimension value - this ensure the collapsable item can grow 109 | // in dimension after initial display (such as an image loading) 110 | node.style.setProperty(dim, "") 111 | scope.modState(_.copy(collapsing = false)) 112 | } 113 | _addEndEventListener(node, onHandleExpandComplete) 114 | scope.modState(_.copy(collapsing = true)) 115 | } 116 | } 117 | 118 | def _addEndEventListener(node: TopNode, complete: (Event) => Unit) = 119 | TransitionEvent.addEndEventListener(node, complete) 120 | 121 | def _removeEndEventListener(node: TopNode, complete: (Event) => Unit) = 122 | TransitionEvent.removeEndEventListener(node, complete) 123 | 124 | def getCollapsableDOMNode: Option[TopNode] 125 | } 126 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/CollapsableNav.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.components.bootstrap.Utils._ 4 | import com.acework.js.utils.{Mappable, Mergeable} 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | import org.scalajs.dom.raw.HTMLUListElement 8 | 9 | import scala.scalajs.js.{UndefOr, undefined} 10 | 11 | 12 | /** 13 | * Created by weiyin on 09/03/15. 14 | */ 15 | 16 | object CollapsableNav extends BootstrapComponent { 17 | override type P = CollapsableNav 18 | override type S = CollapsableState 19 | override type B = Backend 20 | override type N = TopNode 21 | 22 | override def defaultProps = CollapsableNav() 23 | 24 | 25 | case class CollapsableNav(navbar: Boolean = false, stacked: Boolean = false, 26 | justified: Boolean = false, pullRight: Boolean = false, 27 | right: Boolean = false, 28 | collapsable: UndefOr[Boolean] = undefined, 29 | expanded: UndefOr[Boolean] = undefined, 30 | activeHref: UndefOr[String] = undefined, 31 | activeKey: UndefOr[String] = undefined, 32 | role: UndefOr[String] = undefined, 33 | eventKey: UndefOr[String] = undefined, 34 | id: UndefOr[String] = undefined, 35 | bsClass: UndefOr[Classes.Value] = Classes.nav, 36 | bsStyle: UndefOr[Styles.Value] = undefined, 37 | bsSize: UndefOr[Sizes.Value] = undefined, 38 | onSelect: UndefOr[Seq[UndefOr[String]] => Unit] = undefined, 39 | addClasses: String = "") extends BsProps with CollapsableProps with MergeableProps[CollapsableNav] { 40 | 41 | def merge(t: Map[String, Any]): CollapsableNav = implicitly[Mergeable[CollapsableNav]].merge(this, t) 42 | 43 | def asMap: Map[String, Any] = implicitly[Mappable[CollapsableNav]].toMap(this) 44 | 45 | def apply(children: ReactNode*) = component(this, children) 46 | 47 | def apply() = component(this) 48 | } 49 | 50 | 51 | class Backend(val scope: BackendScope[CollapsableNav, CollapsableState]) extends CollapsableMixin[CollapsableNav] { 52 | 53 | def getCollapsableDOMNode: Option[TopNode] = Some(scope.getDOMNode()) 54 | 55 | def getCollapsableDimensionValue: Int = { 56 | 0 57 | } 58 | } 59 | 60 | val ulRef = Ref[HTMLUListElement]("ul") 61 | 62 | override val component = ReactComponentB[CollapsableNav]("Nav") 63 | .initialState(CollapsableState(false, false)) 64 | .backend(new Backend(_)) 65 | .render((P, C, S, B) => { 66 | 67 | def getChildActiveProp(child: ReactNode) = { 68 | val childPropsAny = getChildProps[Any](child) 69 | childPropsAny match { 70 | case props: NavItem.NavItem => 71 | props.active || 72 | props.eventKey.getOrElse("NA1") == P.activeKey.getOrElse("NA") || 73 | props.href.getOrElse("NA1") == P.activeHref.getOrElse("NA") 74 | case _ => false 75 | } 76 | } 77 | 78 | def getChildOnSelectProp(child: ReactNode) = { 79 | val childPropsAny = getChildProps[Any](child) 80 | childPropsAny match { 81 | case props: NavItem.NavItem => 82 | props.onSelect 83 | case _ => undefined 84 | } 85 | } 86 | 87 | def renderChildren(child: ReactNode, index: Int) = { 88 | val (key, _) = getChildKeyAndRef2(child, index) 89 | cloneWithProps(child, (key, s"nocollapse_$key"), Map( 90 | "activeKey" -> P.activeKey, 91 | "activeHref" -> P.activeHref, 92 | "navItem" -> true 93 | )) 94 | } 95 | 96 | def renderCollapsableNavChildren(child: ReactNode, index: Int) = { 97 | val (key, _) = getChildKeyAndRef2(child, index) 98 | 99 | cloneWithProps(child, (key, s"collapsable_$key"), Map( 100 | "active" -> getChildActiveProp(child), 101 | "activeKey" -> P.activeKey, 102 | "activeHref" -> P.activeHref, 103 | "onSelect" -> createChainedFunction1(getChildOnSelectProp(child), P.onSelect), 104 | "navItem" -> true 105 | )) 106 | } 107 | 108 | var classes = if (P.collapsable.getOrElse(false)) B.getCollapsableClassSet("") else Map[String, Boolean]() 109 | classes += ("navbar-collapse" -> P.collapsable.getOrElse(false)) 110 | 111 | <.div(^.classSet1M(P.addClasses, classes), 112 | ValidComponentChildren.map(C, 113 | if (P.collapsable.getOrElse(false)) 114 | renderCollapsableNavChildren 115 | else 116 | renderChildren 117 | ) 118 | ) 119 | 120 | } 121 | ) 122 | .componentWillUpdate((scope, nextProps, nextState) => scope.backend.onComponentWillUpdate(nextProps, nextState)) 123 | .componentDidUpdate((scope, prevProps, prevState) => scope.backend.onComponentDidUpdate(prevProps, prevState)) 124 | .build 125 | 126 | } 127 | 128 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Directions.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | /** 4 | * Created by weiyin on 10/03/15. 5 | */ 6 | object Directions extends Enumeration { 7 | val prev, next, left, right = Value 8 | } 9 | 10 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/DropdownButton.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.components.bootstrap.Utils._ 4 | import com.acework.js.utils.{Mappable, Mergeable} 5 | import japgolly.scalajs.react.Addons.ReactCloneWithProps 6 | import japgolly.scalajs.react.Ref 7 | 8 | import scala.scalajs.js 9 | 10 | /** 11 | * Created by weiyin on 11/03/15. 12 | */ 13 | 14 | import japgolly.scalajs.react._ 15 | import japgolly.scalajs.react.vdom.prefix_<^._ 16 | 17 | import scala.scalajs.js.{UndefOr, undefined} 18 | 19 | object DropdownButton extends BootstrapComponent { 20 | override type P = DropdownButton 21 | override type S = DropdownState 22 | override type B = Backend 23 | override type N = TopNode 24 | 25 | override def defaultProps: P = DropdownButton() 26 | 27 | case class DropdownButton( 28 | /*== start react bootstraps ==*/ 29 | id: UndefOr[String] = undefined, 30 | pullRight: UndefOr[Boolean] = undefined, 31 | dropup: UndefOr[Boolean] = undefined, 32 | title: UndefOr[ReactNode] = undefined, 33 | href: UndefOr[String] = undefined, 34 | eventKey: UndefOr[String] = undefined, 35 | navItem: UndefOr[Boolean] = undefined, 36 | noCaret: UndefOr[Boolean] = undefined, 37 | onClick: () => Unit = () => (), 38 | onSelect: UndefOr[(String) => Unit] = undefined, 39 | /*== end react bootstraps ==*/ 40 | bsClass: UndefOr[Classes.Value] = Classes.btn, 41 | bsStyle: UndefOr[Styles.Value] = Styles.default, 42 | bsSize: UndefOr[Sizes.Value] = undefined, 43 | addClasses: String = "") 44 | extends BsProps with MergeableProps[DropdownButton] { 45 | 46 | def merge(t: Map[String, Any]): DropdownButton = implicitly[Mergeable[DropdownButton]].merge(this, t) 47 | 48 | def asMap: Map[String, Any] = implicitly[Mappable[DropdownButton]].toMap(this) 49 | 50 | def apply(children: ReactNode*) = component(this, children) 51 | 52 | def apply() = component(this) 53 | } 54 | 55 | class Backend(val scope: BackendScope[DropdownButton, DropdownState]) extends DropdownStateMixin[DropdownButton] { 56 | 57 | def handleOptionSelect(key: String): Unit = { 58 | if (scope.props.onSelect.isDefined) 59 | scope.props.onSelect.get(key) 60 | } 61 | 62 | def handleDropdownClick(e: ReactEvent) = { 63 | e.preventDefault() 64 | setDropdownState(!scope.state.open) 65 | } 66 | } 67 | 68 | override val component = ReactComponentB[DropdownButton]("DropdownButton") 69 | .initialState(DropdownState(open = false)) 70 | .backend(new Backend(_)) 71 | .render((P, C, S, B) => { 72 | def renderButtonGroup(children: ReactNode*) = { 73 | var addClasses = "dropdown" 74 | if (S.open) 75 | addClasses += " open" 76 | ButtonGroup(ButtonGroup.ButtonGroup(addClasses = addClasses), children) 77 | } 78 | def renderNavItem(children: ReactNode*) = { 79 | <.li(^.classSet1("dropdown", "open" -> S.open, "dropup" -> P.dropup.getOrElse(false)))(children) 80 | } 81 | 82 | def renderMenuItem(child: ReactNode, index: Int) = { 83 | // Only handle the option selection if an onSelect prop has been set on the 84 | // component or it's child, this allows a user not to pass an onSelect 85 | // handler and have the browser preform the default action. 86 | val handleOptionSelect: js.Any = if (P.onSelect.isDefined) // FIXME || child.props.onSelect) 87 | B.handleOptionSelect _ 88 | else () => () 89 | 90 | val keyAndRef = getChildKeyAndRef(child, index) 91 | ReactCloneWithProps(child, keyAndRef ++ 92 | Map[String, js.Any]( 93 | "onSelect" -> handleOptionSelect) // FIXME createChainedFunction0(childProps.onSelect, handleOptionSelect) 94 | ) 95 | } 96 | 97 | val buttonRef = Ref("dropdownButton") 98 | val buttonProps = Button.Button( 99 | addClasses = "dropdown-toggle", 100 | onClick = (e: ReactEvent) => B.handleDropdownClick(e), 101 | navDropdown = P.navItem.getOrElse(false), 102 | bsStyle = P.bsStyle, 103 | bsSize = P.bsSize, 104 | bsClass = P.bsClass 105 | ) 106 | 107 | val menuRef = Ref("menu") 108 | val dropdownMenu = DropdownMenu.withKey(1).withRef("menu")(DropdownMenu.DropdownMenu(ariaLabelledBy = P.id, 109 | pullRight = P.pullRight), 110 | ValidComponentChildren.map(C, renderMenuItem) 111 | ) 112 | 113 | val button = if (P.noCaret.getOrElse(false)) 114 | Button.withKey(0).withRef("dropdownButton")(buttonProps, P.title.get) 115 | else 116 | Button.withKey(0).withRef("dropdownButton")(buttonProps, P.title.get, " ", <.span(^.className := "caret")) 117 | 118 | if (P.navItem.getOrElse(false)) 119 | renderNavItem(button, dropdownMenu) 120 | else 121 | renderButtonGroup(button, dropdownMenu) 122 | }) 123 | .componentWillUnmount(_.backend.onComponentWillUnmount()) 124 | .build 125 | 126 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/DropdownMenu.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react.Addons.ReactCloneWithProps 5 | 6 | import scala.scalajs.js 7 | 8 | /** 9 | * Created by weiyin on 11/03/15. 10 | */ 11 | 12 | import com.acework.js.components.bootstrap.Utils._ 13 | import japgolly.scalajs.react._ 14 | import japgolly.scalajs.react.vdom.prefix_<^._ 15 | 16 | import scala.scalajs.js.{UndefOr, undefined} 17 | 18 | object DropdownMenu extends BootstrapComponent { 19 | override type P = DropdownMenu 20 | override type S = Unit 21 | override type B = Unit 22 | override type N = TopNode 23 | 24 | override def defaultProps = DropdownMenu() 25 | 26 | case class DropdownMenu( 27 | /*== start react bootstraps ==*/ 28 | ariaLabelledBy: UndefOr[String] = undefined, 29 | pullRight: UndefOr[Boolean] = undefined, 30 | onSelect: UndefOr[(String) => Unit] = undefined, 31 | /*== end react bootstraps ==*/ 32 | addClasses: String = "") extends MergeableProps[DropdownMenu] { 33 | 34 | def merge(t: Map[String, Any]): DropdownMenu = implicitly[Mergeable[DropdownMenu]].merge(this, t) 35 | 36 | def asMap: Map[String, Any] = implicitly[Mappable[DropdownMenu]].toMap(this) 37 | 38 | def apply(children: ReactNode*) = component(this, children) 39 | 40 | def apply() = component(this) 41 | } 42 | 43 | override val component = ReactComponentB[DropdownMenu]("DropdownMenu") 44 | .render((P, C) => { 45 | 46 | def renderMenuItem(child: ReactNode, index: Int) = { 47 | val keyAndRef = getChildKeyAndRef(child, index) 48 | if (P.onSelect.isDefined) 49 | ReactCloneWithProps(child, keyAndRef ++ 50 | Map[String, js.Any]( 51 | "onSelect" -> P.onSelect.get) // FIXME createChainedFunction0(childProps.onSelect, handleOptionSelect) 52 | ) 53 | else 54 | ReactCloneWithProps(child, keyAndRef) 55 | } 56 | 57 | <.ul(^.classSet1("dropdown-menu", "dropdown-menu-right" -> P.pullRight.getOrElse(false)), 58 | ^.role := "menu", ^.aria.labelledby := P.ariaLabelledBy)( 59 | ValidComponentChildren.map(C, renderMenuItem) 60 | ) 61 | }) 62 | .build 63 | 64 | } 65 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/DropdownStateMixin.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.EventListener 4 | import japgolly.scalajs.react.{BackendScope, ReactEvent} 5 | import org.scalajs.dom.document 6 | import org.scalajs.dom.raw.{KeyboardEvent, Node} 7 | 8 | /** 9 | * Created by weiyin on 11/03/15. 10 | */ 11 | 12 | case class DropdownState(open: Boolean) 13 | 14 | trait DropdownStateMixin[P] { 15 | 16 | val scope: BackendScope[P, DropdownState] 17 | 18 | var _onDocumentClickListener: Option[EventListener] = None 19 | var _onDocumentKeyUpListener: Option[EventListener] = None 20 | 21 | /** 22 | * Checks whether a node is within 23 | * a root nodes tree 24 | * 25 | * @param node 26 | * @param root 27 | */ 28 | def isNodeInRoot(node: Node, root: Node): Boolean = { 29 | var aNode = node 30 | var done = false 31 | var result = false 32 | while (aNode != null && !done) { 33 | if (aNode == root) { 34 | done = true 35 | result = true 36 | } 37 | else 38 | aNode = aNode.parentNode 39 | } 40 | result 41 | } 42 | 43 | def setDropdownState(newState: Boolean, onStateChangeComplete: () => Unit = () => ()) = { 44 | if (newState) 45 | bindRootCloseHandlers() 46 | else 47 | unBindRootCloseHandlers() 48 | 49 | scope.modState(s => s.copy(open = newState), onStateChangeComplete) 50 | } 51 | 52 | def bindRootCloseHandlers(): Unit = { 53 | _onDocumentClickListener = Some(EventListener.listen(document, "click", handleDocumentClick(_: ReactEvent))) 54 | _onDocumentKeyUpListener = Some(EventListener.listen(document, "keyup", handleDocumentKeyUp(_: KeyboardEvent))) 55 | } 56 | 57 | def unBindRootCloseHandlers(): Unit = { 58 | _onDocumentClickListener.map(_.remove()) 59 | _onDocumentClickListener = None 60 | _onDocumentKeyUpListener.map(_.remove()) 61 | _onDocumentKeyUpListener = None 62 | } 63 | 64 | def handleDocumentKeyUp(e: KeyboardEvent) = { 65 | if (e.keyCode == 27) 66 | setDropdownState(false) 67 | } 68 | 69 | def handleDocumentClick(e: ReactEvent) = { 70 | if (!isNodeInRoot(e.target, scope.getDOMNode())) 71 | setDropdownState(false) 72 | } 73 | 74 | def onComponentWillUnmount() = unBindRootCloseHandlers() 75 | 76 | } 77 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/FadeMixin.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import japgolly.scalajs.react.{BackendScope, TopNode} 4 | import org.scalajs.dom.raw.HTMLElement 5 | import org.scalajs.dom.{document, raw} 6 | 7 | import scala.scalajs.js 8 | 9 | /** 10 | * Created by weiyin on 11/03/15. 11 | */ 12 | trait FadeMixin[P <: OverlayProps, S] { 13 | 14 | def scope: BackendScope[P, S] 15 | 16 | var fadeOutEl: Option[raw.Element] = None 17 | 18 | def getElementAndSelf(root: TopNode, classes: Array[String]): Array[TopNode] = { 19 | val elements = root.querySelectorAll("." + classes.mkString(".")) 20 | 21 | val els = (for (i <- 0 until elements.length) yield elements(i).asInstanceOf[TopNode]).toArray 22 | 23 | var matched = true 24 | for (i <- 0 until classes.length if matched) { 25 | val Pattern = "\\b${classes(i)}\\b".r 26 | root.className match { 27 | case Pattern() => 28 | case _ => 29 | matched = false 30 | } 31 | } 32 | if (matched) 33 | root +: els 34 | else 35 | els 36 | } 37 | 38 | def _fadeIn() = { 39 | if (scope.isMounted()) { 40 | val elements = getElementAndSelf(scope.getDOMNode(), Array("fade")) 41 | elements.foreach { el => 42 | val classes = el.className.split(" ").filter(_ != "in") ++ Seq("in") 43 | el.className = classes.mkString(" ") 44 | } 45 | } 46 | } 47 | 48 | def _fadeOut() = { 49 | val elements = getElementAndSelf(scope.getDOMNode(), Array("fade", "in")) 50 | elements.foreach { el => 51 | el.className = el.className.replace("\\bin\\b", "") 52 | } 53 | js.timers.setTimeout(300)(handleFadeOutEnd()) 54 | } 55 | 56 | def handleFadeOutEnd(): Unit = { 57 | fadeOutEl.map { fadeout => 58 | if (fadeout.parentNode != null) 59 | fadeout.parentNode.removeChild(fadeout) 60 | } 61 | } 62 | 63 | def onComponentDidMount() = { 64 | // FIXME what does this mean? -- if(document.querySelectorAll) 65 | //val nodes = document.querySelectorAll("") 66 | if (true) { 67 | // Firefox needs delay for transition to be triggered 68 | js.timers.setTimeout(20)(_fadeIn()) 69 | } 70 | } 71 | 72 | def onComponentWillUnmount(): Unit = { 73 | val elements = getElementAndSelf(scope.getDOMNode(), Array("fade")) 74 | // TODO container 75 | val container = scope.props.container.getDOMNode 76 | if (elements.length > 0) { 77 | val fadeOut = document.createElement("div") 78 | container.appendChild(fadeOut) 79 | fadeOut.appendChild(scope.getDOMNode().cloneNode(deep = true)) 80 | fadeOutEl = Some(fadeOut) 81 | js.timers.setTimeout(20)(_fadeOut()) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Grid.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import japgolly.scalajs.react._ 4 | import japgolly.scalajs.react.vdom.prefix_<^._ 5 | 6 | import scala.scalajs.js._ 7 | 8 | /** 9 | * Created by weiyin on 10/03/15. 10 | */ 11 | object Grid { 12 | 13 | case class Grid(fluid: UndefOr[Boolean] = undefined, 14 | componentClass: String = "div", addClasses: String = "") { 15 | 16 | def apply(children: ReactNode*) = component(this, children) 17 | 18 | def apply() = component(this) 19 | } 20 | 21 | val component = ReactComponentB[Grid]("Grid") 22 | .render { (P, C) => 23 | 24 | val classes = if (P.fluid.getOrElse(false)) Map("container-fluid" -> true) else Map("container" -> true) 25 | val componentClass = P.componentClass.reactTag 26 | 27 | // FIXME spread props 28 | componentClass(^.classSet1M(P.addClasses, classes))(C) 29 | 30 | }.build 31 | 32 | def apply(props: Grid, children: ReactNode*) = component(props, children) 33 | 34 | def apply(children: ReactNode*) = component(Grid(), children) 35 | } 36 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Interpolate.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import japgolly.scalajs.react._ 4 | import japgolly.scalajs.react.vdom.prefix_<^._ 5 | 6 | import scala.scalajs.js._ 7 | import Utils._ 8 | 9 | import scala.util.matching.Regex.Groups 10 | 11 | /** 12 | * Created by weiyin on 10/03/15. 13 | */ 14 | object Interpolate extends BootstrapComponent { 15 | override type P = Interpolate 16 | override type S = Unit 17 | override type B = Unit 18 | override type N = TopNode 19 | 20 | def defaultProps = Interpolate() 21 | 22 | case class Interpolate(now: UndefOr[Double] = undefined, 23 | min: UndefOr[Double] = undefined, 24 | max: UndefOr[Double] = undefined, 25 | format: UndefOr[String] = undefined, 26 | componentClass: UndefOr[String] = "span", 27 | unsafe: UndefOr[Boolean] = undefined, 28 | percent: UndefOr[Double] = undefined, 29 | bsClass: UndefOr[Classes.Value] = Classes.label, 30 | bsStyle: UndefOr[Styles.Value] = Styles.default, 31 | bsSize: UndefOr[Sizes.Value] = undefined, 32 | addClasses: String = "") extends BsProps { 33 | 34 | def apply(children: ReactNode*) = component(this, children) 35 | 36 | def apply() = component(this) 37 | } 38 | 39 | val REGEXP = "%\\((.*)\\)s".r 40 | 41 | override val component = ReactComponentB[Interpolate]("Interpolate") 42 | .render { (P, C) => 43 | 44 | def getFormat(): String = { 45 | // FIXME C is str 46 | //if (ValidComponentChildren.hasValidComponents(C)) 47 | if (React.Children.count(C) > 0) 48 | C.asInstanceOf[Array[String]](0) 49 | else 50 | P.format.getOrElse("") 51 | } 52 | 53 | val format = getFormat() 54 | val parent = P.componentClass.get.reactTag 55 | val unsafe = P.unsafe.getOrElse(false) 56 | 57 | val content = REGEXP.replaceAllIn(format, _ match { 58 | case Groups("now") => s"${P.min}" 59 | case Groups("min") => s"${P.min}" 60 | case Groups("max") => s"${P.min}" 61 | case Groups("percent") => s"${P.percent}" 62 | case Groups("bsStyle") => s"${P.bsStyle}" 63 | }) 64 | 65 | // TODO props spread 66 | if (unsafe) { 67 | parent(^.classSet1M(P.addClasses, P.bsClassSet), ^.dangerouslySetInnerHtml(content)) 68 | } 69 | else 70 | parent(^.classSet1M(P.addClasses, P.bsClassSet), content) 71 | }.build 72 | 73 | } 74 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/JQuery.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import org.scalajs.dom._ 4 | 5 | import scala.scalajs.js 6 | 7 | /** 8 | * Minimal facade for JQuery. Use https://github.com/scala-js/scala-js-jquery/blob/master/src/main/scala/org/scalajs/jquery/JQuery.scala 9 | * for more complete one. 10 | */ 11 | trait JQueryEventObject extends Event { 12 | var data: js.Any = js.native 13 | } 14 | 15 | trait JQueryStatic extends js.Object { 16 | def apply(element: Element): JQuery = js.native 17 | } 18 | 19 | trait JQuery extends js.Object { 20 | def on(events: String, selector: js.Any, data: js.Any, handler: js.Function1[JQueryEventObject, js.Any]): JQuery = js.native 21 | 22 | def off(events: String): JQuery = js.native 23 | 24 | def offset(): js.Any = js.native 25 | 26 | def position(): js.Any = js.native 27 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/JsNumberOrString.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import scala.scalajs.js 4 | import scala.scalajs.js.UndefOr 5 | 6 | /** 7 | * Created by weiyin on 17/03/15. 8 | */ 9 | 10 | trait JsNumberOrString extends js.Object 11 | 12 | object JsNumberOrString { 13 | implicit def int2JsNumOrString(i: Int): UndefOr[JsNumberOrString] = i.asInstanceOf[JsNumberOrString] 14 | 15 | implicit def str2JsNumOrString(s: String): UndefOr[JsNumberOrString] = s.asInstanceOf[JsNumberOrString] 16 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Jumbotron.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import japgolly.scalajs.react._ 4 | import japgolly.scalajs.react.vdom.prefix_<^._ 5 | 6 | 7 | /** 8 | * Created by weiyin on 10/03/15. 9 | */ 10 | object Jumbotron extends BootstrapComponent { 11 | override type P = Jumbotron 12 | override type S = Unit 13 | override type B = Unit 14 | override type N = TopNode 15 | 16 | override def defaultProps = Jumbotron() 17 | 18 | 19 | case class Jumbotron(addClasses: String = ""){ 20 | 21 | def apply(children: ReactNode*) = component(this, children) 22 | 23 | def apply() = component(this) 24 | } 25 | 26 | override val component = ReactComponentB[Jumbotron]("Jumbotron") 27 | .render { (P, C) => 28 | <.div(^.classSet1(P.addClasses, "jumbotron" -> true))(C) 29 | }.build 30 | 31 | } 32 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Label.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | 7 | import scala.scalajs.js.{UndefOr, undefined} 8 | 9 | /** 10 | * Created by weiyin on 10/03/15. 11 | */ 12 | object Label extends BootstrapComponent { 13 | override type P = Label 14 | override type S = Unit 15 | override type B = Unit 16 | override type N = TopNode 17 | 18 | override def defaultProps = Label() 19 | 20 | // defaults are based on getDefaultProps 21 | case class Label(bsClass: UndefOr[Classes.Value] = Classes.label, 22 | bsStyle: UndefOr[Styles.Value] = Styles.default, 23 | bsSize: UndefOr[Sizes.Value] = undefined, 24 | addClasses: String = "") extends BsProps with MergeableProps[Label] { 25 | 26 | def merge(t: Map[String, Any]): Label = implicitly[Mergeable[Label]].merge(this, t) 27 | 28 | def asMap: Map[String, Any] = implicitly[Mappable[Label]].toMap(this) 29 | 30 | def apply(children: ReactNode*) = component(this, children) 31 | 32 | def apply() = component(this) 33 | } 34 | 35 | override val component = ReactComponentB[Label]("Label") 36 | .render { (P, C) => 37 | // TODO props spread 38 | <.span(^.classSet1M(P.addClasses, P.bsClassSet))(C) 39 | }.build 40 | 41 | } 42 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/ListGroup.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.components.bootstrap.Utils._ 4 | import japgolly.scalajs.react.Addons.ReactCloneWithProps 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | import scala.scalajs.js._ 9 | 10 | /** 11 | * Created by weiyin on 10/03/15. 12 | */ 13 | object ListGroup extends BootstrapComponent { 14 | override type P = ListGroup 15 | override type S = Unit 16 | override type B = Unit 17 | override type N = TopNode 18 | 19 | override def defaultProps = ListGroup() 20 | 21 | case class ListGroup(fill: Boolean = false, onClick: UndefOr[() => Unit] = undefined) { 22 | def apply(children: ReactNode*) = component(this, children) 23 | 24 | def apply() = component(this) 25 | } 26 | 27 | override val component = ReactComponentB[ListGroup]("ListGroup") 28 | .render { (P, C) => 29 | 30 | def renderListItem(child: ReactNode, index: Int) = { 31 | ReactCloneWithProps(child, getChildKeyAndRef(child, index)) 32 | } 33 | 34 | <.div(^.className := "list-group")( 35 | ValidComponentChildren.map(C, renderListItem) 36 | ) 37 | }.build 38 | 39 | } 40 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/ListGroupItem.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react.Addons.ReactCloneWithProps 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | import scala.scalajs.js.{UndefOr, undefined} 9 | 10 | /** 11 | * Created by weiyin on 10/03/15. 12 | */ 13 | object ListGroupItem extends BootstrapComponent { 14 | override type P = ListGroupItem 15 | override type S = Unit 16 | override type B = Unit 17 | override type N = TopNode 18 | 19 | override def defaultProps = ListGroupItem() 20 | 21 | case class ListGroupItem(active: UndefOr[Boolean] = undefined, 22 | disabled: UndefOr[Boolean] = undefined, 23 | header: UndefOr[ReactNode] = undefined, 24 | eventKey: UndefOr[String] = undefined, 25 | href: UndefOr[String] = undefined, 26 | target: UndefOr[String] = undefined, 27 | onSelect: UndefOr[(UndefOr[String]) => Unit] = undefined, 28 | onClick: UndefOr[(UndefOr[String]) => Unit] = undefined, 29 | bsClass: UndefOr[Classes.Value] = Classes.`list-group-item`, 30 | bsStyle: UndefOr[Styles.Value] = undefined, 31 | bsSize: UndefOr[Sizes.Value] = undefined, 32 | addClasses: String = "") extends BsProps with MergeableProps[ListGroupItem] { 33 | 34 | def merge(t: Map[String, Any]): ListGroupItem = implicitly[Mergeable[ListGroupItem]].merge(this, t) 35 | 36 | def asMap: Map[String, Any] = implicitly[Mappable[ListGroupItem]].toMap(this) 37 | 38 | def apply(children: ReactNode*) = component(this, children) 39 | 40 | def apply() = component(this) 41 | } 42 | 43 | override val component = ReactComponentB[ListGroupItem]("ListGroupItem") 44 | .render { (P, C) => 45 | 46 | def renderAnchor(classes: Map[String, Boolean]) = { 47 | // TODO spread props 48 | <.a(^.classSet1M(P.addClasses, classes))( 49 | if (P.header.isDefined) renderStructuredContent() else C 50 | ) 51 | } 52 | 53 | def renderSpan(classes: Map[String, Boolean]) = { 54 | // TODO spread props 55 | <.span(^.classSet1M(P.addClasses, classes))( 56 | if (P.header.isDefined) renderStructuredContent() else C 57 | ) 58 | } 59 | 60 | def renderStructuredContent(): Seq[ReactNode] = { 61 | val header: ReactNode = 62 | if (React.isValidElement(P.header.get)) { 63 | ReactCloneWithProps(P.header.get, Map("className" -> "list-group-item-heading")) 64 | } 65 | else { 66 | <.h4(^.className := "list-group-item-heading", P.header.get) 67 | } 68 | val content = <.p(^.className := "list-group-item-text")(C) 69 | // FIXME in js, it is {header: header, content: content} 70 | Seq(header, content) 71 | } 72 | 73 | var classes = P.bsClassSet 74 | classes += ("active" -> P.active.getOrElse(false)) 75 | classes += ("disabled" -> P.disabled.getOrElse(false)) 76 | 77 | if (P.href.isDefined || P.target.isDefined || P.onClick.isDefined) 78 | renderAnchor(classes) 79 | else 80 | renderSpan(classes) 81 | 82 | }.build 83 | 84 | } 85 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/MenuItem.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | 7 | import scala.scalajs.js.{UndefOr, undefined} 8 | 9 | /** 10 | * Created by weiyin on 10/03/15. 11 | */ 12 | object MenuItem extends BootstrapComponent { 13 | override type P = MenuItem 14 | override type S = Unit 15 | override type B = Backend 16 | override type N = TopNode 17 | 18 | override def defaultProps = MenuItem() 19 | 20 | case class MenuItem(header: UndefOr[Boolean] = undefined, 21 | divider: UndefOr[Boolean] = undefined, 22 | href: String = "#", 23 | title: UndefOr[String] = undefined, 24 | target: UndefOr[String] = undefined, 25 | eventKey: UndefOr[String] = undefined, 26 | onSelect: UndefOr[(String, String, String) => Unit] = undefined, 27 | addClasses: String = "") extends MergeableProps[MenuItem] { 28 | 29 | def merge(t: Map[String, Any]): MenuItem = implicitly[Mergeable[MenuItem]].merge(this, t) 30 | 31 | def asMap: Map[String, Any] = implicitly[Mappable[MenuItem]].toMap(this) 32 | 33 | def apply(children: ReactNode*) = component(this, children) 34 | 35 | def apply() = component(this) 36 | } 37 | 38 | class Backend($: BackendScope[MenuItem, Unit]) { 39 | def handleClick(e: ReactEvent): Unit = { 40 | if ($.props.onSelect.isDefined) { 41 | e.preventDefault() 42 | $.props.onSelect.get($.props.eventKey.get, $.props.href, $.props.target.get) 43 | } 44 | } 45 | } 46 | 47 | override val component = ReactComponentB[MenuItem]("MenuItem") 48 | .stateless 49 | .backend(new Backend(_)) 50 | .render { (P, C, S, B) => 51 | 52 | def renderAnchor(): ReactNode = { 53 | <.a(^.onClick --> B.handleClick _, ^.href := P.href, ^.target := P.target, ^.title := P.title, ^.tabIndex := -1)(C) 54 | } 55 | 56 | val classes = Map("dropdown-header" -> P.header.getOrElse(false), "divider" -> P.divider.getOrElse(false)) 57 | 58 | val children: TagMod = 59 | if (P.header.getOrElse(false)) 60 | C 61 | else if (!P.divider.getOrElse(false)) 62 | renderAnchor() 63 | else 64 | EmptyTag 65 | 66 | // TODO spread props 67 | <.li(^.classSet1M(P.addClasses, classes), ^.role := "presentation")(children) 68 | }.build 69 | 70 | } 71 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/MergeableProps.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | /** 4 | * Created by weiyin on 18/03/15. 5 | */ 6 | trait MergeableProps[T] { 7 | 8 | def merge(t: Map[String, Any]): MergeableProps[T] 9 | 10 | def asMap: Map[String, Any] 11 | } 12 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/ModalTrigger.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.components.bootstrap.Utils._ 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | 7 | import scala.scalajs.js 8 | import scala.scalajs.js.{UndefOr, undefined} 9 | 10 | /** 11 | * Created by weiyin on 11/03/15. 12 | */ 13 | object ModalTrigger { 14 | 15 | class Container extends OverlayContainer { 16 | override def getDOMNode = super.getDOMNode 17 | } 18 | 19 | case class ModalTrigger(modal: ReactElement, container: OverlayContainer = new OverlayContainer {}) extends OverlayProps { 20 | 21 | def apply(child: ReactNode) = component(this, child) 22 | 23 | def apply() = component(this) 24 | } 25 | 26 | case class State(isOverlayShown: Boolean = false) 27 | 28 | class Backend(val scope: BackendScope[ModalTrigger, State]) extends OverlayMixin[ModalTrigger, State] { 29 | 30 | def show() = scope.modState(_.copy(isOverlayShown = true)) 31 | 32 | def hide() = scope.modState(_.copy(isOverlayShown = false)) 33 | 34 | def toggle() = { 35 | scope.modState(s => s.copy(isOverlayShown = !s.isOverlayShown)) 36 | } 37 | 38 | override def renderOverlay(): Option[ReactElement] = { 39 | if (scope.state.isOverlayShown) 40 | Some(cloneWithProps(scope.props.modal, (undefined, undefined), 41 | Map("container" -> scope.props.container, 42 | "onRequestHide" -> (() => hide())))) 43 | else 44 | Some(<.span()) 45 | } 46 | } 47 | 48 | val component = ReactComponentB[ModalTrigger]("ModelTrigger") 49 | .initialState(State()) 50 | .backend(new Backend(_)) 51 | .render((P, C, S, B) => { 52 | 53 | val child = React.Children.only(C) 54 | val childProps = getChildProps[Any](child).asInstanceOf[js.Dynamic] 55 | val childOnClick: UndefOr[js.Any] = childProps.onClick 56 | val jsChildOnClick: UndefOr[(ReactEvent) => (js.Any)] = if (childOnClick == null || childOnClick == undefined) undefined else childOnClick.asInstanceOf[(ReactEvent) => (js.Any)] 57 | val toggle = ((e: ReactEvent) => B.toggle()).asInstanceOf[(ReactEvent) => (js.Any)] 58 | val onClick = createChainedFunction1(jsChildOnClick, toggle) 59 | 60 | cloneWithProps(child, (undefined, undefined), Map("onClick" -> onClick)) 61 | }) 62 | .componentDidMount(scope => scope.backend.onComponentDidMount()) 63 | .componentDidUpdate((scope, nextProps, state) => scope.backend.onComponentDidUpdate()) 64 | .componentWillUnmount(scope => scope.backend.onComponentWillUnmount()) 65 | .build 66 | 67 | def apply(props: ModalTrigger, child: ReactNode) = component(props, child) 68 | } 69 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Nav.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | import org.scalajs.dom.raw.{HTMLDocument, HTMLUListElement} 7 | 8 | import scala.scalajs.js 9 | import scala.scalajs.js.{UndefOr, undefined} 10 | import com.acework.js.components.bootstrap.Utils._ 11 | 12 | 13 | /** 14 | * Created by weiyin on 09/03/15. 15 | */ 16 | 17 | object Nav extends BootstrapComponent { 18 | override type P = Nav 19 | override type S = CollapsableState 20 | override type B = Backend 21 | override type N = TopNode 22 | 23 | override def defaultProps = Nav() 24 | 25 | @inline implicit final class ReactExt2_PropsChildren(val _c: PropsChildren) extends AnyVal { 26 | @inline def map[U](f: ReactNode => U): UndefOr[Object] = 27 | React.Children.map(_c, (f: js.Function).asInstanceOf[js.Function1[ReactNode, js.Any]]) 28 | 29 | @inline def map[U](f: (ReactNode, Int) => U): UndefOr[Object] = 30 | React.Children.map(_c, (f: js.Function).asInstanceOf[js.Function2[ReactNode, Int, js.Any]]) 31 | } 32 | 33 | case class Nav(navbar: Boolean = false, stacked: Boolean = false, 34 | justified: Boolean = false, pullRight: Boolean = false, 35 | right: Boolean = false, 36 | collapsable: UndefOr[Boolean] = undefined, 37 | expanded: UndefOr[Boolean] = undefined, 38 | activeHref: UndefOr[String] = undefined, 39 | activeKey: UndefOr[String] = undefined, 40 | role: UndefOr[String] = undefined, 41 | eventKey: UndefOr[String] = undefined, 42 | id: UndefOr[String] = undefined, 43 | bsClass: UndefOr[Classes.Value] = Classes.nav, 44 | bsStyle: UndefOr[Styles.Value] = undefined, 45 | bsSize: UndefOr[Sizes.Value] = undefined, 46 | onSelect: UndefOr[Seq[UndefOr[String]] => Unit] = undefined, 47 | addClasses: String = "") extends BsProps with CollapsableProps with MergeableProps[Nav] { 48 | 49 | def merge(t: Map[String, Any]): Nav = implicitly[Mergeable[Nav]].merge(this, t) 50 | 51 | def asMap: Map[String, Any] = implicitly[Mappable[Nav]].toMap(this) 52 | 53 | def apply(children: ReactNode*) = component(this, children) 54 | 55 | def apply() = component(this) 56 | } 57 | 58 | 59 | class Backend(val scope: BackendScope[Nav, CollapsableState]) extends CollapsableMixin[Nav] { 60 | 61 | def getCollapsableDOMNode: Option[TopNode] = Some(scope.getDOMNode()) 62 | 63 | def getCollapsableDimensionValue: Int = { 64 | if (scope.isMounted() && scope.refs("ul").isDefined) { 65 | val node = scope.refs("ul").get.asInstanceOf[TopNode] 66 | val height = node.offsetHeight.toInt 67 | val computedStyles = node.ownerDocument.asInstanceOf[HTMLDocument].defaultView.getComputedStyle(node, "") 68 | height + computedStyles.marginTop.toInt + computedStyles.marginBottom.toInt 69 | } 70 | else 71 | 0 72 | } 73 | 74 | } 75 | 76 | val ulRef = Ref[HTMLUListElement]("ul") 77 | 78 | override val component = ReactComponentB[Nav]("Nav") 79 | .initialState(CollapsableState(false, false)) 80 | .backend(new Backend(_)) 81 | .render((P, C, S, B) => { 82 | 83 | def getChildActiveProp(child: ReactNode) = { 84 | val childPropsAny = getChildProps[Any](child) 85 | childPropsAny match { 86 | case props: NavItem.NavItem => 87 | props.active || 88 | props.eventKey.getOrElse("NA1") == P.activeKey.getOrElse("NA") || 89 | props.href.getOrElse("NA1") == P.activeHref.getOrElse("NA") 90 | case _ => false 91 | } 92 | } 93 | 94 | def getOnSelectProps(child: ReactNode): UndefOr[Seq[UndefOr[String]] => Unit] = { 95 | val childPropsAny = getChildProps[Any](child) 96 | childPropsAny match { 97 | case props: NavItem.NavItem => 98 | if (props.onSelect.isDefined) 99 | props.onSelect 100 | else 101 | P.onSelect 102 | case _ => undefined 103 | } 104 | } 105 | 106 | def renderNavItem(child: ReactNode, index: Int): ReactNode = { 107 | val keyAndRef = getChildKeyAndRef2(child, index) 108 | 109 | val propsMap = Map[String, Any]( 110 | "active" -> getChildActiveProp(child), 111 | "activeKey" -> P.activeKey, 112 | "activeHref" -> P.activeHref, 113 | "onSelect" -> getOnSelectProps(child), 114 | "navItem" -> true 115 | ) 116 | cloneWithProps(child, keyAndRef, propsMap) 117 | } 118 | 119 | def renderUI() = { 120 | val classes = P.bsClassSet ++ Map( 121 | "nav-stacked" -> P.stacked, 122 | "nav-justified" -> P.justified, 123 | "navbar-nav" -> P.navbar, 124 | "pull-right" -> P.pullRight, 125 | "navbar-right" -> P.right) 126 | 127 | /* 128 | val navItems = new ListBuffer[ReactNode] 129 | C.forEach { (n, idx) => 130 | if (React.isValidElement(n)) 131 | navItems.append(renderNavItem(n, idx)) 132 | } */ 133 | 134 | <.ul(^.classSet1M(P.addClasses, classes), ^.ref := ulRef, 135 | ValidComponentChildren.map(C, renderNavItem) 136 | ) 137 | } 138 | 139 | var classes = if (P.collapsable.getOrElse(false)) B.getCollapsableClassSet("") else Map[String, Boolean]() 140 | classes += ("navbar-collapse" -> P.collapsable.getOrElse(false)) 141 | 142 | if (P.navbar && !P.collapsable.getOrElse(false)) 143 | renderUI() 144 | else { 145 | val ui = renderUI() 146 | <.nav(^.classSetM(classes), ^.role := P.role, ui) 147 | } 148 | } 149 | ).build 150 | 151 | } 152 | 153 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/NavBar.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.components.bootstrap.Utils._ 4 | import com.acework.js.utils.{Mappable, Mergeable} 5 | import japgolly.scalajs.react.Addons.ReactCloneWithProps 6 | import japgolly.scalajs.react._ 7 | import japgolly.scalajs.react.vdom.prefix_<^._ 8 | 9 | import scala.scalajs.js 10 | import scala.scalajs.js.{UndefOr, undefined} 11 | 12 | 13 | /** 14 | * Created by weiyin on 09/03/15. 15 | */ 16 | 17 | object NavBar extends BootstrapComponent { 18 | override type P = NavBar 19 | override type S = State 20 | override type B = Backend 21 | override type N = TopNode 22 | 23 | override def defaultProps = NavBar() 24 | 25 | case class NavBar( 26 | /*== start react bootstraps ==*/ 27 | fixedTop: UndefOr[Boolean] = undefined, 28 | fixedBottom: UndefOr[Boolean] = undefined, 29 | staticTop: UndefOr[Boolean] = undefined, 30 | inverse: UndefOr[Boolean] = undefined, 31 | fluid: UndefOr[Boolean] = undefined, 32 | role: UndefOr[String] = "navigation", 33 | componentClass: UndefOr[String] = "Nav", 34 | brand: UndefOr[ReactNode] = undefined, 35 | toggleButton: UndefOr[ReactNode] = undefined, 36 | toggleNavKey: UndefOr[String] = undefined, // FIXME String or Number 37 | onToggle: UndefOr[() => Unit] = undefined, 38 | navExpanded: UndefOr[Boolean] = undefined, 39 | defaultNavExpanded: UndefOr[Boolean] = undefined, 40 | bsClass: UndefOr[Classes.Value] = Classes.navbar, 41 | bsStyle: UndefOr[Styles.Value] = Styles.default, 42 | bsSize: UndefOr[Sizes.Value] = undefined, 43 | addClasses: String = "") extends BsProps with MergeableProps[NavBar] { 44 | 45 | def merge(t: Map[String, Any]): NavBar = implicitly[Mergeable[NavBar]].merge(this, t) 46 | 47 | def asMap: Map[String, Any] = implicitly[Mappable[NavBar]].toMap(this) 48 | 49 | def apply(children: ReactNode*) = component(this, children) 50 | 51 | def apply() = component(this) 52 | } 53 | 54 | case class State(navExpanded: Boolean) 55 | 56 | case class Backend(scope: BackendScope[NavBar, State]) { 57 | var _isChanging = false 58 | 59 | def handleToggle() = { 60 | if (scope.props.onToggle.isDefined) { 61 | _isChanging = true 62 | scope.props.onToggle.get() 63 | _isChanging = false 64 | } 65 | 66 | scope.modState(s => s.copy(navExpanded = !s.navExpanded)) 67 | } 68 | 69 | def isNavExpanded = { 70 | if (scope.props.navExpanded.isDefined) 71 | scope.props.navExpanded.get 72 | else 73 | scope.state.navExpanded 74 | } 75 | } 76 | 77 | 78 | override val component = ReactComponentB[NavBar]("NavBar") 79 | .initialStateP(P => State(navExpanded = P.defaultNavExpanded.getOrElse(false))) 80 | .backend(new Backend(_)) 81 | .render((P, C, S, B) => { 82 | 83 | def renderHeader(): TagMod = { 84 | val brand: TagMod = 85 | if (P.brand.isDefined) { 86 | if (React.isValidElement(P.brand.get)) { 87 | cloneWithProps(P.brand.get, (undefined, undefined), Map("addClasses" -> "navbar-brand")) 88 | } 89 | else 90 | <.span(^.className := "navbar-brand", P.brand.get) 91 | } 92 | else 93 | EmptyTag 94 | 95 | val toggleButton = if (P.toggleButton.isDefined || P.toggleNavKey.isDefined) 96 | renderToggleButton() 97 | else 98 | EmptyTag 99 | 100 | <.div(^.className := "navbar-header", 101 | brand, 102 | toggleButton 103 | ) 104 | } 105 | 106 | def renderChild(child: ReactNode, index: Int) = { 107 | val dynChild = child.asInstanceOf[js.Dynamic] 108 | val childProps = dynChild.props 109 | 110 | val keyAndRef = getChildKeyAndRef2(child, index) 111 | cloneWithProps(child, keyAndRef, Map( 112 | "navbar" -> true, 113 | "collapsable" -> (P.toggleNavKey.isDefined && P.toggleNavKey.get == childProps.eventKey), 114 | "expanded" -> (P.toggleNavKey.isDefined && P.toggleNavKey.get == childProps.eventKey && B.isNavExpanded) 115 | )) 116 | } 117 | 118 | def renderToggleButton(): TagMod = { 119 | if (P.toggleButton.isDefined) { 120 | if (React.isValidElement(P.toggleButton)) 121 | ReactCloneWithProps(P.toggleButton.get, Map[String, js.Any]("className" -> "navbar-toggle", 122 | "onClick" -> (() => B.handleToggle()) // FIXME createChainedFunction0(B.handleToggle(_), P.toggleButton.props.onClick)) 123 | )) 124 | else { 125 | val children: TagMod = if (P.toggleButton.isDefined) 126 | P.toggleButton.get 127 | else 128 | Seq( 129 | <.span(^.className := "sr-only", ^.key := 0, "Toggle navigation"), 130 | <.span(^.className := "icon-bar", ^.key := 1), 131 | <.span(^.className := "icon-bar", ^.key := 2), 132 | <.span(^.className := "icon-bar", ^.key := 3) 133 | ) 134 | 135 | <.button(^.className := "navbar-toggle", ^.tpe := "botton", ^.onClick --> B.handleToggle)(children) 136 | } 137 | } 138 | else 139 | EmptyTag 140 | } 141 | 142 | 143 | 144 | val classes = P.bsClassSet ++ Map( 145 | "navbar-fixed-top" -> P.fixedTop.getOrElse(false), 146 | "navbar-fixed-bottom" -> P.fixedBottom.getOrElse(false), 147 | "navbar-static-top" -> P.staticTop.getOrElse(false), 148 | "navbar-inverse" -> P.inverse.getOrElse(false)) 149 | 150 | val header: TagMod = 151 | if (P.brand.isDefined || P.toggleButton.isDefined || P.toggleNavKey.isDefined) 152 | renderHeader() 153 | else 154 | EmptyTag 155 | 156 | val className = if (P.fluid.getOrElse(false)) "container-fluid" else "container" 157 | if (P.componentClass.isDefined && P.componentClass.get != "Nav") { 158 | val componentClass = P.componentClass.get.reactTag 159 | componentClass(^.classSet1M(P.addClasses, classes), ^.role := P.role, 160 | <.div(^.className := className, 161 | header, 162 | ValidComponentChildren.map(C, renderChild) 163 | ) 164 | ) 165 | } 166 | else { 167 | val addClasses = classes.filter(_._2).map(_._1).mkString(" ") 168 | Nav.Nav(addClasses = s"$addClasses ${P.addClasses}".trim, role = P.role)( 169 | <.div(^.className := className, 170 | header, 171 | ValidComponentChildren.map(C, renderChild) 172 | ) 173 | ) 174 | } 175 | 176 | } 177 | ).build 178 | 179 | } 180 | 181 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/NavItem.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.logger._ 4 | import com.acework.js.utils.{Mappable, Mergeable} 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | import scala.scalajs.js.{UndefOr, undefined} 9 | 10 | /** 11 | * Created by weiyin on 09/03/15. 12 | */ 13 | object NavItem extends BootstrapComponent { 14 | override type P = NavItem 15 | override type S = Unit 16 | override type B = Unit 17 | override type N = TopNode 18 | 19 | override def defaultProps = NavItem() 20 | 21 | case class NavItem(active: Boolean = false, disabled: Boolean = false, 22 | href: UndefOr[String] = "#", 23 | eventKey: UndefOr[String] = undefined, 24 | title: UndefOr[String] = undefined, 25 | target: UndefOr[String] = undefined, 26 | onSelect: UndefOr[Seq[UndefOr[String]] => Unit] = undefined, 27 | addClasses: String = "") extends MergeableProps[NavItem] { 28 | 29 | def merge(t: Map[String, Any]): NavItem = implicitly[Mergeable[NavItem]].merge(this, t) 30 | 31 | def asMap: Map[String, Any] = implicitly[Mappable[NavItem]].toMap(this) 32 | 33 | def apply(children: ReactNode*) = component(this, children) 34 | 35 | def apply() = component(this) 36 | } 37 | 38 | override val component = ReactComponentB[NavItem]("NavItem") 39 | .render((P, C) => { 40 | 41 | def handleClick(e: ReactEvent): Unit = { 42 | P.onSelect.map { onSelect => 43 | e.preventDefault() 44 | if (!P.disabled) 45 | onSelect(Seq(P.eventKey, P.href, P.target)) 46 | } 47 | } 48 | 49 | val classes = Map("active" -> P.active, "disabled" -> P.disabled) 50 | 51 | var link = <.a(^.href := P.href, ^.title := P.title, ^.target := P.target, 52 | ^.ref := "anchor", ^.onClick ==> handleClick) 53 | 54 | if (P.href.getOrElse("NA") == "#") 55 | link = link(^.role := "button") 56 | 57 | <.li(^.classSet1M(P.addClasses, classes), 58 | link(C) 59 | ) 60 | } 61 | 62 | ).build 63 | 64 | } -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/OverlayMixin.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import japgolly.scalajs.react._ 4 | import org.scalajs.dom.raw.HTMLElement 5 | import org.scalajs.dom.{Node, document} 6 | 7 | 8 | /** 9 | * Created by weiyin on 17/03/15. 10 | */ 11 | 12 | trait OverlayContainer { 13 | // Provide `getDOMNode` fn mocking a React component API. The `document.body` 14 | // reference needs to be contained within this function so that it is not accessed 15 | // in environments where it would not be defined, e.g. nodejs. Equally this is needed 16 | // before the body is defined where `document.body === null`, this ensures 17 | // `document.body` is only accessed after componentDidMount. 18 | def getDOMNode: HTMLElement = document.body 19 | } 20 | 21 | class ReferencedContainer[P, S](ref: RefSimple[TopNode], scope: BackendScope[P, S]) extends OverlayContainer { 22 | override def getDOMNode = { 23 | val refNode = ref(scope) 24 | if (refNode != null) 25 | refNode.get.getDOMNode() 26 | else 27 | super.getDOMNode 28 | } 29 | } 30 | 31 | trait OverlayProps { 32 | val container: OverlayContainer 33 | } 34 | 35 | trait OverlayMixin[P <: OverlayProps, S] { 36 | 37 | val scope: BackendScope[P, S] 38 | 39 | var _overlayTarget: Option[Node] = None 40 | var _overlayInstance: Option[ReactComponentM_[TopNode]] = None 41 | 42 | def onComponentWillUnmount() = { 43 | _unrenderOverlay() 44 | _overlayTarget match { 45 | case Some(target) => 46 | getContainerDOMNode.removeChild(target) 47 | _overlayTarget = None 48 | case None => 49 | } 50 | } 51 | 52 | def onComponentDidUpdate() = { 53 | _renderOverlay() 54 | } 55 | 56 | def onComponentDidMount() = { 57 | _renderOverlay() 58 | } 59 | 60 | def _renderOverlay() = { 61 | _overlayTarget match { 62 | case None => 63 | _mountOverlayTarget() 64 | case Some(_) => 65 | } 66 | 67 | renderOverlay() match { 68 | case Some(overlay) => 69 | _overlayInstance = Some(React.render(overlay, _overlayTarget.get)) 70 | case None => 71 | // unreder if the componet is null or transitions to null 72 | _unrenderOverlay() 73 | } 74 | } 75 | 76 | def _unrenderOverlay() = { 77 | _overlayTarget map { target => 78 | React.unmountComponentAtNode(target) 79 | _overlayTarget = None 80 | } 81 | } 82 | 83 | def _mountOverlayTarget() = { 84 | _overlayTarget = Some(document.createElement("div")) 85 | getContainerDOMNode.appendChild(_overlayTarget.get) 86 | } 87 | 88 | def getOverlayDOMNode: Option[TopNode] = { 89 | var node: Option[TopNode] = None 90 | if (scope.isMounted()) { 91 | if (_overlayInstance.isDefined) 92 | node = Some(_overlayInstance.get.getDOMNode()) 93 | } 94 | node 95 | } 96 | 97 | def getContainerDOMNode: HTMLElement = { 98 | scope.props.container.getDOMNode 99 | } 100 | 101 | def renderOverlay(): Option[ReactElement] 102 | 103 | } 104 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/PageHeader.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import japgolly.scalajs.react._ 4 | import japgolly.scalajs.react.vdom.prefix_<^._ 5 | 6 | 7 | /** 8 | * Created by weiyin on 10/03/15. 9 | */ 10 | object PageHeader extends BootstrapComponent { 11 | override type P = PageHeader 12 | override type S = Unit 13 | override type B = Unit 14 | override type N = TopNode 15 | 16 | override def defaultProps = PageHeader() 17 | 18 | case class PageHeader(addClasses: String = "") { 19 | 20 | def apply(children: ReactNode*) = component(this, children) 21 | 22 | def apply() = component(this) 23 | } 24 | 25 | override val component = ReactComponentB[PageHeader]("PageHeader") 26 | .render { (P, C) => 27 | <.div(^.classSet1(P.addClasses, "page-header" -> true), 28 | <.h1(C)) 29 | }.build 30 | 31 | } 32 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/PageItem.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | 7 | import scala.scalajs.js 8 | import scala.scalajs.js.{UndefOr, undefined} 9 | 10 | /** 11 | * Created by weiyin on 10/03/15. 12 | */ 13 | object PageItem extends BootstrapComponent { 14 | override type P = PageItem 15 | override type S = Unit 16 | override type B = Unit 17 | override type N = TopNode 18 | 19 | override def defaultProps = PageItem() 20 | 21 | case class PageItem(href: UndefOr[String] = "#", 22 | title: UndefOr[String] = undefined, 23 | target: UndefOr[String] = undefined, 24 | disabled: UndefOr[Boolean] = undefined, 25 | previous: UndefOr[Boolean] = undefined, 26 | next: UndefOr[Boolean] = undefined, 27 | eventKey: UndefOr[js.Any] = undefined, 28 | onSelect: UndefOr[(js.Any, String, String) => Unit] = undefined, addClasses: String = "") 29 | extends MergeableProps[PageItem] { 30 | 31 | def merge(t: Map[String, Any]): PageItem = implicitly[Mergeable[PageItem]].merge(this, t) 32 | 33 | def asMap: Map[String, Any] = implicitly[Mappable[PageItem]].toMap(this) 34 | 35 | def apply(children: ReactNode*) = component(this, children) 36 | 37 | def apply() = component(this) 38 | } 39 | 40 | override val component = ReactComponentB[PageItem]("PageItem") 41 | .render { (P, C) => 42 | 43 | def handleSelect(e: ReactEvent) = { 44 | if (P.onSelect.isDefined) { 45 | e.preventDefault() 46 | 47 | if (!P.disabled.getOrElse(false)) { 48 | P.onSelect.get(P.eventKey.get, P.href.get, P.target.get) 49 | } 50 | } 51 | } 52 | 53 | val classes = Map( 54 | "disabled" -> P.disabled.getOrElse(false), 55 | "previous" -> P.previous.getOrElse(false), 56 | "next" -> P.next.getOrElse(false) 57 | ) 58 | 59 | <.li(^.classSet1M(P.addClasses, classes), 60 | <.a(^.href := P.href, ^.title := P.title, ^.target := P.target, ^.onClick ==> handleSelect, 61 | C) 62 | ) 63 | }.build 64 | } 65 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Pager.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.components.bootstrap.Utils._ 4 | import com.acework.js.utils.{Mappable, Mergeable} 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | import scala.scalajs.js.{UndefOr, undefined} 9 | 10 | /** 11 | * Created by weiyin on 10/03/15. 12 | */ 13 | object Pager extends BootstrapComponent { 14 | override type P = Pager 15 | override type S = Unit 16 | override type B = Unit 17 | override type N = TopNode 18 | 19 | override def defaultProps = Pager() 20 | 21 | case class Pager(onSelect: UndefOr[Seq[UndefOr[String]] => Unit] = undefined, addClasses: String = "") 22 | extends MergeableProps[Pager] { 23 | 24 | def merge(t: Map[String, Any]): Pager = implicitly[Mergeable[Pager]].merge(this, t) 25 | 26 | def asMap: Map[String, Any] = implicitly[Mappable[Pager]].toMap(this) 27 | 28 | def apply(children: ReactNode*) = component(this, children) 29 | 30 | def apply() = component(this) 31 | } 32 | 33 | override val component = ReactComponentB[Pager]("Pager") 34 | .render { (P, C) => 35 | 36 | def getOnSelectProps(child: ReactNode): UndefOr[Seq[UndefOr[String]] => Unit] = { 37 | val childPropsAny = getChildProps[Any](child) 38 | childPropsAny match { 39 | case props: NavItem.NavItem => 40 | if (props.onSelect.isDefined) 41 | props.onSelect 42 | else 43 | P.onSelect 44 | case _ => undefined 45 | } 46 | } 47 | 48 | def renderPageItem(child: ReactNode, index: Int) = { 49 | val keyAndRef = getChildKeyAndRef2(child, index) 50 | val propsMap = Map("onSelect" -> getOnSelectProps(child)) 51 | cloneWithProps(child, keyAndRef, propsMap) 52 | } 53 | 54 | <.ul(^.classSet1(P.addClasses, "pager" -> true))( 55 | ValidComponentChildren.map(C, renderPageItem)) 56 | }.build 57 | } 58 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/PanelGroup.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.components.bootstrap.Utils._ 4 | import com.acework.js.utils.{Mappable, Mergeable} 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | import scala.scalajs.js 9 | import scala.scalajs.js.{UndefOr, undefined} 10 | 11 | /** 12 | * Created by weiyin on 10/03/15. 13 | */ 14 | object PanelGroup extends BootstrapComponent { 15 | override type P = PanelGroup 16 | override type S = State 17 | override type B = Backend 18 | override type N = TopNode 19 | 20 | override def defaultProps = PanelGroup() 21 | 22 | case class State(activeKey: UndefOr[String]) 23 | 24 | case class PanelGroup(activeKey: UndefOr[String] = undefined, 25 | accordion: UndefOr[Boolean] = undefined, 26 | collapsable: UndefOr[Boolean] = undefined, 27 | onSelect: UndefOr[(UndefOr[String]) => Unit] = undefined, 28 | defaultActiveKey: UndefOr[String] = undefined, 29 | bsClass: UndefOr[Classes.Value] = Classes.`panel-group`, 30 | bsStyle: UndefOr[Styles.Value] = undefined, 31 | bsSize: UndefOr[Sizes.Value] = undefined, 32 | addClasses: String = "") extends BsProps with MergeableProps[PanelGroup] { 33 | 34 | def merge(t: Map[String, Any]): PanelGroup = implicitly[Mergeable[PanelGroup]].merge(this, t) 35 | 36 | def asMap: Map[String, Any] = implicitly[Mappable[PanelGroup]].toMap(this) 37 | 38 | def apply(children: ReactNode*) = component(this, children) 39 | 40 | def apply() = component(this) 41 | } 42 | 43 | 44 | class Backend($: BackendScope[PanelGroup, State]) { 45 | def children: js.Any = $._props.asInstanceOf[js.Dynamic].children 46 | 47 | var isChanging: Boolean = false 48 | 49 | def handleSelect(e: ReactEvent, key: UndefOr[String]): Unit = { 50 | e.preventDefault() 51 | 52 | if ($.props.onSelect.isDefined) { 53 | isChanging = true 54 | $.props.onSelect.get(key) 55 | isChanging = false 56 | } 57 | var newKey: UndefOr[String] = key 58 | if ($.state.activeKey == key) 59 | newKey = undefined 60 | 61 | $.modState(s => s.copy(activeKey = newKey)) 62 | } 63 | 64 | def shouldComponentUpdate = { 65 | // defer any updates to this component during the onselect handler 66 | !isChanging 67 | } 68 | } 69 | 70 | override val component = ReactComponentB[PanelGroup]("PanelGroup") 71 | .initialStateP(P => State(P.defaultActiveKey)) 72 | .backend(new Backend(_)) 73 | .render { 74 | (P, C, S, B) => 75 | 76 | def renderPanel(child: ReactNode, index: Int) = { 77 | val activeKey = if (P.activeKey.isDefined) P.activeKey else S.activeKey 78 | 79 | val childPropsAny = getChildProps[Any](child) 80 | 81 | val (eventKey, bsStyle) = 82 | childPropsAny match { 83 | case props: Panel.Panel => 84 | (props.eventKey, props.bsStyle) 85 | 86 | case _ => (undefined, undefined) 87 | } 88 | 89 | var propsMap = Map[String, Any]("bsStyle" -> (if (bsStyle.isDefined) bsStyle else P.bsStyle)) 90 | 91 | val keyAndRef = getChildKeyAndRef2(child, index) 92 | if (P.accordion.getOrElse(false)) { 93 | propsMap ++= Map( 94 | "collapsable" -> true, 95 | "expanded" -> (eventKey.getOrElse("NA1") == activeKey.getOrElse("NA2")), 96 | "onSelect" -> B.handleSelect _) 97 | } 98 | 99 | cloneWithProps(child, keyAndRef, propsMap) 100 | } 101 | 102 | <.div(^.classSet1M(P.addClasses, P.bsClassSet))( 103 | ValidComponentChildren.map(C, renderPanel) 104 | ) 105 | } 106 | .shouldComponentUpdate((scope, _, _) => scope.backend.shouldComponentUpdate) 107 | .build 108 | 109 | } 110 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Placements.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | /** 4 | * Created by weiyin on 17/03/15. 5 | */ 6 | object Placements extends Enumeration { 7 | val top, right, bottom, left = Value 8 | } 9 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Popover.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | 7 | import scala.scalajs.js.{UndefOr, undefined} 8 | 9 | /** 10 | * Created by weiyin on 10/03/15. 11 | */ 12 | object Popover extends BootstrapComponent { 13 | override type P = Popover 14 | override type S = Unit 15 | override type B = Unit 16 | override type N = TopNode 17 | 18 | override def defaultProps = Popover() 19 | 20 | case class Popover(title: UndefOr[ReactNode] = undefined, 21 | placement: Placements.Value = Placements.right, 22 | positionLeft: UndefOr[Int] = undefined, 23 | positionTop: UndefOr[Int] = undefined, 24 | arrowOffsetLeft: UndefOr[Int] = undefined, 25 | arrowOffsetTop: UndefOr[Int] = undefined, 26 | bsClass: UndefOr[Classes.Value] = Classes.btn, 27 | bsStyle: UndefOr[Styles.Value] = Styles.default, 28 | bsSize: UndefOr[Sizes.Value] = undefined, 29 | addClasses: String = "") extends BsProps with MergeableProps[Popover] { 30 | 31 | def merge(t: Map[String, Any]): Popover = implicitly[Mergeable[Popover]].merge(this, t) 32 | 33 | def asMap: Map[String, Any] = implicitly[Mappable[Popover]].toMap(this) 34 | 35 | def apply(children: ReactNode*) = component(this, children) 36 | 37 | def apply() = component(this) 38 | } 39 | 40 | override val component = ReactComponentB[Popover]("Popover") 41 | .render { (P, C) => 42 | def renderTitle() = { 43 | <.h3(^.className := "popover-title", P.title) 44 | } 45 | 46 | val classes = Map("popover" -> true, 47 | P.placement.toString -> true, 48 | "in" -> (P.positionLeft.isDefined || P.positionTop.isDefined) 49 | ) 50 | 51 | <.div(^.classSet1M(P.addClasses, classes), ^.left := P.positionLeft, ^.top := P.positionTop, 52 | ^.display := "block", 53 | <.div(^.className := "arrow", ^.left := P.arrowOffsetLeft, ^.top := P.arrowOffsetTop), 54 | if (P.title.isDefined) renderTitle() else EmptyTag, 55 | <.div(^.className := "popover-content")(C) 56 | ) 57 | }.build 58 | 59 | } 60 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/ProgressBar.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.components.bootstrap.Utils._ 4 | import com.acework.js.utils.{Mappable, Mergeable} 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | import scala.scalajs.js.{UndefOr, undefined} 9 | 10 | /** 11 | * Created by weiyin on 10/03/15. 12 | */ 13 | object ProgressBar extends BootstrapComponent { 14 | override type P = ProgressBar 15 | override type S = Unit 16 | override type B = Unit 17 | override type N = TopNode 18 | 19 | override def defaultProps = ProgressBar() 20 | 21 | 22 | case class ProgressBar( 23 | min: UndefOr[Double] = 0, 24 | now: UndefOr[Double] = undefined, 25 | max: UndefOr[Double] = 100, 26 | label: UndefOr[ReactNode] = undefined, 27 | srOnly: UndefOr[Boolean] = undefined, 28 | striped: UndefOr[Boolean] = undefined, 29 | active: UndefOr[Boolean] = undefined, 30 | isChild: UndefOr[Boolean] = undefined, 31 | interpolateClass: UndefOr[ReactNode] = undefined, 32 | bsClass: UndefOr[Classes.Value] = Classes.`progress-bar`, 33 | bsStyle: UndefOr[Styles.Value] = Styles.default, 34 | bsSize: UndefOr[Sizes.Value] = undefined, 35 | addClasses: String = "") extends BsProps with MergeableProps[ProgressBar] { 36 | 37 | def merge(t: Map[String, Any]): ProgressBar = implicitly[Mergeable[ProgressBar]].merge(this, t) 38 | 39 | def asMap: Map[String, Any] = implicitly[Mappable[ProgressBar]].toMap(this) 40 | 41 | def apply(children: ReactNode*) = component(this, children) 42 | 43 | def apply() = component(this) 44 | } 45 | 46 | val component = ReactComponentB[ProgressBar]("Progressbar") 47 | .render((P, C) => { 48 | 49 | def getPercentage(now: Double, min: Double, max: Double): Double = 50 | Math.ceil((now - min) / (max - min) * 100) 51 | 52 | def renderLabel(percentage: Double): ReactNode = { 53 | if (P.interpolateClass.isDefined) { 54 | //val interpolateClass = P.interpolateClass.get.reactTag 55 | // TODO P.interpolateClass 56 | "" 57 | } 58 | else { 59 | if (P.label.isDefined) 60 | Interpolate.Interpolate(now = P.now, min = P.min, max = P.max, percent = percentage, 61 | bsStyle = P.bsStyle)(P.label.get) 62 | else 63 | Interpolate.Interpolate(now = P.now, min = P.min, max = P.max, percent = percentage, 64 | bsStyle = P.bsStyle)() 65 | } 66 | } 67 | 68 | def renderScreenReaderOnlyLabel(label: ReactNode) = { 69 | <.span(^.className := "sr-only", label) 70 | } 71 | 72 | def renderChildBar(child: ReactNode, index: Int) = { 73 | val keyAndRef = getChildKeyAndRef2(child, index) 74 | cloneWithProps(child, keyAndRef, Map("isChild" -> true)) 75 | } 76 | 77 | def renderProgressBar() = { 78 | val percentage = getPercentage(P.now.getOrElse(0), P.min.getOrElse(0), P.max.getOrElse(100)) 79 | var label: ReactNode = if (!React.isValidElement(P.label)) 80 | renderLabel(percentage) 81 | else 82 | P.label.getOrElse("") 83 | 84 | if (P.srOnly.getOrElse(false)) 85 | label = renderScreenReaderOnlyLabel(label) 86 | 87 | // FIXME spread props 88 | <.div(^.classSet1M(P.addClasses, P.bsClassSet), ^.role := "progpressbar", 89 | ^.width := s"$percentage%", ^.aria.valuenow := P.now, ^.aria.valuemin := P.min, ^.aria.valuemax := P.max, 90 | label 91 | ) 92 | } 93 | 94 | var classes = Map("progress" -> true) 95 | 96 | if (P.active.getOrElse(false)) { 97 | classes += ("progress-striped" -> true) 98 | classes += ("active" -> true) 99 | } else if (P.striped.getOrElse(false)) 100 | classes += ("progress-striped" -> true) 101 | 102 | if (!ValidComponentChildren.hasValidComponents(C)) { 103 | if (!P.isChild.getOrElse(false)) { 104 | // FIXME spread props 105 | <.div(^.classSet1M(P.addClasses, classes), 106 | renderProgressBar() 107 | ) 108 | } 109 | else 110 | renderProgressBar() 111 | } 112 | else 113 | <.div(^.classSet1M(P.addClasses, classes), 114 | ValidComponentChildren.map(C, renderChildBar) 115 | ) 116 | 117 | }).build 118 | 119 | } 120 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Row.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import japgolly.scalajs.react._ 4 | import japgolly.scalajs.react.vdom.prefix_<^._ 5 | 6 | /** 7 | * Created by weiyin on 10/03/15. 8 | */ 9 | object Row extends BootstrapComponent { 10 | override type P = Row 11 | override type S = Unit 12 | override type B = Unit 13 | override type N = TopNode 14 | 15 | override def defaultProps = Row() 16 | 17 | case class Row(componentClass: String = "div", addClasses: String = "") { 18 | 19 | def apply(children: ReactNode*) = component(this, children) 20 | 21 | def apply() = component(this) 22 | } 23 | 24 | override val component = ReactComponentB[Row]("Row") 25 | .render { (P, C) => 26 | val componentClass = P.componentClass.reactTag 27 | componentClass(^.classSet1(P.addClasses, "row" -> true))(C) 28 | }.build 29 | 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Sizes.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | /** 4 | * Created by weiyin on 09/03/15. 5 | */ 6 | object Sizes extends Enumeration { 7 | val lg, md, sm, xs = Value 8 | } 9 | 10 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/SplitButton.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react.Ref 5 | 6 | /** 7 | * Created by weiyin on 11/03/15. 8 | */ 9 | 10 | import japgolly.scalajs.react._ 11 | import japgolly.scalajs.react.vdom.prefix_<^._ 12 | 13 | import scala.scalajs.js.{UndefOr, undefined} 14 | 15 | object SplitButton extends BootstrapComponent { 16 | override type P = SplitButton 17 | override type S = DropdownState 18 | override type B = Backend 19 | override type N = TopNode 20 | 21 | override def defaultProps = SplitButton() 22 | 23 | case class SplitButton( 24 | /*== start react bootstraps ==*/ 25 | id: UndefOr[String] = undefined, 26 | pullRight: UndefOr[Boolean] = undefined, 27 | title: UndefOr[ReactNode] = undefined, 28 | href: UndefOr[String] = undefined, 29 | target: UndefOr[String] = undefined, 30 | dropdownTitle: UndefOr[String] = "Toggle dropdown", 31 | onClick: UndefOr[(ReactEvent) => Unit] = undefined, 32 | onSelect: UndefOr[(String) => Unit] = undefined, 33 | disabled: UndefOr[Boolean] = undefined, 34 | dropup: UndefOr[Boolean] = undefined, 35 | /*== end react bootstraps ==*/ 36 | bsClass: UndefOr[Classes.Value] = Classes.btn, 37 | bsStyle: UndefOr[Styles.Value] = Styles.default, 38 | bsSize: UndefOr[Sizes.Value] = undefined, 39 | addClasses: String = "") 40 | extends BsProps with MergeableProps[SplitButton] { 41 | 42 | def merge(t: Map[String, Any]): SplitButton = implicitly[Mergeable[SplitButton]].merge(this, t) 43 | 44 | def asMap: Map[String, Any] = implicitly[Mappable[SplitButton]].toMap(this) 45 | 46 | def apply(children: ReactNode*) = component(this, children) 47 | 48 | def apply() = component(this) 49 | } 50 | 51 | class Backend(val scope: BackendScope[SplitButton, DropdownState]) extends DropdownStateMixin[SplitButton] { 52 | def handleButtonClick(e: ReactEvent) = { 53 | if (scope.state.open) 54 | setDropdownState(false) 55 | 56 | if (scope.props.onClick.isDefined) 57 | scope.props.onClick.get(e) // FIXME , scope.props.href, scope.props.target) 58 | } 59 | 60 | def handleOptionSelect(key: String): Unit = { 61 | if (scope.props.onSelect.isDefined) 62 | scope.props.onSelect.get(key) 63 | 64 | setDropdownState(false) 65 | } 66 | 67 | def handleDropdownClick(e: ReactEvent) = { 68 | e.preventDefault() 69 | setDropdownState(!scope.state.open) 70 | } 71 | } 72 | 73 | override val component = ReactComponentB[SplitButton]("SplitButton") 74 | .initialState(DropdownState(open = false)) 75 | .backend(new Backend(_)) 76 | .render((P, C, S, B) => { 77 | 78 | 79 | val buttonRef = Ref("button") 80 | val button = Button.withRef("button")(Button.Button(bsStyle = P.bsStyle, bsClass = P.bsClass, 81 | onClick = (e: ReactEvent) => B.handleButtonClick(e)), P.title.getOrElse(""): ReactNode) 82 | 83 | val dropdownButtonRef = Ref("dropdownButton") 84 | val dropdownButton = Button.withRef("dropdownButton")(Button.Button(bsStyle = P.bsStyle, bsClass = P.bsClass, addClasses = s"${P.addClasses} dropdown-toggle", 85 | onClick = (e: ReactEvent) => B.handleDropdownClick(e)), 86 | <.span(^.className := "sr-only", P.title), 87 | <.span(^.className := "caret")) 88 | 89 | val menuRef = Ref("menu") 90 | var groupClasses = "" 91 | if (S.open) 92 | groupClasses += " open" 93 | if (P.dropup.getOrElse(false)) 94 | groupClasses += " dropup" 95 | 96 | ButtonGroup(ButtonGroup.ButtonGroup(bsSize = P.bsSize, id = P.id, addClasses = groupClasses), button, dropdownButton, 97 | DropdownMenu.withRef("menu")(DropdownMenu.DropdownMenu(pullRight = P.pullRight, 98 | ariaLabelledBy = P.id, onSelect = (key: String) => B.handleOptionSelect(key)), C) 99 | ) 100 | 101 | }) 102 | .componentWillUnmount(_.backend.onComponentWillUnmount()) 103 | .build 104 | 105 | } 106 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Styles.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | /** 4 | * Created by weiyin on 09/03/15. 5 | */ 6 | // Common Bootstrap contextual styles 7 | object Styles extends Enumeration { 8 | val default, primary, success, info, warning, danger, link, inline, tabs, pills, error = Value 9 | } 10 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/SubNav.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.components.bootstrap.Utils._ 4 | import com.acework.js.utils.{Mappable, Mergeable} 5 | import japgolly.scalajs.react.Addons.ReactCloneWithProps 6 | import japgolly.scalajs.react._ 7 | import japgolly.scalajs.react.vdom.prefix_<^._ 8 | 9 | import scala.scalajs.js 10 | import scala.scalajs.js.{UndefOr, undefined} 11 | 12 | 13 | /** 14 | * Created by weiyin on 09/03/15. 15 | */ 16 | 17 | object SubNav extends BootstrapComponent { 18 | override type P = SubNav 19 | override type S = Unit 20 | override type B = Unit 21 | override type N = TopNode 22 | 23 | override def defaultProps = SubNav() 24 | 25 | case class SubNav(active: Boolean = true, 26 | disabled: Boolean = false, 27 | href: UndefOr[String] = undefined, 28 | target: UndefOr[String] = undefined, 29 | title: UndefOr[String] = undefined, 30 | text: UndefOr[String] = undefined, 31 | eventKey: UndefOr[String] = undefined, 32 | onSelect: UndefOr[(String) => Unit] = undefined, 33 | bsClass: UndefOr[Classes.Value] = Classes.nav, 34 | bsStyle: UndefOr[Styles.Value] = undefined, 35 | bsSize: UndefOr[Sizes.Value] = undefined, 36 | addClasses: String = "") extends BsProps with MergeableProps[SubNav] { 37 | 38 | def merge(t: Map[String, Any]): SubNav = implicitly[Mergeable[SubNav]].merge(this, t) 39 | 40 | def asMap: Map[String, Any] = implicitly[Mappable[SubNav]].toMap(this) 41 | 42 | def apply(children: ReactNode*) = component(this, children) 43 | 44 | def apply() = component(this) 45 | } 46 | 47 | // TODO 48 | def getChildActiveProp(child: ReactNode): Boolean = true 49 | 50 | override val component = ReactComponentB[SubNav]("SubNav") 51 | .render((P, C) => { 52 | 53 | val handleClick = (e: ReactEvent) => { 54 | if (P.onSelect.isDefined) { 55 | e.preventDefault() 56 | if (!P.disabled) 57 | P.onSelect.get(P.eventKey.getOrElse("")) // FIXME , P.href, P.target) 58 | } 59 | 60 | } 61 | 62 | def renderNavItem(child: ReactNode, idx: Int): ReactNode = { 63 | val keyAndRef = getChildKeyAndRef(child, idx) 64 | ReactCloneWithProps(child, keyAndRef ++ Map[String, js.Any]( 65 | "active" -> getChildActiveProp(child), 66 | "onSelect" -> P.onSelect.getOrElse(null) // FIXME create chain function 67 | ) 68 | ) 69 | } 70 | 71 | val classes = Map("active" -> P.active, "disabled" -> P.disabled) 72 | val anchorRef = Ref("anchor") 73 | <.li(^.classSet1M(P.addClasses, classes), 74 | <.a(^.href := P.href, ^.title := P.title, ^.target := P.target, 75 | ^.onClick ==> handleClick, ^.ref := anchorRef, P.text), 76 | <.ul(^.className := "nav", 77 | ValidComponentChildren.map(C, renderNavItem) 78 | ) 79 | ) 80 | } 81 | ).build 82 | 83 | } 84 | 85 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/TabPane.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{TransitionEvent, Mappable, Mergeable} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | import org.scalajs.dom.Event 7 | 8 | import scala.scalajs.js 9 | import scala.scalajs.js.{UndefOr, undefined} 10 | import Utils._ 11 | 12 | /** 13 | * Created by weiyin on 22/03/15. 14 | */ 15 | object TabPane extends BootstrapComponent { 16 | override type P = TabPane 17 | override type S = State 18 | override type B = Backend 19 | override type N = TopNode 20 | 21 | override def defaultProps = TabPane() 22 | 23 | case class TabPane(active: Boolean = false, 24 | animation: Boolean = true, 25 | tab: UndefOr[String] = undefined, 26 | eventKey: UndefOr[String] = undefined, 27 | onAnimateOutEnd: UndefOr[() => Unit] = undefined, 28 | bsStyle: UndefOr[Styles.Value] = Styles.default, 29 | addClasses: String = "") extends MergeableProps[TabPane] { 30 | 31 | def merge(t: Map[String, Any]): TabPane = implicitly[Mergeable[TabPane]].merge(this, t) 32 | 33 | def asMap: Map[String, Any] = implicitly[Mappable[TabPane]].toMap(this) 34 | 35 | def apply(children: ReactNode*) = component(this, children) 36 | 37 | def apply() = component(this) 38 | 39 | } 40 | 41 | case class State(animateIn: Boolean, animateOut: Boolean) 42 | 43 | class Backend(scope: BackendScope[TabPane, State]) { 44 | 45 | def onComponentWillReceiveProps(nextProps: TabPane) = { 46 | if (scope.props.animation) { 47 | if (!scope.state.animateIn && nextProps.active && !scope.props.active) 48 | scope.modState(_.copy(animateIn = true)) 49 | else if (!scope.state.animateIn && !nextProps.active && scope.props.active) 50 | scope.modState(_.copy(animateOut = true)) 51 | } 52 | } 53 | 54 | def onComponentDidUpdate() = { 55 | if (scope.state.animateIn) 56 | js.timers.setTimeout(0)(startAnimateIn()) 57 | if (scope.state.animateOut) 58 | TransitionEvent.addEndEventListener( 59 | scope.getDOMNode(), (_: Event) => stopAnimateOut() 60 | ) 61 | } 62 | 63 | def startAnimateIn() = { 64 | if (scope.isMounted()) 65 | scope.modState(_.copy(animateIn = false)) 66 | } 67 | 68 | def stopAnimateOut() = { 69 | if (scope.isMounted()) 70 | scope.modState(_.copy(animateOut = false)) 71 | 72 | if (scope.props.onAnimateOutEnd.isDefined) 73 | scope.props.onAnimateOutEnd.get() 74 | } 75 | } 76 | 77 | override val component = ReactComponentB[TabPane]("TabPane") 78 | .initialState(State(animateIn = false, animateOut = false)) 79 | .backend(new Backend(_)) 80 | .render((P, C, S, B) => { 81 | val classes = Map( 82 | "active" -> (P.active || S.animateOut), 83 | "in" -> (P.active && !S.animateIn) 84 | ) 85 | // FIXME spread props 86 | <.div(^.classSet1M("tab-pane fade", classes), C) 87 | }) 88 | .componentWillReceiveProps((scope, nextProps) => { 89 | scope.backend.onComponentWillReceiveProps(nextProps) 90 | }) 91 | .componentDidUpdate((scope, prevProps, prevState) => scope.backend.onComponentDidUpdate()) 92 | .build 93 | 94 | } 95 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/TabbedArea.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | 7 | import scala.scalajs.js 8 | import scala.scalajs.js.{UndefOr, undefined} 9 | import Utils._ 10 | 11 | /** 12 | * Created by weiyin on 22/03/15. 13 | */ 14 | object TabbedArea extends BootstrapComponent { 15 | override type P = TabbedArea 16 | override type S = State 17 | override type B = Backend 18 | override type N = TopNode 19 | 20 | override def defaultProps = TabbedArea() 21 | 22 | 23 | case class TabbedArea(id: UndefOr[String] = undefined, 24 | activeKey: UndefOr[String] = undefined, 25 | animation: Boolean = true, 26 | defaultActiveKey: UndefOr[String] = undefined, 27 | onSelect: UndefOr[Seq[UndefOr[String]] => Unit] = undefined, 28 | bsStyle: UndefOr[Styles.Value] = Styles.tabs, 29 | addClasses: String = "") extends MergeableProps[TabbedArea] { 30 | 31 | def merge(t: Map[String, Any]): TabbedArea = implicitly[Mergeable[TabbedArea]].merge(this, t) 32 | 33 | def asMap: Map[String, Any] = implicitly[Mappable[TabbedArea]].toMap(this) 34 | 35 | def apply(children: ReactNode*) = component(this, children) 36 | 37 | def apply() = component(this) 38 | } 39 | 40 | case class State(activeKey: UndefOr[String] = undefined, previousActiveKey: UndefOr[String] = undefined) 41 | 42 | case class Backend(scope: BackendScope[TabbedArea, State]) { 43 | var _isChanging = false 44 | 45 | def handlePaneAnimateOutEnd() = { 46 | scope.modState(_.copy(previousActiveKey = undefined)) 47 | } 48 | 49 | def handleSelect(args: Seq[UndefOr[String]]): Unit = { 50 | val key = args.head 51 | if (scope.props.onSelect.isDefined) { 52 | _isChanging = true 53 | scope.props.onSelect.get(args) 54 | _isChanging = false 55 | } else if (key.get != getActiveKey) { 56 | scope.setState(State(key, getActiveKey)) 57 | } 58 | } 59 | 60 | def onComponentWillReceiveProps(nextProps: TabbedArea): Unit = { 61 | if (nextProps.activeKey.isDefined && nextProps.activeKey.get != scope.props.activeKey.getOrElse(false)) 62 | scope.modState(_.copy(previousActiveKey = scope.props.activeKey)) 63 | } 64 | 65 | def getActiveKey: String = { 66 | if (scope.props.activeKey.isDefined) 67 | scope.props.activeKey.get 68 | else 69 | scope.state.activeKey.getOrElse("") 70 | } 71 | 72 | def shouldComponentUpdate(): Boolean = !_isChanging 73 | } 74 | 75 | override val component = ReactComponentB[TabbedArea]("PanelGroup") 76 | // FIXME get default from children 77 | .initialStateP(P => State(P.defaultActiveKey)) 78 | .backend(new Backend(_)) 79 | .render( 80 | (P, C, S, B) => { 81 | val activeKey = if (P.activeKey.isDefined) P.activeKey else S.activeKey 82 | 83 | def getChildEventKey(child: ReactNode): UndefOr[String] = { 84 | val childPropsAny = getChildProps[Any](child) 85 | 86 | childPropsAny match { 87 | case props: TabPane.TabPane => 88 | props.eventKey 89 | case _ => undefined 90 | } 91 | } 92 | def getChildTab(child: ReactNode): UndefOr[String] = { 93 | val childPropsAny = getChildProps[Any](child) 94 | 95 | childPropsAny match { 96 | case props: TabPane.TabPane => 97 | props.tab 98 | case _ => undefined 99 | } 100 | } 101 | 102 | def renderTab(child: ReactNode, childTab: ReactNode): TagMod = { 103 | val eventKey = getChildEventKey(child) 104 | NavItem.withRef(s"tab$eventKey")(NavItem.NavItem(eventKey = eventKey), childTab) 105 | } 106 | 107 | def renderPane(child: ReactNode, index: Int) = { 108 | 109 | val eventKey = getChildEventKey(child) 110 | 111 | val keyAndRef = getChildKeyAndRef2(child, index) 112 | val active = eventKey.getOrElse("NA1") == activeKey.getOrElse("NA2") && 113 | (S.previousActiveKey.isEmpty || !P.animation) 114 | 115 | var propsMap = Map[String, Any]( 116 | "active" -> active, 117 | "animation" -> P.animation 118 | ) 119 | 120 | if (S.previousActiveKey.isDefined && eventKey.getOrElse("NA1") == S.previousActiveKey.get) 121 | propsMap += ("onAnimateOutEnd" -> B.handlePaneAnimateOutEnd _) 122 | 123 | cloneWithProps(child, keyAndRef, propsMap) 124 | } 125 | 126 | def renderTabIfSet(child: ReactNode, index: Int) = { 127 | val tab = getChildTab(child) 128 | if (tab.isDefined) 129 | renderTab(child, tab.get) 130 | else 131 | EmptyTag 132 | } 133 | 134 | val nav = Nav.withRef("tabs")(Nav.Nav(activeKey = activeKey, 135 | addClasses = s"nav-${P.bsStyle.toString}", 136 | onSelect = (args: Seq[UndefOr[String]]) => B.handleSelect(args)), 137 | ValidComponentChildren.map(C, renderTabIfSet) 138 | ) 139 | 140 | val panesRef = Ref("panes") 141 | <.div( 142 | nav, 143 | <.div(^.id := P.id, ^.className := "tab-content", ^.ref := panesRef, 144 | ValidComponentChildren.map(C, renderPane) 145 | ) 146 | ) 147 | }) 148 | .componentWillReceiveProps((scope, nextProps) => scope.backend.onComponentWillReceiveProps(nextProps)) 149 | .shouldComponentUpdate((scope, _, _) => scope.backend.shouldComponentUpdate()) 150 | .build 151 | } 152 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Table.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import japgolly.scalajs.react._ 4 | import japgolly.scalajs.react.vdom.prefix_<^._ 5 | 6 | import scala.scalajs.js._ 7 | 8 | 9 | /** 10 | * Created by weiyin on 10/03/15. 11 | */ 12 | object Table extends BootstrapComponent { 13 | override type P = Table 14 | override type S = Unit 15 | override type B = Unit 16 | override type N = TopNode 17 | 18 | override def defaultProps = Table() 19 | 20 | case class Table(striped: UndefOr[Boolean] = undefined, 21 | bordered: UndefOr[Boolean] = undefined, 22 | condensed: UndefOr[Boolean] = undefined, 23 | hover: UndefOr[Boolean] = undefined, 24 | responsive: UndefOr[Boolean] = undefined, 25 | addClasses: String = "") { 26 | 27 | def apply(children: ReactNode*) = component(this, children) 28 | 29 | def apply() = component(this) 30 | } 31 | 32 | val component = ReactComponentB[Table]("Table") 33 | .stateless 34 | .render { (P, C, _) => 35 | val classes = Map( 36 | "table" -> true, 37 | "table-striped" -> P.striped.getOrElse(false), 38 | "table-bordered" -> P.bordered.getOrElse(false), 39 | "table-condensed" -> P.condensed.getOrElse(false), 40 | "table-hover" -> P.hover.getOrElse(false) 41 | ) 42 | 43 | val table = <.table(^.classSet1M(P.addClasses, classes))(C) 44 | 45 | if (P.responsive.getOrElse(false)) 46 | <.div(^.className := "table-responsive")(table) 47 | else 48 | table 49 | }.build 50 | 51 | } 52 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Tooltip.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | 7 | import scala.scalajs.js.{UndefOr, undefined} 8 | 9 | /** 10 | * Created by weiyin on 09/03/15. 11 | */ 12 | object Tooltip extends BootstrapComponent { 13 | override type P = Tooltip 14 | override type S = Unit 15 | override type B = Unit 16 | override type N = TopNode 17 | 18 | override def defaultProps = Tooltip() 19 | 20 | case class Tooltip(placement: UndefOr[Placements.Value] = Placements.right, 21 | positionLeft: UndefOr[Int] = undefined, 22 | positionTop: UndefOr[Int] = undefined, 23 | arrowOffsetLeft: UndefOr[Int] = undefined, 24 | arrowOffsetTop: UndefOr[Int] = undefined, 25 | bsClass: UndefOr[Classes.Value] = Classes.panel, 26 | bsStyle: UndefOr[Styles.Value] = Styles.default, 27 | bsSize: UndefOr[Sizes.Value] = undefined, 28 | addClasses: String = "") extends BsProps with MergeableProps[Tooltip] { 29 | 30 | def merge(t: Map[String, Any]): Tooltip = implicitly[Mergeable[Tooltip]].merge(this, t) 31 | 32 | def asMap: Map[String, Any] = implicitly[Mappable[Tooltip]].toMap(this) 33 | 34 | def apply(children: ReactNode*) = component(this, children) 35 | 36 | def apply() = component(this) 37 | } 38 | 39 | override val component = ReactComponentB[Tooltip]("Tooltip") 40 | .render((P, C) => { 41 | val classes = Map(P.placement.get.toString -> true, 42 | "in" -> (P.positionLeft.isDefined || P.positionTop.isDefined) 43 | ) 44 | 45 | <.div(^.classSet1M("tooltip", classes), ^.left := P.positionLeft, ^.top := P.positionTop, 46 | <.div(^.className := "tooltip-arrow", ^.left := P.arrowOffsetLeft, ^.top := P.arrowOffsetTop), 47 | <.div(^.className := "tooltip-inner", C) 48 | ) 49 | }) 50 | .build 51 | 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/Well.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components.bootstrap 2 | 3 | import com.acework.js.utils.{Mappable, Mergeable} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | 7 | import scala.scalajs.js.{UndefOr, undefined} 8 | 9 | /** 10 | * Created by weiyin on 10/03/15. 11 | */ 12 | object Well extends BootstrapComponent { 13 | override type P = Well 14 | override type S = Unit 15 | override type B = Unit 16 | override type N = TopNode 17 | 18 | override def defaultProps = Well() 19 | 20 | case class Well(bsClass: UndefOr[Classes.Value] = Classes.well, 21 | bsStyle: UndefOr[Styles.Value] = undefined, 22 | bsSize: UndefOr[Sizes.Value] = undefined, 23 | addClasses: String = "") 24 | extends BsProps with MergeableProps[Well] { 25 | 26 | def merge(t: Map[String, Any]): Well = implicitly[Mergeable[Well]].merge(this, t) 27 | 28 | def asMap: Map[String, Any] = implicitly[Mappable[Well]].toMap(this) 29 | 30 | def apply(children: ReactNode*) = component(this, children) 31 | 32 | def apply() = component(this) 33 | } 34 | 35 | override val component = ReactComponentB[Well]("Well") 36 | .render { (P, C) => 37 | 38 | // TODO spread props 39 | <.div(^.classSet1M(P.addClasses, P.bsClassSet))(C) 40 | 41 | }.build 42 | 43 | } 44 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/components/bootstrap/package.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.components 2 | 3 | import scala.scalajs.js 4 | 5 | package object bootstrap extends js.GlobalScope { 6 | val jQuery: JQueryStatic = js.native 7 | 8 | import scala.language.implicitConversions 9 | 10 | implicit def jq2bootstrap(jq: JQuery): BootstrapJQuery = jq.asInstanceOf[BootstrapJQuery] 11 | 12 | } 13 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/logger/Log4JavaScript.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.logger 2 | 3 | import scala.annotation.elidable 4 | import scala.scalajs.js 5 | import scala.scalajs.js.annotation.JSName 6 | 7 | /** 8 | * Facade for functions in log4javascript that we need 9 | */ 10 | private[logger] trait Log4JavaScript extends js.Object { 11 | def getLogger(name:js.UndefOr[String]):JSLogger = js.native 12 | def setEnabled(enabled:Boolean):Unit = js.native 13 | def isEnabled:Boolean = js.native 14 | } 15 | 16 | @JSName("log4javascript.Level") 17 | private[logger] trait Level extends js.Object { 18 | val ALL:Level = js.native 19 | val TRACE:Level = js.native 20 | val DEBUG:Level = js.native 21 | val INFO:Level = js.native 22 | val WARN:Level = js.native 23 | val ERROR:Level = js.native 24 | val FATAL:Level = js.native 25 | } 26 | 27 | @JSName("log4javascript.Logger") 28 | private[logger] trait JSLogger extends js.Object { 29 | def addAppender(appender:Appender):Unit = js.native 30 | def removeAppender(appender:Appender):Unit = js.native 31 | def removeAllAppenders(appender:Appender):Unit = js.native 32 | def setLevel(level:Level):Unit = js.native 33 | def getLevel:Level = js.native 34 | def trace(msg:String, error:js.UndefOr[js.Error]):Unit = js.native 35 | def debug(msg:String, error:js.UndefOr[js.Error]):Unit = js.native 36 | def info(msg:String, error:js.UndefOr[js.Error]):Unit = js.native 37 | def warn(msg:String, error:js.UndefOr[js.Error]):Unit = js.native 38 | def error(msg:String, error:js.UndefOr[js.Error]):Unit = js.native 39 | def fatal(msg:String, error:js.UndefOr[js.Error]):Unit = js.native 40 | def trace(msg:String):Unit = js.native 41 | def debug(msg:String):Unit = js.native 42 | def info(msg:String):Unit = js.native 43 | def warn(msg:String):Unit = js.native 44 | def error(msg:String):Unit = js.native 45 | def fatal(msg:String):Unit = js.native 46 | } 47 | 48 | @JSName("log4javascript.Layout") 49 | private[logger] trait Layout extends js.Object 50 | 51 | @JSName("log4javascript.JsonLayout") 52 | private[logger] class JsonLayout extends Layout 53 | 54 | @JSName("log4javascript.Appender") 55 | private[logger] trait Appender extends js.Object { 56 | def setLayout(layout:Layout):Unit = js.native 57 | def setThreshold(level:Level):Unit = js.native 58 | } 59 | 60 | @JSName("log4javascript.BrowserConsoleAppender") 61 | private[logger] class BrowserConsoleAppender extends Appender 62 | 63 | @JSName("log4javascript.PopUpAppender") 64 | private[logger] class PopUpAppender extends Appender 65 | 66 | @JSName("log4javascript.AjaxAppender") 67 | private[logger] class AjaxAppender(url:String) extends Appender { 68 | def addHeader(header:String, value:String):Unit = js.native 69 | } 70 | 71 | private[logger] object Log4JavaScript extends js.GlobalScope { 72 | val log4javascript:Log4JavaScript = js.native 73 | } 74 | 75 | class L4JSLogger(jsLogger:JSLogger) extends Logger { 76 | 77 | private var ajaxAppender:AjaxAppender = null 78 | 79 | private def undefOrError(e:Exception):js.UndefOr[js.Error] = { 80 | if(e == null) 81 | js.undefined 82 | else 83 | e.asInstanceOf[js.Error] 84 | } 85 | 86 | override def trace(msg: String, e: Exception): Unit = jsLogger.trace(msg, undefOrError(e)) 87 | override def trace(msg: String): Unit = jsLogger.trace(msg) 88 | override def debug(msg: String, e: Exception): Unit = jsLogger.debug(msg, undefOrError(e)) 89 | override def debug(msg: String): Unit = jsLogger.debug(msg) 90 | override def info(msg: String, e: Exception): Unit = jsLogger.info(msg, undefOrError(e)) 91 | override def info(msg: String): Unit = jsLogger.info(msg) 92 | override def warn(msg: String, e: Exception): Unit = jsLogger.warn(msg, undefOrError(e)) 93 | override def warn(msg: String): Unit = jsLogger.warn(msg) 94 | override def error(msg: String, e: Exception): Unit = jsLogger.error(msg, undefOrError(e)) 95 | override def error(msg: String): Unit = jsLogger.error(msg) 96 | override def fatal(msg: String, e: Exception): Unit = jsLogger.fatal(msg, undefOrError(e)) 97 | override def fatal(msg: String): Unit = jsLogger.fatal(msg) 98 | 99 | override def enableServerLogging(url: String): Unit = { 100 | if(ajaxAppender == null) { 101 | ajaxAppender = new AjaxAppender(url) 102 | ajaxAppender.addHeader("Content-Type", "application/json") 103 | ajaxAppender.setLayout(new JsonLayout) 104 | jsLogger.addAppender(ajaxAppender) 105 | 106 | } 107 | } 108 | 109 | override def disableServerLogging():Unit = { 110 | if(ajaxAppender != null) { 111 | jsLogger.removeAppender(ajaxAppender) 112 | ajaxAppender = null 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/logger/LoggerFactory.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.logger 2 | 3 | import scala.annotation.elidable 4 | import scala.annotation.elidable._ 5 | 6 | trait Logger { 7 | /* 8 | * Use @elidable annotation to completely exclude functions from the compiler generated byte-code based on 9 | * the specified level. In a production build most logging functions will simply disappear with no runtime 10 | * performance penalty. 11 | * 12 | * Specify level as a compiler parameter 13 | * > scalac -Xelide-below INFO 14 | */ 15 | @elidable(FINEST) def trace(msg: String, e: Exception): Unit 16 | @elidable(FINEST) def trace(msg: String): Unit 17 | @elidable(FINE) def debug(msg: String, e: Exception): Unit 18 | @elidable(FINE) def debug(msg: String): Unit 19 | @elidable(INFO) def info(msg: String, e: Exception): Unit 20 | @elidable(INFO) def info(msg: String): Unit 21 | @elidable(WARNING) def warn(msg: String, e: Exception): Unit 22 | @elidable(WARNING) def warn(msg: String): Unit 23 | @elidable(SEVERE) def error(msg: String, e: Exception): Unit 24 | @elidable(SEVERE) def error(msg: String): Unit 25 | @elidable(SEVERE) def fatal(msg: String, e: Exception): Unit 26 | @elidable(SEVERE) def fatal(msg: String): Unit 27 | 28 | def enableServerLogging(url: String): Unit 29 | def disableServerLogging(): Unit 30 | } 31 | 32 | object LoggerFactory { 33 | private[logger] def createLogger(name: String) = {} 34 | 35 | lazy val consoleAppender = new BrowserConsoleAppender 36 | lazy val popupAppender = new PopUpAppender 37 | 38 | /** 39 | * Create a logger that outputs to browser console 40 | */ 41 | def getLogger(name: String): Logger = { 42 | val nativeLogger = Log4JavaScript.log4javascript.getLogger(name) 43 | nativeLogger.addAppender(consoleAppender) 44 | new L4JSLogger(nativeLogger) 45 | } 46 | 47 | /** 48 | * Create a logger that outputs to a separate popup window 49 | */ 50 | def getPopUpLogger(name: String): Logger = { 51 | val nativeLogger = Log4JavaScript.log4javascript.getLogger(name) 52 | nativeLogger.addAppender(popupAppender) 53 | new L4JSLogger(nativeLogger) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/logger/package.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js 2 | 3 | package object logger { 4 | private val defaultLogger = LoggerFactory.getLogger("Log") 5 | 6 | def log = defaultLogger 7 | } 8 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/utils/EventListener.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.utils 2 | 3 | import org.scalajs.dom.raw.{Event, EventTarget, HTMLElement} 4 | 5 | import org.scalajs.dom.{raw, document} 6 | 7 | import scala.scalajs.js 8 | 9 | /** 10 | * Created by weiyin on 11/03/15. 11 | */ 12 | 13 | trait EventListener { 14 | def remove(): Unit 15 | } 16 | 17 | object EventListener { 18 | 19 | /** 20 | * Listen to DOM events during the bubble phase. 21 | * 22 | * @param target DOM element to register listener on. 23 | * @param eventType Event type, e.g. 'click' or 'mouseover'. 24 | * @param listener Callback function. 25 | * @return EventListener with a `remove` method. 26 | * @return 27 | */ 28 | def listen[T <: Event](target: EventTarget, eventType: String, listener: (T) => _) = { 29 | target.addEventListener(eventType, listener, false) 30 | 31 | new EventListener { 32 | override def remove() = { 33 | target.removeEventListener(eventType, listener.asInstanceOf[js.Function1[Event, _]], false) 34 | } 35 | } 36 | } 37 | 38 | // TODO implement attachEvent 39 | /* 40 | if (target.attachEvent) { 41 | target.attachEvent('on ' +eventType, callback); 42 | return { 43 | remove: function () { 44 | target.detachEvent('on ' +eventType, callback); 45 | } 46 | }; 47 | }*/ 48 | 49 | } 50 | -------------------------------------------------------------------------------- /core/src/main/scala/com/acework/js/utils/TransitionEvent.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.utils 2 | 3 | import org.scalajs.dom._ 4 | import org.scalajs.dom.raw.HTMLElement 5 | 6 | /** 7 | * Created by weiyin on 20/03/15. 8 | */ 9 | object TransitionEvent { 10 | 11 | val EVENT_NAME_MAP = Map( 12 | "transitioned" -> Map( 13 | "transition" -> "transitionend", 14 | "WebkitTransition" -> "webkitTransitionEnd", 15 | "MozTransition" -> "mozTransitionEnd", 16 | "OTransition" -> "oTransitionEnd", 17 | "msTransition" -> "MSTransitionEnd" 18 | ), 19 | "animationend" -> Map( 20 | "animation" -> "animationend", 21 | "WebkitAnimation" -> "webkitAnimationEnd", 22 | "MozAnimation" -> "mozAnimationEnd", 23 | "OAnimation" -> "oAnimationEnd", 24 | "msAnimation" -> "MSAnimationEnd" 25 | ) 26 | ) 27 | 28 | lazy val endEvents: List[String] = { 29 | try { 30 | var events: List[String] = Nil 31 | val testEl = document.createElement("div").asInstanceOf[HTMLElement] 32 | val style = testEl.style 33 | 34 | // On some platforms, in particular some releases of Android 4.x, 35 | // the un-prefixed "animation" and "transition" properties are defined on the 36 | // style object but the events that fire will still be prefixed, so we need 37 | // to check if the un-prefixed events are usable, and if not remove them 38 | // from the map 39 | val excludeAnimationEvent = !window.hasOwnProperty("AnimationEvent") 40 | 41 | val excludeTransition = !window.hasOwnProperty("TransitionEvent") 42 | 43 | for ((baseEventName, baseEvents) <- EVENT_NAME_MAP) { 44 | for ((styleName, eventName) <- baseEvents 45 | if style.hasOwnProperty(styleName) && 46 | !(excludeAnimationEvent && styleName == "animation") && 47 | !(excludeTransition && styleName == "transition")) { 48 | events = eventName :: events 49 | } 50 | } 51 | events 52 | } 53 | catch { 54 | case e: Throwable => 55 | Nil 56 | } 57 | } 58 | 59 | def addEventListener(node: EventTarget, eventName: String, listener: (Event) => _) = { 60 | node.addEventListener(eventName, listener, false) 61 | } 62 | 63 | def removeEventListener(node: EventTarget, eventName: String, listener: (Event) => _) = { 64 | node.removeEventListener(eventName, listener, false) 65 | } 66 | 67 | def addEndEventListener(node: EventTarget, listener: (Event) => _) = { 68 | if (endEvents.isEmpty) { 69 | // if CSS transitions are not supported, trigger an "end animation event immediately 70 | //window.setTimeout(listener.asInstanceOf[Function0[Any]], 0) 71 | window.setTimeout(() => listener(null), 0) 72 | } 73 | else { 74 | endEvents.foreach { endEvent => 75 | addEventListener(node, endEvent, listener) 76 | } 77 | } 78 | } 79 | 80 | def removeEndEventListener(node: EventTarget, listener: (Event) => _) = { 81 | endEvents.foreach { endEvent => 82 | removeEventListener(node, endEvent, listener) 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /demo/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiyinteo/scalajs-react-bootstrap/34512f77fe9524bc1bdd4518454861d6339cacf0/demo/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /demo/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiyinteo/scalajs-react-bootstrap/34512f77fe9524bc1bdd4518454861d6339cacf0/demo/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /demo/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiyinteo/scalajs-react-bootstrap/34512f77fe9524bc1bdd4518454861d6339cacf0/demo/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /demo/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiyinteo/scalajs-react-bootstrap/34512f77fe9524bc1bdd4518454861d6339cacf0/demo/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 30 | 31 | 32 | 33 | 34 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /demo/res/app.css: -------------------------------------------------------------------------------- 1 | .navbar-brand { 2 | 3 | cursor: pointer; 4 | } 5 | 6 | li { 7 | list-style-type: none; 8 | cursor: pointer; 9 | } 10 | 11 | 12 | pre { 13 | overflow-x: auto; 14 | } 15 | pre code { 16 | overflow-wrap: normal; 17 | white-space: pre; 18 | } 19 | 20 | 21 | .container { 22 | margin-left: 15px; 23 | margin-right: 15px; 24 | width: 1300px; 25 | } 26 | 27 | 28 | .example-enter { 29 | opacity: 0.01; 30 | transition: opacity .5s ease-in; 31 | } 32 | 33 | .example-enter.example-enter-active { 34 | opacity: 1; 35 | } 36 | .example-leave { 37 | opacity: 1; 38 | transition: opacity .5s ease-in; 39 | } 40 | 41 | .example-leave.example-leave-active { 42 | opacity: 0.01; 43 | } 44 | 45 | div.picture{ 46 | display: inline-block; 47 | margin: 5px; 48 | cursor:pointer; 49 | position: relative; 50 | } 51 | 52 | div.picture.favorite:after{ 53 | content: '❤'; 54 | position: absolute; 55 | font-size: 80px; 56 | line-height: 200px; 57 | color: #FF224D; 58 | width: 100%; 59 | text-align: center; 60 | left: 0; 61 | text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5); 62 | font-weight: bold; 63 | } 64 | 65 | .pictures, .favorites{ 66 | white-space: nowrap; 67 | overflow-y: auto; 68 | margin-bottom: 20px; 69 | height: 230px; 70 | background-color: #F3F3F3; 71 | } 72 | 73 | .pictures p, .favorites p { 74 | padding-top: 100px; 75 | font-size: 13px; 76 | } 77 | 78 | section.demo { 79 | padding-bottom: 4em; 80 | } 81 | 82 | .modal-container { 83 | position: relative; 84 | } 85 | 86 | .modal-container .modal, .modal-container .modal-backdrop { 87 | position: absolute; 88 | } -------------------------------------------------------------------------------- /demo/res/carousel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiyinteo/scalajs-react-bootstrap/34512f77fe9524bc1bdd4518454861d6339cacf0/demo/res/carousel.png -------------------------------------------------------------------------------- /demo/res/default.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Original style from softwaremaniacs.org (c) Ivan Sagalaev 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | background: #f0f0f0; 12 | -webkit-text-size-adjust: none; 13 | } 14 | 15 | .hljs, 16 | .hljs-subst, 17 | .hljs-tag .hljs-title, 18 | .nginx .hljs-title { 19 | color: black; 20 | } 21 | 22 | .hljs-string, 23 | .hljs-title, 24 | .hljs-constant, 25 | .hljs-parent, 26 | .hljs-tag .hljs-value, 27 | .hljs-rules .hljs-value, 28 | .hljs-preprocessor, 29 | .hljs-pragma, 30 | .haml .hljs-symbol, 31 | .ruby .hljs-symbol, 32 | .ruby .hljs-symbol .hljs-string, 33 | .hljs-template_tag, 34 | .django .hljs-variable, 35 | .smalltalk .hljs-class, 36 | .hljs-addition, 37 | .hljs-flow, 38 | .hljs-stream, 39 | .bash .hljs-variable, 40 | .apache .hljs-tag, 41 | .apache .hljs-cbracket, 42 | .tex .hljs-command, 43 | .tex .hljs-special, 44 | .erlang_repl .hljs-function_or_atom, 45 | .asciidoc .hljs-header, 46 | .markdown .hljs-header, 47 | .coffeescript .hljs-attribute { 48 | color: #800; 49 | } 50 | 51 | .smartquote, 52 | .hljs-comment, 53 | .hljs-annotation, 54 | .hljs-template_comment, 55 | .diff .hljs-header, 56 | .hljs-chunk, 57 | .asciidoc .hljs-blockquote, 58 | .markdown .hljs-blockquote { 59 | color: #888; 60 | } 61 | 62 | .hljs-number, 63 | .hljs-date, 64 | .hljs-regexp, 65 | .hljs-literal, 66 | .hljs-hexcolor, 67 | .smalltalk .hljs-symbol, 68 | .smalltalk .hljs-char, 69 | .go .hljs-constant, 70 | .hljs-change, 71 | .lasso .hljs-variable, 72 | .makefile .hljs-variable, 73 | .asciidoc .hljs-bullet, 74 | .markdown .hljs-bullet, 75 | .asciidoc .hljs-link_url, 76 | .markdown .hljs-link_url { 77 | color: #080; 78 | } 79 | 80 | .hljs-label, 81 | .hljs-javadoc, 82 | .ruby .hljs-string, 83 | .hljs-decorator, 84 | .hljs-filter .hljs-argument, 85 | .hljs-localvars, 86 | .hljs-array, 87 | .hljs-attr_selector, 88 | .hljs-important, 89 | .hljs-pseudo, 90 | .hljs-pi, 91 | .haml .hljs-bullet, 92 | .hljs-doctype, 93 | .hljs-deletion, 94 | .hljs-envvar, 95 | .hljs-shebang, 96 | .apache .hljs-sqbracket, 97 | .nginx .hljs-built_in, 98 | .tex .hljs-formula, 99 | .erlang_repl .hljs-reserved, 100 | .hljs-prompt, 101 | .asciidoc .hljs-link_label, 102 | .markdown .hljs-link_label, 103 | .vhdl .hljs-attribute, 104 | .clojure .hljs-attribute, 105 | .asciidoc .hljs-attribute, 106 | .lasso .hljs-attribute, 107 | .coffeescript .hljs-property, 108 | .hljs-phony { 109 | color: #88f; 110 | } 111 | 112 | .hljs-keyword, 113 | .hljs-id, 114 | .hljs-title, 115 | .hljs-built_in, 116 | .css .hljs-tag, 117 | .hljs-javadoctag, 118 | .hljs-phpdoc, 119 | .hljs-dartdoc, 120 | .hljs-yardoctag, 121 | .smalltalk .hljs-class, 122 | .hljs-winutils, 123 | .bash .hljs-variable, 124 | .apache .hljs-tag, 125 | .hljs-type, 126 | .hljs-typename, 127 | .tex .hljs-command, 128 | .asciidoc .hljs-strong, 129 | .markdown .hljs-strong, 130 | .hljs-request, 131 | .hljs-status { 132 | font-weight: bold; 133 | } 134 | 135 | .asciidoc .hljs-emphasis, 136 | .markdown .hljs-emphasis { 137 | font-style: italic; 138 | } 139 | 140 | .nginx .hljs-built_in { 141 | font-weight: normal; 142 | } 143 | 144 | .coffeescript .javascript, 145 | .javascript .xml, 146 | .lasso .markup, 147 | .tex .hljs-formula, 148 | .xml .javascript, 149 | .xml .vbscript, 150 | .xml .css, 151 | .xml .hljs-cdata { 152 | opacity: 0.5; 153 | } 154 | -------------------------------------------------------------------------------- /demo/res/docs.css: -------------------------------------------------------------------------------- 1 | /* 2 | * React Bootstrap Documentation 3 | * Special styles for presenting react-bootstrap's documentation and code examples. 4 | * Based on the Bootstrap Documentation styles and overridden as necessary. 5 | */ 6 | 7 | body { 8 | background-color: #f9f9f9; 9 | } 10 | 11 | .bs-docs-nav { 12 | background-color: #222; 13 | } 14 | 15 | .bs-docs-nav .navbar-nav>li>a { 16 | color: #aaa; 17 | font-weight: normal; 18 | } 19 | 20 | .bs-docs-nav .navbar-nav>li>a:hover,.bs-docs-nav .navbar-nav>.active>a, .bs-docs-nav .navbar-nav>.active>a:hover { 21 | background: #333; 22 | color: #fafafa; 23 | } 24 | 25 | .bs-docs-nav .navbar-collapse { 26 | overflow: hidden; 27 | } 28 | 29 | @media (min-width: 768px) { 30 | 31 | .bs-docs-nav .navbar-nav>li>a { 32 | border-bottom: 3px solid #222; 33 | } 34 | .bs-docs-nav .navbar-nav>li>a:hover,.bs-docs-nav .navbar-nav>.active>a, .bs-docs-nav .navbar-nav>.active>a:hover { 35 | border-bottom: 3px solid #cc7a6f; 36 | } 37 | } 38 | 39 | .navbar>.container .navbar-brand, .navbar>.container-fluid .navbar-brand { 40 | color: #00d8ff; 41 | } 42 | 43 | .bs-docs-masthead, .bs-docs-header { 44 | background: #2d2d2d; 45 | filter: none; 46 | color: #e9e9e9; 47 | } 48 | 49 | .bs-docs-header h1 { 50 | color: #e9e9e9; 51 | } 52 | 53 | .bs-docs-header p { 54 | color: #e9e9e9; 55 | } 56 | 57 | .bs-docs-sidebar .nav>li>a { 58 | color: #666; 59 | } 60 | 61 | .bs-docs-sidebar .nav>li>a:hover, .bs-docs-sidebar .nav>li>a:focus { 62 | color: #cc7a6f; 63 | border-left: 1px solid #cc7a6f; 64 | } 65 | 66 | .back-to-top:hover { 67 | color: #cc7a6f; 68 | } 69 | 70 | 71 | .CodeMirror { 72 | height: auto; 73 | } 74 | 75 | .bs-example .btn-toolbar + .btn-toolbar { 76 | margin-top: 10px; 77 | } 78 | 79 | .bs-example .static-modal .modal { 80 | position: relative; 81 | top: auto; 82 | right: auto; 83 | left: auto; 84 | bottom: auto; 85 | z-index: 1; 86 | display: block; 87 | } 88 | 89 | .bs-docs-booticon { 90 | background: url('./logo.png') 0 0 no-repeat; 91 | background-size: contain; 92 | border: 0; 93 | width: 200px; 94 | height: 200px; 95 | } 96 | 97 | .bs-example-scroll { 98 | overflow: scroll; 99 | height: 200px; 100 | } 101 | 102 | .bs-example-scroll > div { 103 | position: relative; 104 | padding: 100px 0; 105 | } 106 | 107 | .playground { 108 | margin-bottom: 36px; 109 | } 110 | 111 | .bs-example { 112 | margin-bottom: 0; 113 | } 114 | 115 | .bs-example + .highlight { 116 | margin-top: 0; 117 | margin-bottom: 0; 118 | border-top: none; 119 | border-bottom-right-radius: 0; 120 | } 121 | 122 | .code-toggle { 123 | float: right; 124 | display: inline-block; 125 | position: relative; 126 | top: -1px; 127 | background: #fafafa; 128 | border-bottom-left-radius: 4px; 129 | border-bottom-right-radius: 4px; 130 | border: 1px solid #e1e1e8; 131 | border-top: none; 132 | padding: 4px 8px; 133 | } 134 | 135 | @media (min-width: 768px) { 136 | .code-toggle { 137 | background: #fff; 138 | } 139 | } 140 | 141 | .code-toggle.open { 142 | background: #f8f5ec; 143 | } 144 | 145 | /* Minimal CSS Needed for contained modals */ 146 | .modal-container { 147 | position: relative; 148 | } 149 | .modal-container .modal, .modal-container .modal-backdrop { 150 | position: absolute; 151 | } 152 | 153 | -------------------------------------------------------------------------------- /demo/res/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/weiyinteo/scalajs-react-bootstrap/34512f77fe9524bc1bdd4518454861d6339cacf0/demo/res/logo.png -------------------------------------------------------------------------------- /demo/src/main/scala/demo/ReactApp.scala: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | /** 4 | * Created by weiyin on 16/03/15. 5 | */ 6 | 7 | import com.acework.js.components.bootstrap.Nav.Nav 8 | import com.acework.js.components.bootstrap.NavBar.NavBar 9 | import demo.pages.Components 10 | import japgolly.scalajs.react._ 11 | import japgolly.scalajs.react.vdom.prefix_<^._ 12 | import org.scalajs.dom 13 | import scala.scalajs.js.JSApp 14 | import scala.scalajs.js.annotation.JSExport 15 | 16 | object ReactApp extends JSApp { 17 | 18 | case class State(index: Int) 19 | 20 | class Backend(scope: BackendScope[_, State]) { 21 | def onMenuClick(newIndex: Int) = scope.modState(_.copy(index = newIndex)) 22 | } 23 | 24 | val navMenu = ReactComponentB[(List[String], Backend)]("appMenu") 25 | .render(P => { 26 | val (data, backend) = P 27 | def element(name: String, index: Int) = 28 | <.li(^.cls := "navbar-brand", ^.onClick --> backend.onMenuClick(index), name) 29 | 30 | NavBar(componentClass = "header", staticTop = true, addClasses = "bs-docs-nav", role = "banner", toggleNavKey = "0")( 31 | Nav(addClasses = "bs-navbar-collapse", role = "navigation", eventKey = "0", id = "top")( 32 | data.zipWithIndex.map { case (name, index) => element(name, index)} 33 | ) 34 | ) 35 | }).build 36 | 37 | val container = ReactComponentB[String]("appMenu") 38 | .render(P => { 39 | <.div(^.cls := "container", 40 | P match { 41 | case "Home" => <.div("home") 42 | case "Components" => Components.content() 43 | case "Getting Started" => <.div("Getting started") 44 | } 45 | ) 46 | }).build 47 | 48 | val app = ReactComponentB[List[String]]("app") 49 | .initialState(State(0)) 50 | .backend(new Backend(_)) 51 | .render((P, S, B) => { 52 | <.div( 53 | navMenu((P, B)), 54 | container(P(S.index)) 55 | ) 56 | 57 | }).build 58 | 59 | @JSExport 60 | override def main(): Unit = 61 | app(List("Home", "Getting Started", "Components")) render dom.document.body 62 | 63 | } 64 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/examples/AlertAutoDismissable.scala: -------------------------------------------------------------------------------- 1 | package demo.examples 2 | 3 | import com.acework.js.components.bootstrap.{Button, Alert, Styles} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | 7 | /** 8 | * Created by weiyin on 13/03/15. 9 | */ 10 | object AlertAutoDismissable { 11 | 12 | case class State(alertVisible: Boolean = true) 13 | 14 | class Backend(scope: BackendScope[Unit, State]) { 15 | 16 | def handleAlertDismiss() = scope.modState(s => s.copy(alertVisible = false)) 17 | 18 | def handleAlertShow() = scope.modState(s => s.copy(alertVisible = true)) 19 | } 20 | 21 | 22 | val AlertAutoDismissable = ReactComponentB[Unit]("AlertAutoDismissable") 23 | .initialState(State(alertVisible = false)) 24 | .backend(new Backend(_)) 25 | .render((P, C, S, B) => { 26 | 27 | if (S.alertVisible) 28 | Alert.Alert(bsStyle = Styles.danger, onDismiss = () => B.handleAlertDismiss(), dismissAfter = 2000)( 29 | <.h4("Oh snap! You got an error!"), 30 | <.p("But this will hide after 2 seconds.")) 31 | else 32 | Button.Button(onClick = (e: ReactEvent) => B.handleAlertShow())("Show Alert") 33 | }).buildU 34 | 35 | def apply() = AlertAutoDismissable() 36 | 37 | } 38 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/examples/AlertDismissable.scala: -------------------------------------------------------------------------------- 1 | package demo.examples 2 | 3 | import com.acework.js.components.bootstrap.{Alert, Button, Styles} 4 | import japgolly.scalajs.react._ 5 | import japgolly.scalajs.react.vdom.prefix_<^._ 6 | 7 | /** 8 | * Created by weiyin on 13/03/15. 9 | */ 10 | object AlertDismissable { 11 | 12 | case class State(alertVisible: Boolean = true) 13 | 14 | class Backend(scope: BackendScope[Unit, State]) { 15 | def handleAlertDismiss() = scope.modState(s => s.copy(alertVisible = false)) 16 | 17 | def handleAlertShow() = scope.modState(s => s.copy(alertVisible = true)) 18 | } 19 | 20 | 21 | val AlertDismissable = ReactComponentB[Unit]("AlertDismissable") 22 | .initialState(State(alertVisible = true)) 23 | .backend(new Backend(_)) 24 | .render((P, C, S, B) => { 25 | 26 | if (S.alertVisible) 27 | Alert.Alert(bsStyle = Styles.danger, onDismiss = () => B.handleAlertDismiss())( 28 | <.h4("Oh snap! You got an error!"), 29 | <.p("Change this and that and try again. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cras mattis consectetur purus sit amet fermentum."), 30 | <.p( 31 | Button.Button(bsStyle = Styles.danger)("Take this action"), 32 | "or", 33 | Button.Button(onClick = (e: ReactEvent) => B.handleAlertDismiss())("Hide Alert"))) 34 | else 35 | Button.Button(onClick = (e: ReactEvent) => B.handleAlertShow())("Show Alert") 36 | }).buildU 37 | 38 | def apply() = AlertDismissable() 39 | 40 | } 41 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/examples/ContainedModal.scala: -------------------------------------------------------------------------------- 1 | package demo.examples 2 | 3 | import com.acework.js.components.bootstrap.Modal.{N, B, S, P} 4 | import com.acework.js.components.bootstrap._ 5 | import japgolly.scalajs.react.ReactComponentC.{ReqProps, ConstProps} 6 | import japgolly.scalajs.react._ 7 | import japgolly.scalajs.react.vdom.prefix_<^._ 8 | import org.scalajs.dom.raw.HTMLElement 9 | import org.scalajs.dom.{Node, document} 10 | 11 | /** 12 | * Created by weiyin on 21/03/15. 13 | */ 14 | object ContainedModal { 15 | 16 | val containerRef: RefSimple[TopNode] = Ref[HTMLElement]("container") 17 | 18 | def containedModel = { 19 | Modal.Modal(bsStyle = Styles.primary, title = "Contained Modal": ReactNode, animation = true, 20 | onRequestHide = () => ())( 21 | <.div(^.className := "modal-body", 22 | <.h4("Text in a modal"), 23 | <.p("Elit est explicabo ipsum eaque dolorem blanditiis doloribus sed id ipsam, beatae, rem fuga id earum? Inventore et facilis obcaecati.") 24 | ), 25 | <.div(^.className := "modal-footer", 26 | // FIXME link onClick to onRequestHide 27 | Button(Button.Button(), "Close")) 28 | ) 29 | } 30 | 31 | class Backend(val scope: BackendScope[Unit, Unit]) 32 | 33 | val trigger = ReactComponentB[Unit]("Trigger") 34 | .stateless 35 | .backend(new Backend(_)) 36 | .render((_, _, B) => { 37 | 38 | <.div(^.className := "modal-container", ^.height := 300, ^.ref := containerRef, 39 | ModalTrigger.ModalTrigger(modal = containedModel, 40 | container = new ReferencedContainer(containerRef, B.scope))( 41 | Button(Button.Button(bsStyle = Styles.primary, bsSize = Sizes.lg), "Launch contained modal") 42 | ) 43 | ) 44 | }). 45 | buildU 46 | 47 | } 48 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/examples/ExamplePageFooter.scala: -------------------------------------------------------------------------------- 1 | package demo.examples 2 | 3 | import japgolly.scalajs.react._ 4 | import japgolly.scalajs.react.vdom.prefix_<^._ 5 | 6 | /** 7 | * Created by weiyin on 14/03/15. 8 | */ 9 | object ExamplePageFooter { 10 | val component = ReactComponentB[Unit]("ExamplePageFooter") 11 | .stateless 12 | .render((P, C) => { 13 | <.footer(^.className := "bc-docs-footer", ^.role := "contentinfo", 14 | 15 | <.p("Code licensed under ", <.a(^.href := "https://github.com/weiyinteo/scalajs-react-bootstrap/blob/master/LICENSE", ^.target := "_blank", "MIT")) 16 | ) 17 | }).buildU 18 | 19 | def apply() = component() 20 | 21 | } 22 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/examples/ExamplePageHeader.scala: -------------------------------------------------------------------------------- 1 | package demo.examples 2 | 3 | import japgolly.scalajs.react._ 4 | import japgolly.scalajs.react.vdom.prefix_<^._ 5 | 6 | /** 7 | * Created by weiyin on 14/03/15. 8 | */ 9 | object ExamplePageHeader { 10 | 11 | case class ExamplePageHeader(title: String, subTitle: String) { 12 | def apply() = component(this) 13 | } 14 | 15 | val component = ReactComponentB[ExamplePageHeader]("ExamplePageHeader") 16 | .stateless 17 | .render((P, C) => { 18 | <.div(^.className := "bs-doc-header", ^.id := "content", 19 | <.div(^.className := "container", 20 | <.h1(P.title), 21 | <.p(P.subTitle) 22 | ) 23 | ) 24 | }).build 25 | 26 | } 27 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/examples/util/CodeContent.scala: -------------------------------------------------------------------------------- 1 | package demo.examples.util 2 | 3 | import japgolly.scalajs.react._ 4 | import japgolly.scalajs.react.vdom.prefix_<^._ 5 | 6 | import org.scalajs.dom.document 7 | import org.scalajs.dom.ext.PimpedNodeList 8 | 9 | /** 10 | * Created by weiyin on 16/03/15. 11 | */ 12 | object CodeContent { 13 | 14 | case class Content(scalaSource: String, el: ReactNode, exampleClasses: String = "") { 15 | def apply() = component(this) 16 | } 17 | 18 | case class State(showCode: Boolean = false) 19 | 20 | class Backend(scope: BackendScope[Content, State]) { 21 | 22 | def toggleCodeMode(e: ReactEvent) = { 23 | e.preventDefault() 24 | scope.modState(s => s.copy(showCode = !s.showCode)) 25 | } 26 | } 27 | 28 | def installSyntaxHighlighting[P, S, B] = 29 | (_: ReactComponentB[P, S, B]) 30 | .componentDidMount(_ => applySyntaxHighlight()) 31 | .componentDidUpdate((_, _, _) => applySyntaxHighlight()) 32 | 33 | 34 | def applySyntaxHighlight() = { 35 | import scala.scalajs.js.Dynamic.{global => g} 36 | val nodeList = document.querySelectorAll("pre code").toArray 37 | nodeList.foreach(n => g.hljs.highlightBlock(n)) 38 | } 39 | 40 | val component = ReactComponentB[Content]("CodeContent") 41 | .initialState(State()) 42 | .backend(new Backend(_)) 43 | .render((P, C, S, B) => { 44 | <.div(^.className := "playground", 45 | <.div(^.className := s"bs-example ${P.exampleClasses}", 46 | <.div(P.el)), 47 | if (S.showCode) 48 | <.div( 49 | <.pre(<.code(P.scalaSource.trim)), 50 | <.a(^.className := "code-toggle", ^.onClick ==> B.toggleCodeMode, ^.href := "#", "Hide code") 51 | ) 52 | else 53 | <.a(^.className := "code-toggle", ^.onClick ==> B.toggleCodeMode, ^.href := "#", "Show code") 54 | 55 | ) 56 | }).configure(installSyntaxHighlighting) 57 | .build 58 | } 59 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/Alerts.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.{AlertAutoDismissable, AlertDismissable} 5 | import demo.examples.util.CodeContent 6 | import japgolly.scalajs.react._ 7 | import japgolly.scalajs.react.vdom.prefix_<^._ 8 | 9 | /** 10 | * Created by weiyin on 16/03/15. 11 | */ 12 | object Alerts { 13 | 14 | val exampleSource = 15 | """ 16 | |Alert.Alert(bsStyle = Styles.warning)(<.strong("Holy guacamole!"), " Best check yo self, you're not looking too good.")) 17 | """.stripMargin 18 | 19 | def exampleContent = CodeContent.Content(exampleSource, 20 | Alert.Alert(bsStyle = Styles.warning)(<.strong("Holy guacamole!"), " Best check yo self, you're not looking too good.")) 21 | 22 | val closableSource = 23 | """ 24 | |object AlertDismissable { 25 | | case class State(alertVisible: Boolean = true) 26 | | 27 | | class Backend(scope: BackendScope[Unit, State]) { 28 | | def handleAlertDismiss() = scope.modState(s => s.copy(alertVisible = false)) 29 | | 30 | | def handleAlertShow() = scope.modState(s => s.copy(alertVisible = true)) 31 | | } 32 | | 33 | | 34 | | val AlertDismissable = ReactComponentB[Unit]("AlertDismissable") 35 | | .initialState(State(alertVisible = true)) 36 | | .backend(new Backend(_)) 37 | | .render((P, C, S, B) => { 38 | | 39 | | if (S.alertVisible) 40 | | Alert.Alert(bsStyle = Styles.danger, onDismiss = () => B.handleAlertDismiss())( 41 | | <.h4("Oh snap! You got an error!"), 42 | | <.p("Change this and that and try again. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cras mattis consectetur purus sit amet fermentum."), 43 | | <.p( 44 | | Button.Button(bsStyle = Styles.danger)("Take this action"), 45 | | "or", 46 | | Button.Button(onClick = (e: ReactEvent) => B.handleAlertDismiss())("Hide Alert"))) 47 | | else 48 | | Button.Button(onClick = (e: ReactEvent) => B.handleAlertShow())("Show Alert") 49 | | }).buildU 50 | | 51 | | def apply() = AlertDismissable() 52 | |} 53 | | 54 | |AlertDismissable() 55 | """.stripMargin 56 | 57 | def closableContent = CodeContent.Content(closableSource, AlertDismissable()) 58 | 59 | val autoClosableSource = 60 | """ 61 | |object AlertAutoDismissable { 62 | | 63 | | case class State(alertVisible: Boolean = true) 64 | | 65 | | class Backend(scope: BackendScope[Unit, State]) { 66 | | 67 | | def handleAlertDismiss() = scope.modState(s => s.copy(alertVisible = false)) 68 | | 69 | | def handleAlertShow() = scope.modState(s => s.copy(alertVisible = true)) 70 | | } 71 | | 72 | | 73 | | val AlertAutoDismissable = ReactComponentB[Unit]("AlertAutoDismissable") 74 | | .initialState(State(alertVisible = false)) 75 | | .backend(new Backend(_)) 76 | | .render((P, C, S, B) => { 77 | | 78 | | if (S.alertVisible) 79 | | Alert.Alert(bsStyle = Styles.danger, onDismiss = () => B.handleAlertDismiss(), dismissAfter = 2000)( 80 | | <.h4("Oh snap! You got an error!"), 81 | | <.p("But this will hide after 2 seconds.")) 82 | | else 83 | | Button.Button(onClick = (e: ReactEvent) => B.handleAlertShow())("Show Alert") 84 | | }).buildU 85 | | 86 | | def apply() = AlertAutoDismissable() 87 | |} 88 | | 89 | |AlertAutoDismissable() 90 | """.stripMargin 91 | 92 | def autoClosableContent = CodeContent.Content(autoClosableSource, AlertAutoDismissable()) 93 | 94 | val contextualSource = 95 | """ 96 | |<.div( 97 | | Alert.Alert(bsStyle = Styles.success)("Well done! You successfully read this important alert message."), 98 | | Alert.Alert(bsStyle = Styles.info)("Heads up! This alert needs your attention, but it's not super important."), 99 | | Alert.Alert(bsStyle = Styles.warning)("Warning! Better check yourself, you're not looking too good."), 100 | | Alert.Alert(bsStyle = Styles.danger)("Oh snap! Change a few things up and try submitting again.") 101 | |) 102 | """.stripMargin 103 | 104 | def contextualContent = CodeContent.Content(contextualSource, 105 | <.div( 106 | Alert.Alert(bsStyle = Styles.success)("Well done! You successfully read this important alert message."), 107 | Alert.Alert(bsStyle = Styles.info)("Heads up! This alert needs your attention, but it's not super important."), 108 | Alert.Alert(bsStyle = Styles.warning)("Warning! Better check yourself, you're not looking too good."), 109 | Alert.Alert(bsStyle = Styles.danger)("Oh snap! Change a few things up and try submitting again.") 110 | ) 111 | ) 112 | 113 | val content = Section("alerts", <.span("Alert messages ", <.small("alert")) 114 | , SubSection("alerts-example", "Example Alerts", 115 | <.p("Basic alert styles."), 116 | exampleContent(), 117 | <.p("Closeable alerts, just pass in a ", <.code("onDismiss"), " function."), 118 | closableContent(), 119 | <.p("Auto close after a set time with ", <.code("dismissAfter"), " prop"), 120 | autoClosableContent()) 121 | , SubSection("alerts-contextual", "Contextual alternatives", 122 | <.p("Like other components, easily make an alert more meaningful to a particular context by adding a ", <.code("bsStyle"), " prop."), 123 | contextualContent()) 124 | ) 125 | } 126 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/Badges.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.util.CodeContent 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | /** 9 | * Created by weiyin on 16/03/15. 10 | */ 11 | object Badges { 12 | 13 | val exampleSource = 14 | """ 15 | |<.p("Badges ", Badge(42)) 16 | """.stripMargin 17 | 18 | def exampleContent = CodeContent.Content(exampleSource, 19 | <.p("Badges ", Badge(42)) 20 | ) 21 | 22 | val content = Section("badges", <.span("Badges") 23 | , <.p("Easily highlight new or unread items by adding a ", <.code("Badge"), " to links, Bootstrap navs, and more") 24 | , SubSection("badges-example", "Example", 25 | exampleContent()) 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/Carousels.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.util.CodeContent 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | import scala.scalajs.js.UndefOr 9 | import scala.scalajs.js.UndefOr._ 10 | 11 | /** 12 | * Created by weiyin on 16/03/15. 13 | */ 14 | object Carousels { 15 | 16 | val exampleSource = 17 | """ 18 | |Carousel( 19 | | CarouselItem( 20 | | <.img(^.width := 900, ^.height := 500, ^.alt := "900x500", ^.src := "res/carousel.png"), 21 | | <.div(^.className := "carousel-caption", 22 | | <.h3("First slide label"), 23 | | <.p("Nulla vitae elit libero, a pharetra augue mollis interdum.") 24 | | ) 25 | | ), 26 | | CarouselItem( 27 | | <.img(^.width := 900, ^.height := 500, ^.alt := "900x500", ^.src := "res/carousel.png"), 28 | | <.div(^.className := "carousel-caption", 29 | | <.h3("Second slide label"), 30 | | <.p("Lorem ipsum dolor sit amet, consectetur adipiscing elit.") 31 | | ) 32 | | ), 33 | | CarouselItem( 34 | | <.img(^.width := 900, ^.height := 500, ^.alt := "900x500", ^.src := "res/carousel.png"), 35 | | <.div(^.className := "carousel-caption", 36 | | <.h3("Third slide label"), 37 | | <.p("Praesent commodo cursus magna, vel scelerisque nisl consectetur.") 38 | | ) 39 | | ) 40 | |) 41 | """.stripMargin 42 | 43 | def exampleContent = CodeContent.Content(exampleSource, 44 | Carousel( 45 | CarouselItem( 46 | <.img(^.width := 900, ^.height := 500, ^.alt := "900x500", ^.src := "res/carousel.png"), 47 | <.div(^.className := "carousel-caption", 48 | <.h3("First slide label"), 49 | <.p("Nulla vitae elit libero, a pharetra augue mollis interdum.") 50 | ) 51 | ), 52 | CarouselItem( 53 | <.img(^.width := 900, ^.height := 500, ^.alt := "900x500", ^.src := "res/carousel.png"), 54 | <.div(^.className := "carousel-caption", 55 | <.h3("Second slide label"), 56 | <.p("Lorem ipsum dolor sit amet, consectetur adipiscing elit.") 57 | ) 58 | ), 59 | CarouselItem( 60 | <.img(^.width := 900, ^.height := 500, ^.alt := "900x500", ^.src := "res/carousel.png"), 61 | <.div(^.className := "carousel-caption", 62 | <.h3("Third slide label"), 63 | <.p("Praesent commodo cursus magna, vel scelerisque nisl consectetur.") 64 | ) 65 | ) 66 | ) 67 | ) 68 | 69 | val content = Section("carousels", <.span("Carousels ", <.small("Carousel, CarouselItem")) 70 | , SubSection("carousels-example", "Example carousels", 71 | <.h3("Uncontrolled"), 72 | <.p("Allow the component to control its own state."), 73 | exampleContent() 74 | ) 75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/Glyphicons.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.util.CodeContent 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | /** 9 | * Created by weiyin on 16/03/15. 10 | */ 11 | object Glyphicons { 12 | 13 | val defaultSource = 14 | """ 15 | |<.div( 16 | | ButtonToolbar( 17 | | ButtonGroup( 18 | | Button(Glyphicon.Glyphicon(glyph = "align-left")()), 19 | | Button(Glyphicon.Glyphicon(glyph = "align-center")()), 20 | | Button(Glyphicon.Glyphicon(glyph = "align-right")()), 21 | | Button(Glyphicon.Glyphicon(glyph = "align-justify")()) 22 | | ) 23 | | ), 24 | | ButtonToolbar( 25 | | ButtonGroup( 26 | | Button.Button(bsSize = Sizes.lg)(Glyphicon.Glyphicon(glyph = "star")(), "Star"), 27 | | Button.Button()(Glyphicon.Glyphicon(glyph = "star")(), "Star"), 28 | | Button.Button(bsSize = Sizes.sm)(Glyphicon.Glyphicon(glyph = "star")(), "Star"), 29 | | Button.Button(bsSize = Sizes.xs)(Glyphicon.Glyphicon(glyph = "star")(), "Star") 30 | | ) 31 | | ) 32 | |) 33 | """.stripMargin 34 | 35 | def exampleContent = CodeContent.Content(defaultSource, 36 | <.div( 37 | ButtonToolbar( 38 | ButtonGroup( 39 | Button(Glyphicon.Glyphicon(glyph = "align-left")()), 40 | Button(Glyphicon.Glyphicon(glyph = "align-center")()), 41 | Button(Glyphicon.Glyphicon(glyph = "align-right")()), 42 | Button(Glyphicon.Glyphicon(glyph = "align-justify")()) 43 | ) 44 | ), 45 | ButtonToolbar( 46 | ButtonGroup( 47 | Button.Button(bsSize = Sizes.lg)(Glyphicon.Glyphicon(glyph = "star")(), "Star"), 48 | Button.Button()(Glyphicon.Glyphicon(glyph = "star")(), "Star"), 49 | Button.Button(bsSize = Sizes.sm)(Glyphicon.Glyphicon(glyph = "star")(), "Star"), 50 | Button.Button(bsSize = Sizes.xs)(Glyphicon.Glyphicon(glyph = "star")(), "Star") 51 | ) 52 | ) 53 | ) 54 | ) 55 | 56 | val content = Section("glyphicons", "Glyphicons" 57 | , <.p("Use them in buttons, button groups for a toolbar, navigation, or prepended form inputs.") 58 | , SubSection("glyphicons-example", "Example", 59 | exampleContent()) 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/Grids.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.util.CodeContent 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | /** 9 | * Created by weiyin on 16/03/15. 10 | */ 11 | object Grids { 12 | 13 | val exampleSource = 14 | """ 15 | |Grid( 16 | | Row.Row(addClasses = "show-grid")( 17 | | Col.Col(xs = 12, md = 8)(<.code("Col(Col.props(xs=12 md=8)")), 18 | | Col.Col(xs = 6, md = 4)(<.code("Col(Col.props(xs=6 md=4)")) 19 | | ), 20 | | Row.Row(addClasses = "show-grid")( 21 | | Col.Col(xs = 6, md = 4)(<.code("Col(Col.props(xs=6 md=4)")), 22 | | Col.Col(xs = 6, md = 4)(<.code("Col(Col.props(xs=6 md=4)")), 23 | | Col.Col(xs = 6, md = 4)(<.code("Col(Col.props(xs=6 md=4)")) 24 | | ), 25 | | Row.Row(addClasses = "show-grid")( 26 | | Col.Col(xs = 6, xsOffset = 6)(<.code("Col(Col.Props(xs = 6, xsOffset = 6)")) 27 | | ), 28 | | Row.Row(addClasses = "show-grid")( 29 | | Col.Col(md = 6, mdPush = 6)(<.code("Col(Col.Props(md = 6, mdPush = 6)")), 30 | | Col.Col(md = 6, mdPull = 6)(<.code("Col(Col.Props(md = 6, mdPull = 6)")) 31 | | ) 32 | |) 33 | """.stripMargin 34 | 35 | def exampleContent = CodeContent.Content(exampleSource, 36 | Grid( 37 | Row.Row(addClasses = "show-grid")( 38 | Col.Col(xs = 12, md = 8)(<.code("Col(Col.props(xs=12 md=8)")), 39 | Col.Col(xs = 6, md = 4)(<.code("Col(Col.props(xs=6 md=4)")) 40 | ), 41 | Row.Row(addClasses = "show-grid")( 42 | Col.Col(xs = 6, md = 4)(<.code("Col(Col.props(xs=6 md=4)")), 43 | Col.Col(xs = 6, md = 4)(<.code("Col(Col.props(xs=6 md=4)")), 44 | Col.Col(xs = 6, md = 4)(<.code("Col(Col.props(xs=6 md=4)")) 45 | ), 46 | Row.Row(addClasses = "show-grid")( 47 | Col.Col(xs = 6, xsOffset = 6)(<.code("Col(Col.Props(xs = 6, xsOffset = 6)")) 48 | ), 49 | Row.Row(addClasses = "show-grid")( 50 | Col.Col(md = 6, mdPush = 6)(<.code("Col(Col.Props(md = 6, mdPush = 6)")), 51 | Col.Col(md = 6, mdPull = 6)(<.code("Col(Col.Props(md = 6, mdPull = 6)")) 52 | ) 53 | ) 54 | ) 55 | 56 | 57 | val content = Section("grids", <.span("Grids ", <.small("Grid, Row, Col")) 58 | , SubSection("grids-example", "Example grids", 59 | exampleContent() 60 | ) 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/Jumbotrons.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.util.CodeContent 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | /** 9 | * Created by weiyin on 16/03/15. 10 | */ 11 | object Jumbotrons { 12 | 13 | val exampleSource = 14 | """ 15 | |Jumbotron( 16 | | <.h1("Hello, world!"), 17 | | <.p("This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information."), 18 | | <.p(Button(Button.Props(bsStyle = Styles.primary), "Learn more")) 19 | |) 20 | """.stripMargin 21 | 22 | def exampleContent = CodeContent.Content(exampleSource, 23 | Jumbotron( 24 | <.h1("Hello, world!"), 25 | <.p("This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information."), 26 | <.p(Button(Button.Button(bsStyle = Styles.primary), "Learn more")) 27 | ) 28 | ) 29 | 30 | val content = Section("jumbotron", <.span("Jumbotron") 31 | , <.p("A lightweight, flexible component that can optionally extend the entire viewport to showcase key content on your site.") 32 | , SubSection("jumbotron-example", "Example", 33 | exampleContent()) 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/Labels.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.util.CodeContent 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | /** 9 | * Created by weiyin on 16/03/15. 10 | */ 11 | object Labels { 12 | 13 | val exampleSource = 14 | """ 15 | |<.div( 16 | | <.h1("Label ", Label("New")), 17 | | <.h2("Label ", Label("New")), 18 | | <.h3("Label ", Label("New")), 19 | | <.h4("Label ", Label("New")), 20 | | <.h5("Label ", Label("New")), 21 | | <.p("Label ", Label("New")) 22 | |) 23 | """.stripMargin 24 | 25 | def exampleContent = CodeContent.Content(exampleSource, 26 | <.div( 27 | <.h1("Label ", Label("New")), 28 | <.h2("Label ", Label("New")), 29 | <.h3("Label ", Label("New")), 30 | <.h4("Label ", Label("New")), 31 | <.h5("Label ", Label("New")), 32 | <.p("Label ", Label("New")) 33 | ) 34 | ) 35 | 36 | def variationSource = 37 | """ 38 | |<.div( 39 | | Label.Label(bsStyle = Styles.default)("Default"), 40 | | Label.Label(bsStyle = Styles.primary)("Primary"), 41 | | Label.Label(bsStyle = Styles.success)("Success"), 42 | | Label.Label(bsStyle = Styles.info)("Info"), 43 | | Label.Label(bsStyle = Styles.warning)("Warning") 44 | |) 45 | """.stripMargin 46 | 47 | def variationContent = CodeContent.Content(variationSource, 48 | <.div( 49 | Label.Label(bsStyle = Styles.default)("Default"), 50 | Label.Label(bsStyle = Styles.primary)("Primary"), 51 | Label.Label(bsStyle = Styles.success)("Success"), 52 | Label.Label(bsStyle = Styles.info)("Info"), 53 | Label.Label(bsStyle = Styles.warning)("Warning") 54 | ) 55 | ) 56 | 57 | val content = Section("labels", <.span("Labels") 58 | , SubSection("labels-example", "Example", 59 | <.p("Create a ", <.code(""), " show highlight information"), 60 | exampleContent()) 61 | , SubSection("labels-variations", "Available variations", 62 | <.p("Add any of the below mentioned modifier classes to change the appearance of a label."), 63 | variationContent()) 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/ListGroups.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.util.CodeContent 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | /** 9 | * Created by weiyin on 16/03/15. 10 | */ 11 | object ListGroups { 12 | 13 | val exampleSource = 14 | """ 15 | |ListGroup( 16 | | ListGroupItem("Item 1"), 17 | | ListGroupItem("Item 2"), 18 | | ListGroupItem("...") 19 | |) 20 | """.stripMargin 21 | 22 | def exampleContent = CodeContent.Content(exampleSource, 23 | ListGroup( 24 | ListGroupItem("Item 1"), 25 | ListGroupItem("Item 2"), 26 | ListGroupItem("...") 27 | ) 28 | ) 29 | 30 | val linkedSource = 31 | """ 32 | |ListGroup( 33 | | ListGroupItem.ListGroupItem(href = "#link1")("Link 1"), 34 | | ListGroupItem.ListGroupItem(href = "#link2")("Link 2"), 35 | | ListGroupItem.ListGroupItem(href = "#linkN")("...") 36 | |) 37 | """.stripMargin 38 | 39 | def linkedContent = CodeContent.Content(linkedSource, 40 | ListGroup( 41 | ListGroupItem.ListGroupItem(href = "#link1")("Link 1"), 42 | ListGroupItem.ListGroupItem(href = "#link2")("Link 2"), 43 | ListGroupItem.ListGroupItem(href = "#linkN")("...") 44 | ) 45 | ) 46 | 47 | def stylingStatusSource = 48 | """ 49 | |ListGroup( 50 | | ListGroupItem(ListGroupItem.Props(href = "#", active = true), "Link 1"), 51 | | ListGroupItem(ListGroupItem.Props(href = "#"), "Link 2"), 52 | | ListGroupItem(ListGroupItem.Props(href = "#", disabled = true), "Link 3") 53 | |) 54 | """.stripMargin 55 | 56 | def stylingStatusContent = CodeContent.Content(stylingStatusSource, 57 | ListGroup( 58 | ListGroupItem.ListGroupItem(href = "#", active = true)("Link 1"), 59 | ListGroupItem.ListGroupItem(href = "#")("Link 2"), 60 | ListGroupItem.ListGroupItem(href = "#", disabled = true)("Link 3") 61 | ) 62 | ) 63 | 64 | def stylingSource = 65 | """ 66 | |ListGroup( 67 | | ListGroupItem.ListGroupItem(bsStyle = Styles.success)("Success"), 68 | | ListGroupItem.ListGroupItem(bsStyle = Styles.info)("Info"), 69 | | ListGroupItem.ListGroupItem(bsStyle = Styles.warning)("Warning"), 70 | | ListGroupItem.ListGroupItem(bsStyle = Styles.danger)("Danger") 71 | |) 72 | """.stripMargin 73 | 74 | def stylingContent = CodeContent.Content(stylingSource, 75 | ListGroup( 76 | ListGroupItem.ListGroupItem(bsStyle = Styles.success)("Success"), 77 | ListGroupItem.ListGroupItem(bsStyle = Styles.info)("Info"), 78 | ListGroupItem.ListGroupItem(bsStyle = Styles.warning)("Warning"), 79 | ListGroupItem.ListGroupItem(bsStyle = Styles.danger)("Danger") 80 | ) 81 | ) 82 | 83 | def headerSource = 84 | """ 85 | |ListGroup( 86 | | ListGroupItem.ListGroupItem(header = "Heading 1": ReactNode)("Some body text"), 87 | | ListGroupItem.ListGroupItem(header = "Heading 2": ReactNode, href = "#")("Linked item"), 88 | | ListGroupItem.ListGroupItem(header = "Heading 3": ReactNode, bsStyle = Styles.danger)("Danger styling") 89 | |) 90 | """.stripMargin 91 | 92 | def headerContent = CodeContent.Content(headerSource, 93 | ListGroup( 94 | ListGroupItem.ListGroupItem(header = "Heading 1": ReactNode)("Some body text"), 95 | ListGroupItem.ListGroupItem(header = "Heading 2": ReactNode, href = "#")("Linked item"), 96 | ListGroupItem.ListGroupItem(header = "Heading 3": ReactNode, bsStyle = Styles.danger)("Danger styling") 97 | ) 98 | ) 99 | 100 | val content = Section("listgroup", <.span("ListGroup ", <.small(" ListGroup, ListGroupItem")) 101 | , <.p("Quick previous and next links.") 102 | , SubSection("listgroup-example", "Default", 103 | <.p("Centers by default."), 104 | exampleContent()) 105 | , SubSection("listgroup-linked", "Linked", 106 | <.p("Set the ", <.code("href"), " or ", <.code("onClick"), " prop on ListGroupItem, to create a linked or clickable element."), 107 | linkedContent()) 108 | , SubSection("listgroup-styling", "Styling", 109 | <.p("Set the ", <.code("active"), " or ", <.code("disabled"), " prop to true to mark or disable the item."), 110 | stylingStatusContent(), 111 | <.p("Set the ", <.code("bsStyle"), " prop to style the item."), 112 | stylingContent()) 113 | , SubSection("listgroup-header", "With header", 114 | <.p("Set the ", <.code("header"), " prop to create a structured item, with a heading and a body area."), 115 | headerContent()) 116 | ) 117 | } 118 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/Navs.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.util.CodeContent 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | import scala.scalajs.js.UndefOr 9 | import scala.scalajs.js.UndefOr._ 10 | 11 | /** 12 | * Created by weiyin on 17/03/15. 13 | */ 14 | object Navs { 15 | 16 | val exampleSource = 17 | """ 18 | |val handleSelect = (selectedKey: String) => Console.println(s"selected $selectedKey") 19 | |Nav(Nav.Props(bsStyle = Styles.pills, activeKey = "1", onSelect = handleSelect), 20 | | NavItem(NavItem.Props(eventKey = "1", href = "/home"), "NavItem 1 content"), 21 | | NavItem(NavItem.Props(eventKey = "2", title = "Item"), "NavItem 1 content"), 22 | | NavItem(NavItem.Props(eventKey = "3", disabled = true), "NavItem 3 content") 23 | |) 24 | """.stripMargin 25 | 26 | val handleSelect = (selectedKey: Seq[UndefOr[String]]) => Console.println(s"selected $selectedKey") 27 | 28 | val exampleContent = CodeContent.Content(exampleSource, { 29 | Nav.Nav(bsStyle = Styles.pills, activeKey = "1", onSelect = handleSelect)( 30 | NavItem.NavItem(eventKey = "1", href = "/home")("NavItem 1 content"), 31 | NavItem.NavItem(eventKey = "2", title = "Item")("NavItem 1 content"), 32 | NavItem.NavItem(eventKey = "3", disabled = true)("NavItem 3 content") 33 | ) 34 | } 35 | ) 36 | 37 | val dropdownSource = 38 | """ 39 | |val handleSelect = (selectedKey: String) => Console.println(s"selected $selectedKey") 40 | |Nav.Nav(bsStyle = Styles.tabs, activeKey = "1", onSelect = handleSelect)( 41 | | NavItem.NavItem(eventKey = "1", href = "/home")("NavItem 1 content"), 42 | | NavItem.NavItem(eventKey = "2", title = "Item")("NavItem 1 content"), 43 | | NavItem.NavItem(eventKey = "3", disabled = true)("NavItem 3 content"), 44 | | DropdownButton.DropdownButton(eventKey = "4", title = "Dropdown": ReactNode, navItem = true)( 45 | | MenuItem.MenuItem(eventKey = "4.1")("Action"), 46 | | MenuItem.MenuItem(eventKey = "4.2")("Another Action"), 47 | | MenuItem.MenuItem(eventKey = "4.3")("Something else here"), 48 | | MenuItem.MenuItem(divider = true)(), 49 | | MenuItem.MenuItem(eventKey = "4.4")("Separated link")) 50 | |) 51 | """.stripMargin 52 | 53 | val dropdownContent = CodeContent.Content(dropdownSource, 54 | Nav.Nav(bsStyle = Styles.tabs, activeKey = "1", onSelect = handleSelect)( 55 | NavItem.NavItem(eventKey = "1", href = "/home")("NavItem 1 content"), 56 | NavItem.NavItem(eventKey = "2", title = "Item")("NavItem 1 content"), 57 | NavItem.NavItem(eventKey = "3", disabled = true)("NavItem 3 content"), 58 | DropdownButton.DropdownButton(eventKey = "4", title = "Dropdown": ReactNode, navItem = true)( 59 | MenuItem.MenuItem(eventKey = "4.1")("Action"), 60 | MenuItem.MenuItem(eventKey = "4.2")("Another Action"), 61 | MenuItem.MenuItem(eventKey = "4.3")("Something else here"), 62 | MenuItem.MenuItem(divider = true)(), 63 | MenuItem.MenuItem(eventKey = "4.4")("Separated link")) 64 | ) 65 | ) 66 | val stackedSource = 67 | """ 68 | |val handleSelect = (selectedKey: String) => Console.println(s"selected $selectedKey") 69 | |Nav.Nav(bsStyle = Styles.pills, stacked = true, activeKey = "1", onSelect = handleSelect)( 70 | | NavItem.NavItem(eventKey = "1", href = "/home")("NavItem 1 content"), 71 | | NavItem.NavItem(eventKey = "2", title = "Item")("NavItem 1 content"), 72 | | NavItem.NavItem(eventKey = "3", disabled = true)("NavItem 3 content") 73 | |) 74 | """.stripMargin 75 | 76 | val stackedContent = CodeContent.Content(stackedSource, 77 | Nav.Nav(bsStyle = Styles.pills, stacked = true, activeKey = "1", onSelect = handleSelect)( 78 | NavItem.NavItem(eventKey = "1", href = "/home")("NavItem 1 content"), 79 | NavItem.NavItem(eventKey = "2", title = "Item")("NavItem 1 content"), 80 | NavItem.NavItem(eventKey = "3", disabled = true)("NavItem 3 content") 81 | ) 82 | ) 83 | 84 | val justifiedSource = 85 | """ 86 | |<.div( 87 | | Nav.Nav(bsStyle = Styles.tabs, justified = true, activeKey = "1", onSelect = handleSelect)( 88 | | NavItem.NavItem(eventKey = "1", href = "/home")("NavItem 1 content"), 89 | | NavItem.NavItem(eventKey = "2", title = "Item")("NavItem 1 content"), 90 | | NavItem.NavItem(eventKey = "3", disabled = true)("NavItem 3 content") 91 | | ), 92 | | <.br(), 93 | | Nav.Nav(bsStyle = Styles.pills, justified = true, activeKey = "1", onSelect = handleSelect)( 94 | | NavItem.NavItem(eventKey = "1", href = "/home")("NavItem 1 content"), 95 | | NavItem.NavItem(eventKey = "2", title = "Item")("NavItem 1 content"), 96 | | NavItem.NavItem(eventKey = "3", disabled = true)("NavItem 3 content") 97 | | ) 98 | |) 99 | """.stripMargin 100 | 101 | val justifiedContent = CodeContent.Content(justifiedSource, 102 | <.div( 103 | Nav.Nav(bsStyle = Styles.tabs, justified = true, activeKey = "1", onSelect = handleSelect)( 104 | NavItem.NavItem(eventKey = "1", href = "/home")("NavItem 1 content"), 105 | NavItem.NavItem(eventKey = "2", title = "Item")("NavItem 1 content"), 106 | NavItem.NavItem(eventKey = "3", disabled = true)("NavItem 3 content") 107 | ), 108 | <.br(), 109 | Nav.Nav(bsStyle = Styles.pills, justified = true, activeKey = "1", onSelect = handleSelect)( 110 | NavItem.NavItem(eventKey = "1", href = "/home")("NavItem 1 content"), 111 | NavItem.NavItem(eventKey = "2", title = "Item")("NavItem 1 content"), 112 | NavItem.NavItem(eventKey = "3", disabled = true)("NavItem 3 content") 113 | ) 114 | ) 115 | ) 116 | 117 | val content = Section("navs", <.span("Navs ", <.small("Nav, NavItem")) 118 | , SubSection("navs-examples", "Example navs", 119 | <.p("Navs come in two styles, ", <.code("pills"), " and ", <.code("tabs"), ". Disable a tab by adding disabled"), 120 | exampleContent()) 121 | , SubSection("navs-dropdown", "Dropdown", 122 | <.p("Add dropdowns using the ", <.code("DropdownButton"), " component. Just make sure to set ", <.code("navItem"), " property to true."), 123 | dropdownContent()) 124 | , SubSection("navs-stacked", "Stacked", 125 | <.p("They can also be ", <.code("stacked"), " vertically"), 126 | stackedContent()) 127 | , SubSection("navs-justified", "Justified", 128 | <.p("They can also be ", <.code("justified"), " to take the full width of their parent."), 129 | justifiedContent()) 130 | ) 131 | } 132 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/PageHeaders.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.util.CodeContent 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | /** 9 | * Created by weiyin on 16/03/15. 10 | */ 11 | object PageHeaders { 12 | 13 | val exampleSource = 14 | """ 15 | |PageHeader("Example page header ", <.small("Subtext"), " for header") 16 | """.stripMargin 17 | 18 | def exampleContent = CodeContent.Content(exampleSource, 19 | PageHeader("Example page header ", <.small("Subtext for header")) 20 | ) 21 | 22 | val content = Section("page-header", <.span("Page Header") 23 | , <.p("A simple shell for an ", <.code("h1"), 24 | " to appropriately space out and segment sections of content on a page. It can utilize the ", 25 | <.code("h1"), "’s default ", 26 | <.code("small"), " element, as well as most other components (with additional styles).") 27 | , SubSection("page-header-example", "Example", 28 | exampleContent()) 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/Pagers.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.util.CodeContent 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | /** 9 | * Created by weiyin on 16/03/15. 10 | */ 11 | object Pagers { 12 | 13 | val exampleSource = 14 | """ 15 | |Pager.Pager()( 16 | | PageItem.PageItem(href = "#")("Previous"), 17 | | PageItem.PageItem(href = "#")("Next") 18 | |) 19 | """.stripMargin 20 | 21 | def exampleContent = CodeContent.Content(exampleSource, 22 | Pager.Pager()( 23 | PageItem.PageItem(href = "#")("Previous"), 24 | PageItem.PageItem(href = "#")("Next") 25 | ) 26 | ) 27 | 28 | val alignedSource = 29 | """ 30 | |Pager.Pager()( 31 | | PageItem.PageItem(previous = true, href = "#")("Previous"), 32 | | PageItem.PageItem(next = true, href = "#")("Next") 33 | |) 34 | """.stripMargin 35 | 36 | def alignedContent = CodeContent.Content(alignedSource, 37 | Pager.Pager()( 38 | PageItem.PageItem(previous = true, href = "#")("Previous"), 39 | PageItem.PageItem(next = true, href = "#")("Next") 40 | ) 41 | ) 42 | 43 | val disabledSource = 44 | """ 45 | |Pager.Pager()( 46 | | PageItem.PageItem(previous = true, href = "#")("Previous"), 47 | | PageItem.PageItem(next = true, disabled = true, href = "#")("Next") 48 | |) 49 | """.stripMargin 50 | 51 | def disabledContent = CodeContent.Content(disabledSource, 52 | Pager.Pager()( 53 | PageItem.PageItem(previous = true, href = "#")("Previous"), 54 | PageItem.PageItem(next = true, disabled = true, href = "#")("Next") 55 | ) 56 | ) 57 | 58 | val content = Section("pagers", <.span("Pager ", <.small("Pager, PageItem")) 59 | , <.p("quick previous and next links") 60 | , SubSection("grids-default", "Default", 61 | <.p("centered by default"), 62 | exampleContent()) 63 | , SubSection("grids-aligned", "Aligned", 64 | <.p("Set the ", <.code("previous"), " or ", <.code("next"), " props to ", <.code("true"), " to align left or right"), 65 | alignedContent()) 66 | , SubSection("grids-disabled", "Disabled", 67 | <.p("Set the ", <.code("disabled"), " props to ", <.code("true"), " to disable the link"), 68 | disabledContent()) 69 | ) 70 | } 71 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/Progressbars.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.util.CodeContent 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | import scala.scalajs.js.UndefOr._ 9 | 10 | /** 11 | * Created by weiyin on 17/03/15. 12 | */ 13 | object Progressbars { 14 | 15 | val exampleSource = 16 | """ 17 | |ProgressBar.Props(now = 60.0)() 18 | """.stripMargin 19 | 20 | val exampleContent = CodeContent.Content(exampleSource, 21 | ProgressBar.ProgressBar(now = 60.0)() 22 | ) 23 | 24 | val withLabelSource = 25 | """ 26 | |ProgressBar.ProgressBar(now = 60.0, label = "%(percent)s%": ReactNode)() 27 | """.stripMargin 28 | 29 | val withLabelContent = CodeContent.Content(withLabelSource, 30 | ProgressBar.ProgressBar(now = 60.0, label = "%(percent)s%": ReactNode)() 31 | ) 32 | 33 | val srOnlySource = 34 | """ 35 | |ProgressBar.ProgressBar(now = 60.0, label = "%(percent)s%": ReactNode, srOnly = true)() 36 | """.stripMargin 37 | 38 | val srOnlyContent = CodeContent.Content(srOnlySource, 39 | ProgressBar.ProgressBar(now = 60.0, label = "%(percent)s%": ReactNode, srOnly = true)() 40 | ) 41 | 42 | val contextualSource = 43 | """ 44 | |<.div( 45 | | ProgressBar.ProgressBar(bsStyle = Styles.success, now = 40.0)(), 46 | | ProgressBar.ProgressBar(bsStyle = Styles.info, now = 20.0)(), 47 | | ProgressBar.ProgressBar(bsStyle = Styles.warning, now = 60.0)(), 48 | | ProgressBar.ProgressBar(bsStyle = Styles.danger, now = 80.0)() 49 | |) 50 | """.stripMargin 51 | 52 | val contextualContent = CodeContent.Content(contextualSource, 53 | <.div( 54 | ProgressBar.ProgressBar(bsStyle = Styles.success, now = 40.0)(), 55 | ProgressBar.ProgressBar(bsStyle = Styles.info, now = 20.0)(), 56 | ProgressBar.ProgressBar(bsStyle = Styles.warning, now = 60.0)(), 57 | ProgressBar.ProgressBar(bsStyle = Styles.danger, now = 80.0)() 58 | ) 59 | ) 60 | 61 | val stripedSource = 62 | """ 63 | |<.div( 64 | | ProgressBar.ProgressBar(striped = true, bsStyle = Styles.success, now = 40.0)(), 65 | | ProgressBar.ProgressBar(striped = true, bsStyle = Styles.info, now = 20.0)(), 66 | | ProgressBar.ProgressBar(striped = true, bsStyle = Styles.warning, now = 60.0)(), 67 | | ProgressBar.ProgressBar(striped = true, bsStyle = Styles.danger, now = 80.0)()) 68 | """.stripMargin 69 | 70 | val stripedContent = CodeContent.Content(stripedSource, 71 | <.div( 72 | ProgressBar.ProgressBar(striped = true, bsStyle = Styles.success, now = 40.0)(), 73 | ProgressBar.ProgressBar(striped = true, bsStyle = Styles.info, now = 20.0)(), 74 | ProgressBar.ProgressBar(striped = true, bsStyle = Styles.warning, now = 60.0)(), 75 | ProgressBar.ProgressBar(striped = true, bsStyle = Styles.danger, now = 80.0)()) 76 | ) 77 | 78 | val animatedSource = 79 | """ 80 | |ProgressBar.Props(active = true, now = 60.0)() 81 | """.stripMargin 82 | 83 | val animatedContent = CodeContent.Content(animatedSource, 84 | ProgressBar.ProgressBar(active = true, now = 60.0)() 85 | ) 86 | 87 | val stackedSource = 88 | """ 89 | |ProgressBar( 90 | | ProgressBar.withKey(1)(ProgressBar.ProgressBar(bsStyle = Styles.success, now = 20.0)), 91 | | ProgressBar.withKey(2)(ProgressBar.ProgressBar(bsStyle = Styles.warning, now = 60.0)), 92 | | ProgressBar.withKey(3)(ProgressBar.ProgressBar(bsStyle = Styles.danger, now = 80.0)) 93 | |) 94 | """.stripMargin 95 | 96 | val stackedContent = CodeContent.Content(stackedSource, 97 | ProgressBar( 98 | ProgressBar.withKey(1)(ProgressBar.ProgressBar(bsStyle = Styles.success, now = 35.0)), 99 | ProgressBar.withKey(2)(ProgressBar.ProgressBar(bsStyle = Styles.warning, now = 20.0)), 100 | ProgressBar.withKey(3)(ProgressBar.ProgressBar(bsStyle = Styles.danger, now = 10.0)) 101 | ) 102 | ) 103 | 104 | 105 | val content = Section("progressbars", <.span("Progress bars ", <.small("ProgressBar")) 106 | , <.h3("Provide up-to-date feedback on the progress of a workflow or action with simple yet flexible progress bars.") 107 | , SubSection("progressbars-examples", "Basic example", 108 | <.p("Default progressbar"), 109 | exampleContent()) 110 | , SubSection("progressbars-label", "With label", 111 | <.p("Add a ", <.code("label"), " prop to show a visible percentage. For low percentages, consider adding a min-width to ensure the label's text is fully visible."), 112 | <.p("The following keys are interpolated with the current values: ", 113 | <.code("%(min)s"), " ", <.code("%(max)s"), " ", <.code("%(now)s"), " ", <.code("%(percent)s"), " ", <.code("%(bsStyle)s")), 114 | withLabelContent()) 115 | , SubSection("progressbars-sr-only", "Screenreader only label", 116 | <.p("Add a ", <.code("srOnly"), " prop to hide the label visually."), 117 | srOnlyContent()) 118 | , SubSection("progressbars-contextual", "Contextual alternatives", 119 | <.p("Progress bars use some of the same button and alert classes for consistent styles."), 120 | contextualContent()) 121 | , SubSection("progressbars-striped", "Striped", 122 | <.p("Uses a gradient to create a striped effect. Not available in IE8."), 123 | stripedContent()) 124 | , SubSection("progressbars-animated", "Animated", 125 | <.p("Add ", <.code("active"), " prop to animate the stripes right to left. Not available in IE9 and below."), 126 | animatedContent()) 127 | , SubSection("progressbars-stacked", "Stacked", 128 | <.p("Nest ", <.code(""), "s to stack them."), 129 | stackedContent()) 130 | ) 131 | } 132 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/Section.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import japgolly.scalajs.react._ 4 | import japgolly.scalajs.react.vdom.prefix_<^._ 5 | 6 | /** 7 | * Created by weiyin on 16/03/15. 8 | */ 9 | object Section { 10 | 11 | 12 | def Section(id: String, header: ReactNode, subSections: ReactNode*) = 13 | <.div(^.className := "bs-docs-section", 14 | <.h1(^.id := id, ^.className := "page-header", header), 15 | subSections 16 | ) 17 | 18 | def apply(id: String, h: ReactNode, s: ReactNode*) = Section(id, h, s) 19 | } 20 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/SubSection.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import japgolly.scalajs.react._ 4 | import japgolly.scalajs.react.vdom.prefix_<^._ 5 | import com.acework.js.components.bootstrap._ 6 | 7 | /** 8 | * Created by weiyin on 16/03/15. 9 | */ 10 | object SubSection { 11 | def SubSection(id: String, header: ReactNode, description: ReactNode, 12 | content: ReactNode*) 13 | = <.div(<.h2(^.id := id, header), 14 | description, 15 | content) 16 | 17 | def apply(id: String, h: ReactNode, d: ReactNode, c: ReactNode*) = SubSection(id, h, d, c) 18 | 19 | } 20 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/Tables.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.util.CodeContent 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | /** 9 | * Created by weiyin on 16/03/15. 10 | */ 11 | object Tables { 12 | 13 | val exampleSource = 14 | """ 15 | |Table.Table(striped = true, bordered = true, condensed = true, hover = true)( 16 | | <.thead( 17 | | <.tr( 18 | | <.th("#"), 19 | | <.th("First Name"), 20 | | <.th("Last Name"), 21 | | <.th("User Name") 22 | | ) 23 | | ), 24 | | <.tbody( 25 | | <.tr( 26 | | <.td(1), 27 | | <.td("Mark"), 28 | | <.td("Otto"), 29 | | <.td("@mdo") 30 | | ), 31 | | <.tr( 32 | | <.td(2), 33 | | <.td("Jacob"), 34 | | <.td("Thornton"), 35 | | <.td("@fat") 36 | | ), 37 | | <.tr( 38 | | <.td(3), 39 | | <.td(^.colSpan := 2, "Larry the Bird"), 40 | | <.td("@twitter") 41 | | ) 42 | | ) 43 | |) 44 | """.stripMargin 45 | 46 | def exampleContent = CodeContent.Content(exampleSource, 47 | Table.Table(striped = true, bordered = true, condensed = true, hover = true)( 48 | <.thead( 49 | <.tr( 50 | <.th("#"), 51 | <.th("First Name"), 52 | <.th("Last Name"), 53 | <.th("User Name") 54 | ) 55 | ), 56 | <.tbody( 57 | <.tr( 58 | <.td(1), 59 | <.td("Mark"), 60 | <.td("Otto"), 61 | <.td("@mdo") 62 | ), 63 | <.tr( 64 | <.td(2), 65 | <.td("Jacob"), 66 | <.td("Thornton"), 67 | <.td("@fat") 68 | ), 69 | <.tr( 70 | <.td(3), 71 | <.td(^.colSpan := 2, "Larry the Bird"), 72 | <.td("@twitter") 73 | ) 74 | ) 75 | ) 76 | ) 77 | 78 | val responsiveSource = 79 | """ 80 | |Table.Table(responsive = true)( 81 | | <.thead( 82 | | <.tr( 83 | | <.th("#"), 84 | | <.th("Table heading"), 85 | | <.th("Table heading"), 86 | | <.th("Table heading"), 87 | | <.th("Table heading"), 88 | | <.th("Table heading"), 89 | | <.th("Table heading") 90 | | ) 91 | | ), 92 | | <.tbody( 93 | | <.tr( 94 | | <.td(1), 95 | | <.td("Table cell"), 96 | | <.td("Table cell"), 97 | | <.td("Table cell"), 98 | | <.td("Table cell"), 99 | | <.td("Table cell"), 100 | | <.td("Table cell") 101 | | ), 102 | | <.tr( 103 | | <.td(2), 104 | | <.td("Table cell"), 105 | | <.td("Table cell"), 106 | | <.td("Table cell"), 107 | | <.td("Table cell"), 108 | | <.td("Table cell"), 109 | | <.td("Table cell") 110 | | ), 111 | | <.tr( 112 | | <.td(3), 113 | | <.td("Table cell"), 114 | | <.td("Table cell"), 115 | | <.td("Table cell"), 116 | | <.td("Table cell"), 117 | | <.td("Table cell"), 118 | | <.td("Table cell") 119 | | ) 120 | | ) 121 | |) 122 | """.stripMargin 123 | 124 | def responsiveContent = CodeContent.Content(responsiveSource, 125 | Table.Table(responsive = true)( 126 | <.thead( 127 | <.tr( 128 | <.th("#"), 129 | <.th("Table heading"), 130 | <.th("Table heading"), 131 | <.th("Table heading"), 132 | <.th("Table heading"), 133 | <.th("Table heading"), 134 | <.th("Table heading") 135 | ) 136 | ), 137 | <.tbody( 138 | <.tr( 139 | <.td(1), 140 | <.td("Table cell"), 141 | <.td("Table cell"), 142 | <.td("Table cell"), 143 | <.td("Table cell"), 144 | <.td("Table cell"), 145 | <.td("Table cell") 146 | ), 147 | <.tr( 148 | <.td(2), 149 | <.td("Table cell"), 150 | <.td("Table cell"), 151 | <.td("Table cell"), 152 | <.td("Table cell"), 153 | <.td("Table cell"), 154 | <.td("Table cell") 155 | ), 156 | <.tr( 157 | <.td(3), 158 | <.td("Table cell"), 159 | <.td("Table cell"), 160 | <.td("Table cell"), 161 | <.td("Table cell"), 162 | <.td("Table cell"), 163 | <.td("Table cell") 164 | ) 165 | ) 166 | ) 167 | ) 168 | 169 | val content = Section("tables", "Tables" 170 | , SubSection("tables-default", "Default Wells", 171 | <.p("Use the ", <.code("striped"), ", ", <.code("bordered"), ", ", <.code("condensed"), 172 | " and ", <.code("hover"), " props to customise the table."), 173 | exampleContent()) 174 | , SubSection("tables-responsive", "Responsive", 175 | <.p("Add ", <.code("responsive"), " prop to make them scroll horizontally up to small devices (under 768px). When viewing on anything larger than 768px wide, you will not see any difference in these tables."), 176 | responsiveContent()) 177 | ) 178 | } 179 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/Tabs.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.util.CodeContent 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | /** 9 | * Created by weiyin on 16/03/15. 10 | */ 11 | object Tabs { 12 | 13 | val exampleSource = 14 | """ 15 | |TabbedArea.TabbedArea(defaultActiveKey = "2")( 16 | | TabPane.TabPane(eventKey = "1", tab = "Tab 1")("TabPane 1 content"), 17 | | TabPane.TabPane(eventKey = "2", tab = "Tab 2")("TabPane 2 content")) 18 | """.stripMargin 19 | 20 | def exampleContent = CodeContent.Content(exampleSource, exampleClasses = "bs-example-tabs", el = 21 | TabbedArea.TabbedArea(defaultActiveKey = "2")( 22 | TabPane.TabPane(eventKey = "1", tab = "Tab 1")("TabPane 1 content"), 23 | TabPane.TabPane(eventKey = "2", tab = "Tab 2")("TabPane 2 content")) 24 | ) 25 | 26 | val noanimationSource = 27 | """ 28 | |TabbedArea.TabbedArea(defaultActiveKey = "2", animation = false)( 29 | | TabPane.TabPane(eventKey = "1", tab = "Tab 1")("TabPane 1 content"), 30 | | TabPane.TabPane(eventKey = "2", tab = "Tab 2")("TabPane 2 content")) 31 | """.stripMargin 32 | 33 | def noanimationContent = CodeContent.Content(noanimationSource, exampleClasses = "bs-noanimation-tabs", el = 34 | TabbedArea.TabbedArea(defaultActiveKey = "2", animation = false)( 35 | TabPane.TabPane(eventKey = "1", tab = "Tab 1")("TabPane 1 content"), 36 | TabPane.TabPane(eventKey = "2", tab = "Tab 2")("TabPane 2 content")) 37 | ) 38 | 39 | 40 | val content = Section("tabs", <.span("Togglable tabs ", <.small("TabbedArea, TabPane")) 41 | , SubSection("grids-example", "Example tabs", 42 | <.p("Add quick, dynamic tab functionality to transition through panes of local content, even via dropdown menus."), 43 | <.h3("Uncontrolled"), 44 | <.p("Allow the component to control its state"), 45 | exampleContent(), 46 | <.h3("No Animation"), 47 | <.p("Set the ", <.code("animation"), " prop to ", <.code("false")), 48 | noanimationContent() 49 | ) 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /demo/src/main/scala/demo/pages/Wells.scala: -------------------------------------------------------------------------------- 1 | package demo.pages 2 | 3 | import com.acework.js.components.bootstrap._ 4 | import demo.examples.util.CodeContent 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom.prefix_<^._ 7 | 8 | /** 9 | * Created by weiyin on 16/03/15. 10 | */ 11 | object Wells { 12 | 13 | val defaultSource = 14 | """ 15 | |Well("Look I'm in a well!") 16 | """.stripMargin 17 | 18 | def defaultContent = CodeContent.Content(defaultSource, 19 | Well("Look I'm in a well!") 20 | ) 21 | 22 | val optionalSource = 23 | """ 24 | |<.div( 25 | | Well.Props(bsSize = Sizes.lg)("Look I'm in a well!"), 26 | | Well.Props(bsSize = Sizes.sm)("Look I'm in a well!") 27 | |) 28 | """.stripMargin 29 | 30 | def optionalContent = CodeContent.Content(optionalSource, 31 | <.div( 32 | Well.Well(bsSize = Sizes.lg)("Look I'm in a well!"), 33 | Well.Well(bsSize = Sizes.sm)("Look I'm in a well!") 34 | ) 35 | ) 36 | 37 | val content = Section("wells", "Wells" 38 | , <.p("Use the well as a simple effect on an element to give it an inset effect.") 39 | , SubSection("wells-default", "Default Wells", 40 | defaultContent()) 41 | , SubSection("wells-optional", "Optional classes", 42 | <.p("Control padding and rounded corners with two optional modifier classes."), 43 | optionalContent()) 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /macro/build.sbt: -------------------------------------------------------------------------------- 1 | scalaVersion := "2.11.5" -------------------------------------------------------------------------------- /macro/src/main/scala/com/acework/js/utils/Mappable.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.utils 2 | 3 | 4 | import scala.reflect.macros.blackbox 5 | 6 | import scala.language.experimental.macros 7 | 8 | trait Mappable[T] { 9 | def toMap(t: T): Map[String, Any] 10 | 11 | def fromMap(m: Map[String, Any]): T 12 | } 13 | 14 | /** 15 | * Created by weiyin on 18/03/15. 16 | */ 17 | object Mappable { 18 | implicit def materializeMappable[T]: Mappable[T] = macro materializeMappableImpl[T] 19 | 20 | def materializeMappableImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[Mappable[T]] = { 21 | import c.universe._ 22 | val tpe = weakTypeOf[T] 23 | val companion: Symbol = tpe.typeSymbol.companion 24 | 25 | // get fields from primary constructor 26 | 27 | val fields = tpe.decls.collectFirst { 28 | case m: MethodSymbol if m.isPrimaryConstructor => m 29 | }.get.paramLists.head 30 | 31 | val (toMapParams, fromMapParams) = fields.map { field => 32 | val name = field.asTerm.name 33 | val decoded = name.decodedName.toString 34 | val returnType = tpe.decl(name).typeSignature 35 | 36 | (q"$decoded -> t.$name", q"map($decoded).asInstanceOf[$returnType]") 37 | }.unzip 38 | 39 | c.Expr[Mappable[T]] { q""" 40 | new Mappable[$tpe] { 41 | def toMap(t: $tpe) = Map(..$toMapParams) 42 | def fromMap(map: Map[String, Any]) = $companion(..$fromMapParams) 43 | } 44 | """ 45 | } 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /macro/src/main/scala/com/acework/js/utils/Mergeable.scala: -------------------------------------------------------------------------------- 1 | package com.acework.js.utils 2 | 3 | import scala.reflect.macros.blackbox 4 | 5 | /** 6 | * Created by weiyin on 18/03/15. 7 | */ 8 | 9 | trait Common { 10 | 11 | def getFieldNamesAndTypes(c: blackbox.Context)(tpe: c.universe.Type): Iterable[(c.universe.Name, c.universe.Type)] = { 12 | import c.universe._ 13 | 14 | object CaseField { 15 | def unapply(trmSym: TermSymbol): Option[(Name, Type)] = { 16 | if (trmSym.isVal && trmSym.isCaseAccessor) 17 | Some((TermName(trmSym.name.toString.trim), trmSym.typeSignature)) 18 | else 19 | None 20 | } 21 | } 22 | 23 | tpe.decls.collect { 24 | case CaseField(nme, tpe) => 25 | (nme, tpe) 26 | } 27 | } 28 | } 29 | 30 | trait Mergeable[T] { 31 | def merge(t: T, map: Map[String, Any]): T 32 | } 33 | 34 | object Mergeable { 35 | 36 | implicit def materializeMergeable[T]: Mergeable[T] = macro materializeMergeableImpl[T] 37 | 38 | def materializeMergeableImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Expr[Mergeable[T]] = { 39 | import c.universe._ 40 | val tpe = weakTypeOf[T] 41 | val companion: Symbol = tpe.typeSymbol.companion 42 | 43 | // get fields from primary constructor 44 | val fields = tpe.decls.collectFirst { 45 | case m: MethodSymbol if m.isPrimaryConstructor => m 46 | }.get.paramLists.head 47 | 48 | val fromMapParams = fields.map { field => 49 | val name = field.asTerm.name 50 | val decoded = name.decodedName.toString 51 | val returnType = tpe.decl(name).typeSignature 52 | q"map.getOrElse($decoded, t.$name).asInstanceOf[$returnType]" 53 | } 54 | 55 | //val params: Iterable[Name] = tpe.decls.collect { 56 | // case param if param.isMethod && param.asMethod.isCaseAccessor => param.name 57 | // } 58 | 59 | c.Expr[Mergeable[T]] { q""" 60 | new Mergeable[$tpe] { 61 | def merge(t: $tpe, map: Map[String, Any]) = $companion(..$fromMapParams) 62 | } 63 | """ 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /project/ScalajsReactBootstrap.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | 4 | import org.scalajs.sbtplugin.ScalaJSPlugin 5 | import ScalaJSPlugin._ 6 | import ScalaJSPlugin.autoImport._ 7 | 8 | object Settings { 9 | 10 | object versions { 11 | val scala = "2.11.5" 12 | val scalajsReact = "0.8.1" 13 | } 14 | 15 | } 16 | 17 | object ScalajsReactBootstrap extends Build { 18 | 19 | def commonSettings: Project => Project = 20 | _.enablePlugins(ScalaJSPlugin) 21 | .settings( 22 | organization := "com.acework", 23 | version := "0.0.1-SNAPSHOT", 24 | scalaVersion := Settings.versions.scala, 25 | scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature", 26 | "-language:postfixOps", "-language:implicitConversions", "-language:reflectiveCalls", 27 | "-language:higherKinds", "-language:existentials") 28 | ) 29 | 30 | def preventPublication: Project => Project = 31 | _.settings( 32 | publishArtifact := false) 33 | 34 | def publicationSettings: Project => Project = 35 | _.settings( 36 | publishTo := { 37 | val nexus = "https://oss.sonatype.org/" 38 | if (isSnapshot.value) 39 | Some("snapshots" at nexus + "content/repositories/snapshots") 40 | else 41 | Some("releases" at nexus + "service/local/staging/deploy/maven2") 42 | } 43 | ) 44 | 45 | def utestSettings: Project => Project = 46 | _.configure(useReactJs("test")) 47 | .settings( 48 | libraryDependencies ++= Seq( 49 | "com.lihaoyi" %%% "utest" % "0.3.0" 50 | , "com.github.japgolly.scalajs-react" %%% "test" % "0.8.2" % "test" 51 | ) 52 | , testFrameworks += new TestFramework("utest.runner.Framework") 53 | , scalaJSStage in Test := FastOptStage 54 | , requiresDOM := true 55 | , jsEnv in Test := PhantomJSEnv().value 56 | ) 57 | 58 | def useReactJs(scope: String = "compile"): Project => Project = 59 | _.settings( 60 | jsDependencies ++= Seq( 61 | "org.webjars" % "react" % "0.12.1" % scope / "react-with-addons.js" commonJSName "React" 62 | , "org.webjars" % "jquery" % "1.11.1" / "jquery.min.js" 63 | , "org.webjars" % "bootstrap" % "3.3.2" / "bootstrap.min.js" dependsOn "jquery.min.js" 64 | , "org.webjars" % "log4javascript" % "1.4.10" / "js/log4javascript_uncompressed.js" 65 | ) 66 | , skip in packageJSDependencies := false 67 | ) 68 | 69 | lazy val root = Project("root", file(".")) 70 | .aggregate(core, test, demo) 71 | 72 | lazy val macroSub = Project("macro", file("macro")) 73 | .configure(commonSettings, publicationSettings) 74 | .settings(name := "macro", 75 | scalacOptions += "-language:experimental.macros" 76 | , libraryDependencies += "org.scala-lang" % "scala-reflect" % Settings.versions.scala 77 | ) 78 | 79 | lazy val core = project 80 | .configure(commonSettings, publicationSettings) 81 | .dependsOn(macroSub) 82 | .settings( 83 | name := "core", 84 | emitSourceMaps := true, 85 | libraryDependencies ++= Seq( 86 | "org.scala-js" %%% "scalajs-dom" % "0.8.0" 87 | , "com.github.japgolly.scalajs-react" %%% "core" % Settings.versions.scalajsReact 88 | , "com.github.japgolly.scalajs-react" %%% "extra" % Settings.versions.scalajsReact 89 | )) 90 | 91 | lazy val test = project 92 | .configure(commonSettings, publicationSettings, utestSettings) 93 | .dependsOn(core) 94 | .settings( 95 | name := "test", 96 | scalacOptions += "-language:reflectiveCalls") 97 | 98 | lazy val demo = Project("demo", file("demo")) 99 | .dependsOn(core) 100 | .configure(commonSettings, useReactJs(), preventPublication) 101 | .settings( 102 | emitSourceMaps := true, 103 | artifactPath in(Compile, fullOptJS) := file("demo/res/demo.js") 104 | ) 105 | } 106 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.1") 2 | 3 | addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.3") 4 | -------------------------------------------------------------------------------- /test/src/test/scala/test/CoreTest.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import com.acework.js.components.bootstrap._ 4 | 5 | import japgolly.scalajs.react._ 6 | import japgolly.scalajs.react.vdom._ 7 | import japgolly.scalajs.react.vdom.prefix_<^._ 8 | import scala.scalajs.js, js.{Array => JArray} 9 | 10 | import utest._ 11 | import TestUtil._ 12 | 13 | object CoreTest extends TestSuite { 14 | val tests = TestSuite { 15 | 'alerts { 16 | 'ExampleAlert { 17 | Alert(Alert.Alert(bsStyle = Styles.warning), 18 | <.strong("Holy guacamole!"), " Best check yo self, you're not looking too good.") shouldRender 19 | "
Holy guacamole! Best check yo self, you're not looking too good.
" 20 | } 21 | 22 | } 23 | 24 | 'buttons { 25 | Button("Hi") shouldRender "" 26 | } 27 | 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /test/src/test/scala/test/TestUtil.scala: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import japgolly.scalajs.react.{React, ReactElement} 4 | 5 | /** 6 | * Created by weiyin on 16/03/15. 7 | */ 8 | object TestUtil { 9 | def assertRender(comp: ReactElement, expected: String): Unit = { 10 | val rendered: String = React.renderToStaticMarkup(comp) 11 | assert(rendered == expected, s"found $rendered") 12 | } 13 | 14 | implicit class ReactComponentUAS(val c: ReactElement) extends AnyVal { 15 | def shouldRender(expected: String) = assertRender(c, expected) 16 | } 17 | } 18 | --------------------------------------------------------------------------------